Make a Search Method generic using LINQ - c#

I have a method in my project that repeats over and over:
public PAC PAC_GetByCodiPac(string codiPac)
{
var sel = _gam.PAC.Where(pac => pac.CODI_PAC == codiPac);
if (sel.Count() > 0)
return sel.First();
return null;
}
The table PAC means (patient), so I have these methods for all the tables I have.
How can I make a generic method for this?
Thanks in advance.

Here is your generic method. Note, that as others pointed out FirstOrDefault is better than count and then first, so I'm using it here. But it's also possible to write the expression so that it mimics what your original code does. Please let me know if you need additional help with this.
public static T GetByCodi<T>(IQueryable<T> table, string codi, string fieldName) where T : class
{
// x
ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
Expression currentExpression = parameter;
Type currentType = typeof(T);
PropertyInfo property = currentType.GetProperty(fieldName);
// x.CODI_xxx
currentExpression = Expression.Property(currentExpression, property);
// x.CODI_xxx == codi
currentExpression = Expression.Equal(currentExpression, Expression.Constant(codi));
// x => x.CODI_xxx == codi
LambdaExpression lambdaExpression = Expression.Lambda(currentExpression, parameter);
return table.FirstOrDefault((Func<T, bool>)lambdaExpression.Compile());
}
You use it like this:
PAC xxx = GetByCodi<PAC>(_gam.PAC, codiPac, "CODI_PAC");
Edit 1:
I changed the code according to the comment so that you can pass arbitrary ID field name in.

I see that what you asked is a very straight forward where query even doesn't require to have have it on a separate method.
Also you can simply enhance your query link the following:
public PAC PAC_GetByCodiPac(string codiPac)
{
return _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
}
FirstOrDefault will return the first item on the array, if not it will return null.

If you want a generic method that lets you specify any table and any predicate for records from that table then you can't really get any better than the built-in Where<T>(...) and (as others have already pointed out) the FirstOrDefault<T>(...) extension methods.
Your code would then look like so:
var result = _gam.PAC.Where(pac => pac.CODI_PAC == codiPac).FirstOrDefault();
// OR
var result = _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
The best you could get then, writing your own generic method, would be this:
public T FirstOrDefault<T>(IQueryable<T> source,
Expression<Func<T, bool>> predicate)
{
return source.Where(predicate).FirstOrDefault();
// OR
// return source.FirstOrDefault(predicate);
}
And that is really just redundant. Especially when your calling code would be actually longer using the helper method:
var result = FirstOrDefault(_gam.PAC, pac => pac.CODI_PAC == codiPac);
// versus
var result = _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
And even worse, your code is no longer using a fluent, composable syntax. This just makes readability and maintenance more difficult.
If you stick with using the IQueryable<T> extension methods then you can do composition like this:
var result = _gam.PAC
.Where(pac => pac.CODI_PAC == codiPac)
.Where(pac => pac.SomeOtherProperty == someOtherValue)
.FirstOrDefault();
// OR
var result = (from pac in _gam.PAC
where pac.CODI_PAC == codiPac
where pac.SomeOtherProperty == someOtherValue
select pac).FirstOrDefault();
One very important thing to note here is that the predicate parameter in the IQueryable<T>.Where<T>(...) extension method is of type Expression<Func<T, bool>>. This allows the IQueryable<T> provider to construct the native SQL (or other native provider query) at the very last moment before returning a result.
Not using Expression<Func<T, bool>> means that your query would be the equivalent of this:
var result =
_gam.PAC
.ToArray()
.Where(pac => pac.CODI_PAC == codiPac)
.FirstOrDefault();
And that would mean the query will load every record from the "PAC" table into memory before selecting the first filtered result and throwing out the rest of the results.
The bottom-line is that by making a generic helper method you are rewriting existing framework code and you open yourself to performance and maintenance issues while also reducing code readability.
I hope this helps.

I'm not sure if you are asking for this, but this method could be in a static class and method and so you'd be able to call it from everywhere.

An easy solution will be:
//a generic method
private PAC PAC_GetPAC(Func<PAC, bool> predicate)
{
return _gam.PAC.Where(predicate).FirstOrDefault();
}
public PAC PAC_GetPACById(long id)
{
return PAC_GetPAC(p => p.ID == id);
}
public PAC PAC_GetByCodiPac(string codiPac)
{
return PAC_GetPAC(p => pac.CODI_PAC == codiPac);
}

Related

Entity Framework Core Dynamic Where Clause

I have been searching and I have not seen an example like this.
I want to query a Model table to retrieve all Models from German manufacturers.
My data structure is Model Table has a Navigation Property called "Manufacturer" and an FK called "IdManufacturer"
A Manufacturer Has a Navigation Property called "Origin" and an FK called IdOrigin.
I would like to have a query that includes a where clause something like this:
(m => m.Manufacturer.Origin.Name == "German");
I would like this to be dynamically created at run time. Not only the lookup value might change, but the next time even the fields may change to, for example:
(m.Type.Name == "SUV");
Finally, there will not necessarily be a UI associated with this request, it may be generated in code.
Please don't get too hung up on the business of it, I am hoping this made up example will be simple to understand.
Any suggestions on how to handle this would be greatly appreciated. Any general thoughts on performance would be created.
My solution:
I have a model RouteList, in this model I created Expression:
public static Expression<Func<RouteList, bool>> FilterByDateOrShiftCode(RoutesFilterViewModel filter)
{
Expression<Func<RouteList, bool>> result = rl => true;
if (string.IsNullOrEmpty(filter.ShiftCode))
{
result = rl => rl.RouteListDate.Date >= filter.From.Date && rl.RouteListDate.Date <= filter.To.Date && rl.User.Id == filter.User.Id;
}
else
{
result = rl => rl.ShiftCode == filter.ShiftCode && rl.User.Id == filter.User.Id;
}
Place where I use it:
model.RouteLists = await _context.RouteLists
.Include(rl => rl.Status)
.Include(rl => rl.User)
.Include(rl => rl.RouteListRows).ThenInclude(rlr => rlr.Address)
.Where(RouteList.FilterByDateOrShiftCode(filter))
.ToListAsync();
Speed of executing grow in several times.
Try the following simple extension method. Note that this realization will work only with sting properties.
Usage:
query = query.Where("Manufacturer.Origin.Name", "German");
Realization:
public static class QueryableExtensions
{
public static IQueryable<T> Where<T>(this IQueryable<T> query, string propPath, string value)
{
var param = Expression.Parameter(typeof(T), "e");
// e.Prop1.Prop2 == value
var body = Expression.Equal(MakePropPath(param, propPath), Expression.Constant(value));
// e => e.Prop1.Prop2 == value
var predicateLambda = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(predicateLambda);
}
static Expression MakePropPath(Expression objExpression, string path)
{
return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
}
}

Using Func as parameter for LinqToEntities

I have a Linq-Query to get my EF-Data. My query joins 4 tables and selects the result to a denormalized type. I will need this query very often but with different predicates. The List-ExtensionMethods (e.g. .Where() are working with a Func<T,bool> as a parameter and I wanted to do it the same - but I don´t find a way to access my predicate in my Method.
public DenormalizedType GetData(Func<Thing, bool> predicate)
{
using (var dbContext = new MyDbContext())
{
var myData = (from some in dbContext.Thing
join other in dbContext.OtherThing
on some.OtherId equals other.Id
// => HowToWhere ???
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
}).ToList();
}
}
I have 3 questions regarding this issue.
First (obviously): how to invoke my predicate to use a dynamic where-clause?
Second: If my initial idea doesn´t work (because teovankots answer indicates, that my approach isn´t valid for LinqToEntities) is it somehow possible, to make a method of the join only?
Third: What is the best performing approach to return my results to another software-component?
EF query provider needs to translate the LINQ query expression tree to SQL, which is not possible when you pass a Func<...> (and more generally, invocation expression like delegate, unknown method etc.).
Shortly, what you ask is not possible with Func<...> type parameters.
The first thing to consider when working with Queryable methods is to use Expression<Func<...>> whenever you would use Func<..> in Enumerable methods. So change your method argument like this:
public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
Unfortunately using the supplied expression inside LINQ query syntax is not supported out of the box. To do that, you need some expression tree processing library.
The problem and the possible solution is explained in the LINQKit package page. The solution provided by the package is through AsExpandable and Invoke custom extension methods.
Install the nuget package, add
using LinqKit;
to the source code file, and now you can use something like this to achieve the goal:
public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
{
using (var dbContext = new MyDbContext())
{
var myData = (from some in dbContext.Thing.AsExpandable() // <=
join other in dbContext.OtherThing
on some.OtherId equals other.Id
where predicate.Invoke(some) // <=
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
}).ToList();
}
}
At least I´ve found a way to solve my issue - but I´d highly appreciate hints, and or ways to make it better, because I can´t imagine that this is the holy grail or even close to...
However, the first step is my join, which I return as IQueryable. Important: No using here, because otherwise the dbContext will be disposed, which is not so nice, while working with the IQueryable:
private static MyDbContext _dbContext;
private static IQueryable<DenormalizedType> SelectType()
{
_dbContext = new MyDbContext();
var myData = (from some in dbContext.Thing
join other in dbContext.OtherThing
on some.OtherId equals other.Id
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
};
return myData;
}
I´ve learned a lot today. For example: IEnumerable and IQueryable have both an extension method .Where(). But only IEnumerable.Where() has a Func<T,bool> as a parameter. IQueryable takes a Expression<Func<T,bool>> for its Where(). If I want my query to be executed, with all of my conditions I need to work with the IQueryable-type, as long as all my wheres aren´t executed. So I needed to take a closer look to the Expression-Type. I didn´t understand what all this actually does, but it works ;)
The first thing I had to do, was writing my Where-Methods.. That was pretty easy after I´ve read this one: Entity Framework Filter "Expression<Func<T, bool>>". The Method looks like this:
public static IQueryable<DenormalizedType> SelectWhereCriteria(IQueryable<DenormalizedType> data, Expression<Func<DenormalizedType, bool>> predicate)
{
return data.Where(predicate);
}
The Expression itself was a little more complicated, because I have a Selection-Enum which should select specified filters. The Expression looks like:
Expression<Func<DenormalizedType, bool>> FilterBySelection(Selection selection)
{
switch(selection)
{
case Selection.Active:
return x => x.IsActive == true;
case Selection.InActive:
return x => x.IsActive == false;
case Selection.SomeOtherSelection:
return x => x.SomeOther == "Criteria"
default:
return x => true;
}
}
This Expression works fine on my IQueryable:
var selectedQuery = DataHandler.SelectWhereCriteria(query, FilterBySelection(selection));
The only thing I needed now, was ordering. I found some very cool stuff from MarcGravell (what a genius btw) Dynamic LINQ OrderBy on IEnumerable<T> where he posted some code as an answer, which you can use, to OrderBy PropertyName. His first piece of code takes an IQueryable, orders it by PropertyName (he provides Extensions for Descending OrderyBy as well) and returns an IOrderedQueryable. The ToList()-Operation is the very last operation I execute.
And one more thing: Don´t forget to Dispose the DbContext:
public static void Dispose()
{
_dbContext.Dispose();
}
You can call it easily. Install this package, add:
public DenormalizedType GetData(Expression<Func<Thing, bool>> predicate)
{
using (var dbContext = new MyDbContext())
{
var myData = (from some in dbContext.Thing
join other in dbContext.OtherThing
on some.OtherId equals other.Id
where predicate.Invoke(some) //check this
select new DenormalizedType()
{
SomeEntry = some.Entry
SomeOtherId = some.OtherId
OtherValue = other.Value
}).ToList();
}
}
You just should know that Func<T1,T2> is a method. With this signature:
T2 predicate(T1 parameter) { /*...*/ }
Your second question depends on how you connect your conponent. But as long as you get DenormalizedType not DbEntity your example looks ok.

How can I build logic upon supplied logic in a LINQ-to-Entities Where expression?

I often come across, in LINQ for Entity Framework, a pattern where I add a .Where clause if a string value is specified, like:
IQueryable<Foo> query = Foos.AsQueryable()
if (!string.IsNullOrWhitespace(nameFilter)) query = query.Where(x => x.Name == name);
if (!string.IsNullOrWhitespace(addressFilter) != null) query = query.Where(x => x.Address == addressFilter );
if (!string.IsNullOrWhitespace(cityFilter) != null) query = query.Where(x => x.City == cityFilter );
// ...
I wanted to clean this up and avoid repeating the filter. I thought I could create an extension method:
public static IQueryable<T> WhereEqualIfSpecified<T>(
this IQueryable<T> query,
Expression<Func<T, string>> fieldDelegate,
string filterValue)
{
return string.IsNullOrWhiteSpace(filterValue)
? query
: query.Where(x => fieldDelegate(x) == filterValue); // not valid, see question below
}
So that I can instead change my code to:
IQueryable<Foo> query = Foos.AsQueryable()
.WhereEqualIfSpecified(x => x.Name, nameFilter)
.WhereEqualIfSpecified(x => x.Address, addressFilter)
.WhereEqualIfSpecified(x => x.City, cityFilter)
// ...
;
But I found that, in the WhereEqualIfSpecified method above, fieldDelegate must be compiled to a Func() to be invoked against the entity source, which ruins the point of doing these steps, which would be executed in the database in my original code.
I am missing the last step of how to create a new Expression from fieldDelegate that can do a comparison, rather than just returning the string value. Will this approach work? How do I make the necessary Expression in WhereEqualIfSpecified to allow LINQ-to-Entities to execute it later?
What you're trying to do here is to compose expressions. Expressions, unlike delegates, are a bit tricker to compose. Here is one implementation of how to compose expressions. Once you have that Compose method you can write your extension method as:
public static IQueryable<T> WhereEqualIfSpecified<T>(
this IQueryable<T> query,
Expression<Func<T, string>> fieldExpression,
string filterValue)
{
return string.IsNullOrWhiteSpace(filterValue)
? query
: query.Where(fieldExpression.Compose(value => value == filterValue);
}

Build expression tree for LINQ using List<T>.Contains method

Problem
I'm working on refactoring some LINQ queries for several reports in our web application, and I'm attempting to move some duplicate query predicates into their own IQueryable exension methods so we can reuse them for these reports, and reports in the future. As you can probably infer, I've already refactored the predicate for groups, but the predicate for codes is giving me problems. This is an example of one of the report methods I have so far:
DAL method:
public List<Entities.QueryView> GetQueryView(Filter filter)
{
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
IQueryable Extension:
public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
if (codes.Count > 0)
return query.Where(Predicates.FilterByCode<T>(codes));
return query;
}
Predicate:
public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
// Method info for List<string>.Contains(code).
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
// List of codes to call .Contains() against.
var instance = Expression.Variable(typeof(List<string>), "codes");
var param = Expression.Parameter(typeof(T), "j");
var left = Expression.Property(param, "Code");
var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}
The problem I'm having is that Queryable.Where doesn't accept a type of Expression<Func<T, List<string>, bool>. The only way I can think of creating this predicate dynamically is to use two parameters, which is the part that is really stumping me.
What I'm not comprehending is the following method works. I can pass the exact lambda expression I am trying to create dynamically, and it correctly filters my data.
public List<Entities.QueryView> GetQueryView(Filter filter)
{
// Get the codes here.
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.Where(p => codes.Contains(p.Code)) // This works fine.
//.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
Questions
Can I implement my own Queryable.Where overload? If so, is it even feasible?
If an overload isn't feasible, is there a way to dynamically construct the predicate p => codes.Contains(p.Code) without using two parameters?
Is there an easier way to do this? I feel like I'm missing something.
You can create your own extension method, name it Where, accept an IQueryable<T>, return an IQueryable<T>, and otherwise make it emulate the form of LINQ methods. It wouldn't be a LINQ method, but it would look like one. I would discourage you from writing such a method simply because it would likely confuse others; even if you want to make a new extension method, use a name not used in LINQ to avoid confusion. In short, do what you're doing now, create new extensions without actually naming them Where. If you really wanted to name one Where though nothing's stopping you.
Sure, just use a lambda:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
where T : ICoded //some interface with a `Code` field
{
return p => codes.Contains(p.Code);
}
If you really cannot have your entities implement an interface (hint: you almost certainly can), then the code would look identical to the code that you have, but using the list that you pass in as a constant rather than a new parameter:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
{
var methodInfo = typeof(List<string>).GetMethod("Contains",
new Type[] { typeof(string) });
var list = Expression.Constant(codes);
var param = Expression.Parameter(typeof(T), "j");
var value = Expression.Property(param, "Code");
var body = Expression.Call(list, methodInfo, value);
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, bool>>(body, param);
}
I would strongly encourage use of the former method; this method loses static type safety, and is more complex and as such harder to maintain.
Another note, the comment you have in your code: // j => codes.Contains(j.Code) isn't accurate. What that lambda actually looks like is: (j, codes) => codes.Contains(j.Code); which is actually noticeably different.
See the first half of #2.

How to stay DRY whilst using LINQ to Entities and helper methods?

Lets say that I have a particular way of deciding whether some strings "match", like this:
public bool stringsMatch(string searchFor, string searchIn)
{
if (string.IsNullOrEmpty(searchFor))
{
return true;
}
return searchIn != null &&
(searchIn.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
searchIn.Contains(" " + searchFor));
}
I would like to pull matches out of a database using Linq To Entities and this helper. However, when I try this:
IQueryable<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name);
I get "LINQ to Entities does not recognize the method..."
If I re-write the code as:
IQueryable<Blah> blahs = query.Where(b =>
string.IsNullOrEmpty(searchText) ||
(b.Name != null &&
(b.Name.Trim().ToLower().StartsWith(searchText.Trim().ToLower()) ||
b.Name.Contains(" " + searchText)));
Which is logically equivalent, then things work fine. The problem is that the code isn't as readable, and I have to re-write it for each different entity I want to match.
As far as I can tell from questions like this one, what I want to do is impossible at the moment, but I'm hoping that I'm missing something, am I?
If all the 'blahs' (classes) that you will be filtering have the same structure, you can use a simple method like this. The main difference is that it returns an Expression that Linq should be able to parse and it brings in the whole instance and filters on Name instead of bringing in just the string name.
public static Expression<Func<T, bool>> BuildStringMatch<T>(string searchFor) where T : IHasName
{
return b =>
string.IsNullOrEmpty(searchFor) ||
(b.Name != null &&
(b.Name.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
b.Name.Contains(" " + searchFor)));
}
You can use that method like this:
IQueryable<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText));
That assumes all your classes that you'd want to filter on implement some interface such as:
public interface IHasName
{
string Name { get; }
}
If you want to be filtering on different properties, I don't think that's something you can do with simple code like this. I believe you'll need to build the Expression yourself with reflection (or with the help of a library that uses reflection) - it's still possible but much more difficult.
Edit: It sounds like you need dynamic behavior, so I borrowed some logic from dtb's answer to this question and came up with this:
public static Expression<Func<T, bool>> BuildStringMatch<T>(Expression<Func<T, string>> property, string searchFor)
{
var searchForExpression = Expression.Constant(searchFor, typeof(string));
return
Expression.Lambda<Func<T, bool>>(
Expression.OrElse(
Expression.Call(typeof(string), "IsNullOrEmpty", null, searchForExpression),
Expression.AndAlso(
Expression.NotEqual(property.Body, Expression.Constant(null, typeof(string))),
Expression.OrElse(
Expression.Call(Expression.Call(Expression.Call(property.Body, "Trim", null), "ToLower", null), "StartsWith", null,
Expression.Call(Expression.Call(searchForExpression, "Trim", null), "ToLower", null)),
Expression.Call(property.Body, "Contains", null, Expression.Call(typeof(string), "Concat", null, Expression.Constant(" "), searchForExpression))
)
)
),
property.Parameters
);
}
You would use it like:
IQueryable<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText));
It's long and verbose but you can see how it's similiar to the original method written in straight C# code. Note: I didn't test this code, so there could be a few small problems - but that is the general idea.
Using a freely available library called LINQKit (as mentioned by #Eranga) this task becomes reasonable. Using LINQKit the code I have now looks like:
protected Expression<Func<T, bool>> stringsMatch(string searchFor, Expression<Func<T, string>> searchIn)
{
if (string.IsNullOrEmpty(searchFor))
{
return e => true;
}
return
e =>
(searchIn.Invoke(e) != null &&
(searchIn.Invoke(e).Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) ||
searchIn.Invoke(e).Contains(" " + searchFor)));
}
And needs to be called like this (note the AsExpandable() call)
IQueryable<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name));
The magic parts are the searchIn.Invoke(e) calls and the use of AsExpandable() which adds a wrapper layer that allows them to work.
The AsExpandable() bit is explained in detail by the original author here.
Note that I'm still a bit hazy on some of the details of expressions, so please add a comment/edit this answer if it can be made better/shorter/clearer.

Categories

Resources