How to get list of an entity with generic search - c#

I have a generic repository, something like this:
public IEnumerable<T> SelectAll()
{
return table.ToList();
}
public T SelectByID(object id)
{
return table.Find(id);
}
public void Insert(T obj)
{
table.Add(obj);
}
This works fine for the basic CRUD, but now I need to search an entity (table) against an user entered searchterm. Is it at all possible to do it with something like:
public IEnumerable<T> SelectAll(T obj, string searchText, string columnName)
{
// I am not sure what code to write here... It should give me all the records that contain the search term.
// I was thinking something like this could be made to work...but I need help with it.
return table.GetType().GetProperty(columnName).GetValue())ToList();
}

Use a Lambda Expression as the argument
public virtual async Task<List<T>> SearchBy(Expression<Func<Table, bool>> searchBy)
{
return await _ctx.Set<Table>().Where(searchBy).ToListAsync();
}
with this, you will call the search with a table expression argument returning a boolen

This is what I am using now:
public IEnumerable<T> SelectAll(Expression<Func<T, bool>> predicate)
{
IEnumerable<T> searchResult = context.Set<T>().Where(predicate);
return searchResult;
}
And the method call:
var lstResult = objRepo.SelectAll(x=>x.ColumnName.Contains(searchText));

Related

How to override combination of where and select of List <T>

I have following class to override list methods:
public class As400Set<T> : List<T>
{
public As400Set<T> Where(Expression<Func<T, bool>> expression)
{
//do something
}
public T SingleOrDefault(Expression<Func<T, bool>> expression)
{
//do something
}
}
Everything works. So far so good.
Now I want to extend the query with a select:
var item= context.Persons.Where(w => w.FirstName == "Hans").Select(s => s.LastName);
How can I override and the select method now? Is it even possible to query both methods one after the other?
What am I trying to achieve with my As400Set?
I am currently developing my own little OR mapper for our IBM system and would like to use it like EF. The where extension, for example, indicates the expression and query the database.
I also override for example the add method to set a changetracker state
public new void Add(T entity)
{
// do something
base.Add(entity);
}
That's why I've derived from List so far

LINQ: Customize the count method

I'm trying to implement a custom LinQ Count() method. Basically what I'm trying to achieve here is before calling the Count method, I want to filter out all elements that have the property IsDeleted set to true. So, I created an extension class and I added these methods:
public static int Count2<T>(this IEnumerable<T> source, Func<T, bool> selector)
where T : Model
{
return source.Where(x => !x.IsDeleted).Count(selector);
}
public static int Count2<T>(this IQueryable<T> source, Expression<Func<T, bool>> selector)
where T : Model
{
return source.Where(x => !x.IsDeleted).Count(selector);
}
public static int Count2<T>(this IEnumerable<T> source)
where T : Model
{
return source.Count(x => !x.IsDeleted);
}
public static int Count2<T>(this IQueryable<T> source)
where T : Model
{
return source.Count(x => !x.IsDeleted);
}
This works just find for local collections, but when executing this command for instance:
ListOfModels.Sum(x => x.PropertyThatIsAList.Count2())
and ListOfModels is an instance of IQueryable, i.e. it has to be executed in the database, it gives me this error:
The LINQ expression 'Sum()' could not be translated and will be evaluated locally.
I looked around on the web and I saw some answers saying I have to implement the IQueryableProvider but I think there is no need to go into such complicated path since the Sum() and Count() are translatable, I only need to count conditionally. Is it possible, and if it is, can anyone give me a clue on how to do it?
I suggest you instead of customizing all LinQ methods use an extended method like Validate():
public static IEnumerable<T> Validate<T>(this IEnumerable<T> list) where T: IDeleteable
{
return list.Where(w => !w.IsDeleted);
}
That IDeleteable interface is like this:
public interface IDeleteable
{
bool IsDeleted { get; set; }
}
Then use it before other methods.

how to check if IQueryable<T>.ElementType is Iterface

My (EF db first) type sits behind interface IPolicyNumber. I get IQueryable<T> and want to check what I got is correct type (does this table is searchable by that column which is determined by having that interface). Currently I am using typeof(IPolicyNumber).IsAssignableFrom(typeof(T)) which is a bit old school, I was wondering if there was a way to use something like:
IQueryable<T>.ElementType is IPolicyNumber
Full method is below:
public static IQueryable<T> ApplySearch<T>(this IQueryable<T> queryable, SearchModel search) where T : class
{
if (search != null && search.PolicyNumber.HasValue && typeof(IPolicyNumber).IsAssignableFrom(typeof(T)))
{
queryable = queryable.SearchByPolicyNumber(search);
}
return queryable;
}
public static IQueryable<IPolicyNumber> SearchByPolicyNumber<IPolicyNumber>(this IQueryable<IPolicyNumber> queryable, SearchModel search)
{
var policyNumberParameterLambda = Expression.Parameter((typeof(IPolicyNumber)));
var policyNumberColumnLambda = Expression.Property(policyNumberParameterLambda, "POLICY_NO");
var lambda = Expression.Lambda<Func<IPolicyNumber, bool>>(
Expression.Equal(policyNumberColumnLambda,
Expression.Convert(Expression.Constant(search.PolicyNumber), policyNumberColumnLambda.Type)
), policyNumberParameterLambda);
return queryable.Where(lambda);
}
Syntax to match interface to type while using IQueryable<T>.ElementType would be following:
typeof(IPolicyNumber).IsAssignableFrom(queryable.ElementType)

Creating generic Expression

I have a problem regarding generic expressions.
I have an Expression<Func<T, bool>> and I want to convert the Expression<Func<T, bool>> to Expression<Func<Y, bool>>. The object of class T has the same properties as the class Y.
Does anybody know how to solve this?
The "preferred" way to do this is use a Interface that contains the property you care about.
interface IMyProp
{
string SomeProp {get;}
}
class T : IMyProp
{
public string SomeProp
{
get
{
//Some complicated logic
}
}
}
class Y : IMyProp
{
public string SomeProp {get; set;}
}
The just code your expression to Expression<Func<IMyProp, bool>>
However it is understandable that you can not always do this, for situations like this you can use a library like AutoMapper
class T
{
public string SomeProp
{
get
{
//Some complicated logic
}
}
}
class Y
{
public string SomeProp {get; set;}
}
//Some initiation code somewhere else in your project
public static void InitializeMappings()
{
Mapper.CreateMap<T, Y>();
}
public static IQueryable<Y> FilterOnTAndMapToY(IQueryable<T> source, Expression<Func<T,bool>> filter)
{
return source.Where(filter).Project().To<Y>();
}
Now this does not exactly turn your Expression<Func<T, bool>> in to a to Expression<Func<Y, bool>> but it does let you use your T expression and use it to get a Y result after the filtering has been applied.
The way AutoMapper's Queryable Extensions work is the querys and casts to go from T to Y happen all server side when you are doing LinqToEntities. So you could even do
public static IQueryable<Y> MultiFilterCast(IQueryable<T> source, Expression<Func<T,bool>> tTypeFilter, Expression<Func<Y,bool>> yTypeFilter)
{
var filteredStage1 = source.Where(tTypeFilter);
var castToY = filteredStage1.Project().To<Y>();
var filteredStage2 = castToY.Where(yTypeFilter);
return filteredStage2;
}
both tTypeFilter and yTypeFilter will be applied server side before you get the result set.

C# generic constraint not working as expected

I have a situation where I need to dynamically build up a list of filters to apply to a list of objects. Those objects can be anything that implements an interface which contains all of the properties I need to filter on.
public interface IStuff
{
bool SuitableForSomething { get; set; }
bool SuitableForSomethingElse { get; set; }
}
public class SomeStuff : IStuff
{
...
}
public class SomeOtherStuff : IStuff
{
...
}
I have a list of criteria defined like so ...
public List<Expression<Func<IStuff, bool>>> Criteria { get; private set; }
and add criteria like so ...
Criteria.Add(x => x.SuitableForSomething);
Criteria.Add(x => x.SuitableForSomethingElse);
I then apply the criteria to my query like so ...
var stuff= _stuffCache
.GetAll()
.AsQueryable()
.ApplyCriteria(Criteria);
which uses the following extension method ...
public static IQueryable<T> ApplyCriteria<T>(this IQueryable<T> stuff, List<Expression<Func<IStuff, bool>>> criteria)
where T : IStuff
{
foreach (var expression in criteria)
{
stuff = Queryable.Where(stuff, expression);
}
return stuff;
}
The compiler is telling me ...
cannot convert from
'System.Linq.Expressions.Expression<System.Func<IStuff,bool>>'
to
'System.Linq.Expressions.Expression<System.Func<T,int,bool>>'
When I hover over the red line under the error in the IDE it's saying it cannot resolve method between
IQueryable<IStuff> Where<IStuff>(this IQueryable<IStuff>, Expression<Func<IStuff, bool>>) in class Queryable
and
IQueryable<T> Where<T>(this IQueryable<T>, Expression<Func<T,int,bool>>) in class Queryable
If I try casting the expression to Expression<Func<T, bool>>, which ought to work as T is constrained to implement the IStuff interface. I get
Cannot cast expression of type 'Expression<Func<IStuff, bool>>' to type 'Expression<Func<T, bool>>'
EDIT
Thanks to Raphaƫl's answer I fixed the extension method and eventually found the real problem I had, which was a casting problem when I was calling the code. Easily fixed by adding a .Cast<SomeStuff>() after the ApplyCriteria call.
Before
var stuff= _stuffCache
.GetAll()
.AsQueryable()
.ApplyCriteria(Criteria);
After
var stuff= _stuffCache
.GetAll()
.AsQueryable()
.ApplyCriteria(Criteria)
.Cast<SomeStuff>();
change second parameter type to List<Expression<Func<T,bool>>> (T instead of IStuff)
public static IQueryable<T> ApplyCriteria<T>(this IQueryable<T> stuff, List<Expression<Func<T, bool>>> criteria)
where T : IStuff
{
foreach (var expression in criteria)
{
stuff = Queryable.Where(stuff, expression);
//or stuff = stuff.Where(expression), as Where is an Extension method;
}
return stuff;
}
and your method can be (thx resharper), rewritten to
public static IQueryable<T> ApplyCriteria<T>(this IQueryable<T> stuff, List<Expression<Func<T, bool>>> criteria)
where T : IStuff
{
return criteria.Aggregate(stuff, (current, expression) => current.Where(expression));
}

Categories