Chaining predicates to use inside .Where clause [duplicate] - c#

This question already has answers here:
Combining two expressions (Expression<Func<T, bool>>)
(10 answers)
Closed 1 year ago.
I want to create EFCore query that will return all entities that meet some conditions for their related entities.
For example entities look like this (that's pretty simplified example):
public class MyEntity
{
public int Id { get; set; }
public List<MyOtherEntity> OtherEntities { get; set; }
}
public class MyOtherEntity
{
public int Id { get; set; }
public int SomeProperty1 { get; set; }
public int SomeProperty2 { get; set; }
}
And I have a method that takes array of simplified MyOtherEntity objects:
public class MySimpleOtherEntity
{
public int SomeProperty1 { get; set; }
public int SomeProperty2 { get; set; }
}
Now I have some method that takes IEnumerable of these simplified objects, and I want to return all of the MyEntity objects that have in their relations MyOtherEntities that match all of the required conditions:
public IEnumerable<MyEntity> GetMyEntitiesByMyOtherEntities(IEnumerable<MySimpleOtherEntity> entities)
{
// example with some static values
// we want to find all MyEntities that have MyOtherEntity with value 1,2 AND MyOtherEntity with value 2,2
_dataContext
.Where(x => x.OtherEntities.Any(y => y.SomeProperty1 == 1 && y.SomeProperty2 == 2)
&&
x.OtherEntities.Any(y => y.SomeProperty1 == 2 && y.SomeProperty2 == 2)
&&
.
. // and so on
.)
.ToList();
The query above is translated correctly to SQL. I already created a solution with glueing some raw SQL parts that gives correct results, because it's just attaching AND EXISTS parts with proper subqueries.
That being said I would (if possible) rather like to have it as some dynamic LINQ Where expression. SQL parser creates pretty much as good SQL as I would do for this example, but with raw SQL queries I lose some of the control that EFCore gives me.
I created some list of predicates that I would like to chain together and inject into .Where:
public IEnumerable<MyEntity> GetMyEntitiesByMyOtherEntities(IEnumerable<MySimpleOtherEntity> entities)
{
var predicates = new List<Expression<Func<MyEntity, bool>>>();
foreach(var entity in entities)
{
predicates.Add(x => x.OtherEntities.Any(y => y.SomeProperty1 == entity.SomeProperty1
&& y.SomeProperty2 == entity.SomeProperty2);
}
}
Unfortunately I don't know how to chain them properly. I tried to use
var combinedPredicate = predicates.Aggregate((l, r) => Expression.AndAlso(l, r));
But it has some casting issues (probably related to AndAlso returning BinaryExpression?) that won't allow me to do it in such simple way.
How can I achieve that so it's not overly complicated?

Since it's a "And" that should be applied between each condition why you do not use "Where" multiple time ?
var predicates = ...
var myElements = ...
foreach(var predicate in predicate)
{
myElements = myElements.Where(predicate);
}
The aggregatation you tried to do with expression could work but will be a bit more complicated.
EDIT here is how you can do it by aggregating expressions :
var param = predicates.First().Parameters.First();
var body = predicates.Select(s => s.Body).Aggregate(Expression.AndAlso);
var lambda = (Expression<Func<Temp, bool>>)Expression.Lambda(body, param);
So the first part of the code is not so difficult. Let's say you have two predicates :
t => t.Value < 10;
t => t.Value > 5;
The first parameter will be kept (t, I'll explain why later).
Then we extract the body of the expression so we get :
t.Value < 10;
t.Value > 5;
Then we aggregate them with an "And" :
t.Value < 10 && t.Value > 5
Then we create a lambda again :
t => t.Value < 10 && t.Value > 5
So everything seems fine but if you try to compile it you will get an error.
Why? Everything seems OK visually.
It's because the "t" at the beginning and the "t" in the second condition are not the same... They have the same name but they come from different expressions (so different objects were created and the name is not enough to same they are the same...).
In order to solve that you need to check every time the parameter is used to replace it by the same value
You need to implement a "visitor" (from the Visitor pattern) that will inspect the whole expression to replace usage of the parameter :
public static class ExpressionHelper
{
public static Expression<Func<T, bool>> ReplaceParameters<T>(this Expression<Func<T, bool>> expression, ParameterExpression param)
{
return (Expression<Func<T, bool>>)new ReplaceVisitor<T>(param).Modify(expression);
}
private class ReplaceVisitor<T> : ExpressionVisitor
{
private readonly ParameterExpression _param;
public ReplaceVisitor(ParameterExpression param)
{
_param = param;
}
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == typeof(T) ? _param : node;
}
}
}
This implementation is naive and have surely a lot of flaws but in basic cases like this it will be enough I think.
Then you can use it by adding this line to the first block of code :
lambda = lambda.ReplaceParameters(param);
And you can now use it with EF... Or even for an in memory object:
var result = lambda.Compile()(new Temp() {Value = 5});

Related

How can I construct a where clause in C#/EF Core with the field defined at runtime?

This seems simple, but I can't decipher the LINQ required to do it. I also can't add any new dependencies.
Basically, I'm trying to make this code generic:
if (filter.matchMode.ToUpper().Equals("EQ")) {
query = query.Where(x => x.SomeField.Equals(filter.Value));
if (filter.matchMode.ToUpper().Equals("LT")) {
query = query.Where(x => x.SomeField < filter.Value);
} else [... 5 other match modes ...]
}
Now, SomeField is only one of about 5 fields that needs this functionality. And there's 5 matching operators. I could just copy/pasta the whole thing, and then deal with the debt of having to change tons of code every time a new operator or field enters the mix, but that really doesn't seem right.
Basically, I need some way of defining SomeField at runtime, so I can factor out the whole if/else tree and use it for each supported field.
I've tried going down this road, but I think I'm misunderstanding something fundamental about expressions:
var entityType = typeof(TableObject);
ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression amproperty = Expression.Property(arg, "SomeField");
MemberExpression property = Expression.Property(amproperty, "Equals"); // ???
// what's next, if this is even the right direction...
EDIT: a shorter version of the question might be: how can I construct the following object foo using MemberExpressions and lambdas, such that SomeField can be passed in as a string "SomeField":
Expression<Func<TableObject, bool>> foo = x => x.SomeField.Equals("FOO");
UPDATE: here's what I ended up coming up with:
private IQueryable<TableObject> processFilter(
IQueryable<TableObject> query,
FilterItem filter,
string fieldName)
{
var entityType = typeof(TableObject);
// construct the argument and access object
var propertyInfo = entityType.GetProperty(fieldName);
ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression access = Expression.MakeMemberAccess(arg,
typeof(TableObject).GetProperty(fieldName)
);
// translate the operation into the appropriate Expression method
Expression oprFunc;
if (filter.MatchMode.ToUpper().Equals("EQ")) {
oprfunc =
Expression.Equal(access, Expression.Constant(filter.Value));
} else if (filter.MatchMode.ToUpper().Equals("LT")) {
oprfunc =
Expression.LessThan(access, Expression.Constant(filter.IntValue));
} else {
throw new ArgumentException(
$"invalid argument: ${filter.MatchMode}"
);
}
// construct the lambda
var func = Expression.Lambda<Func<TableObject, bool>>(oprFunc, arg);
// return the new query
return query.Where(func);
}
So far, this seems to cover most of the cases. It starts to go off the rails with nullable fields and date comparisons, but it will work for what I need it to do. The part I'm still not completely sure about is Expression.MakeMemberAccess. I've seen this written many ways and I'm not sure if that's the correct way to create that expression.
Let's say you have class like this:
class SomeClass
{
public string a1 { get; set; }
public string b1 { get; set; }
public string c1 { get; set; }
}
also let's assume you have method like this:
public List<T> GetAll(Expression<Func<T, bool>> criteria )
{
return someDataLINQCapable.Where(criteria);
}
Method could be called like this:
GetAll<SomeClass>(m => m.a1 != null && m.b1=="SomethingUseful")

Dynamically building LINQ queries from string

Here's my scenario:
There is a collection of objects where each object contains a Dictionary<string, string>. The user can build a set of queries for this collection from another app to obtain a subset by selecting a Key in the Dictionary, an operator such as > or CONTAINS, etc., and a Value. They can also balance parenthesis to create groups of queries and select AND/OR operators to combine the queries.
As an example, let's say I have a collection of Car objects and the Dictionary contains keys for Make, Model, and Year.
My app is getting these queries in the form of a string like so:
"((Make = Honda) AND (Model CONTAINS Civic)) || (Year >= 2015)"
This tells me that from the collection of Car objects that I want cars that have Dictionary keys/values of <Make, Honda> and <Model, anything that contains "Civic"> OR <Year, greater than or equal to 2015>
So, I parse these out and put them into a QueryClass containing three string fields for the Key, the Operator, and the Value. I also keep track of the operator between the queries, and if they are in a group of parentheses or not.
Currently, I have to go through each QueryClass one by one performing the query, checking what the previous operator was, if it's part of a group, etc. and combining collections over and over until it reaches the end. This is tedious and seems like an awful way to do things. If there was a way to build these LINQ queries dynamically or perform SQL statements (what these are essential) on this collection it would be better.
Here's my query class that I'm storing the parsed strings in:
class QueryClass
{
public string FieldName { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
public QueryClass(string pInput)
{
var returned = RegexHelpers.SplitKeyValue(pInput); //just returns a string like "Make = Honda" into three parts
if (returned != null)
{
FieldName = returned.Item1;
Operator = returned.Item2;
Value = returned.Item3;
}
}
}
My parsing class is pretty long so I won't post the whole thing but it returns a List<object> where each element is either:
A QueryClass
"AND" or "OR"
Another List which means it's a group of queries that were grouped by parentheses, containing the two choices above.
Here's an example of the List<object> I get after parsing a string:
I then just loop through each element, determine if the value is a double or string, and execute a LINQ statement on my collection. I'm checking if the operator was "AND" or "OR" (or none if it's just one query), if it's part of a group or not, and combining the results appropriately.
Here is my implementation of converting your query into a Func. Since I wasn't sure what type was in your collection, I made an interface to represent objects that had an attributes Dictionary<string, string> and processed that.
Basically I added a method to QueryClass to convert it to an Expression. It uses a helper dictionary string->lambda that builds the appropriate comparison Expression for each operator.
Then I added a class to convert the List<object> into a Func<IItem,bool> suitable for a LINQ Where filter.
public interface IItem {
Dictionary<string, string> attributes { get; set; }
}
class QueryClass {
public string FieldName { get; set; }
public string Operator { get; set; }
public object Value { get; set; }
public QueryClass(string pInput) {
var returned = RegexHelpers.SplitKeyValue(pInput); //just returns a string like "Make = Honda" into three parts
if (returned != null) {
FieldName = returned.Item1;
Operator = returned.Item2;
Value = returned.Item3;
}
}
static MethodInfo getItemMI = typeof(Dictionary<string, string>).GetMethod("get_Item");
static Dictionary<string, Func<Expression, Expression, Expression>> opTypes = new Dictionary<string, Func<Expression, Expression, Expression>> {
{ "==", (Expression lhs, Expression rhs) => Expression.MakeBinary(ExpressionType.Equal, lhs, rhs) },
{ ">=", (Expression lhs, Expression rhs) => Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, Expression.Call(lhs, typeof(String).GetMethod("CompareTo", new[] { typeof(string) }), rhs), Expression.Constant(0)) },
{ "CONTAINS", (Expression lhs, Expression rhs) => Expression.Call(lhs, typeof(String).GetMethod("Contains"), rhs) }
};
static MemberInfo attribMI = typeof(IItem).GetMember("attributes")[0];
public Expression AsExpression(ParameterExpression p) {
var dictField = Expression.MakeMemberAccess(p, attribMI);
var lhs = Expression.Call(dictField, getItemMI, Expression.Constant(FieldName));
var rhs = Expression.Constant(Value);
if (opTypes.TryGetValue(Operator, out var exprMakerFn))
return exprMakerFn(lhs, rhs);
else
throw new InvalidExpressionException($"Unrecognized operator {Operator}");
}
}
public class LinqBuilder {
static Type TItems = typeof(IItem);
static Expression BuildOneLINQ(object term, ParameterExpression parm) {
switch (term) {
case QueryClass qc: // d => d[qc.FieldName] qc.Operator qc.Value
return qc.AsExpression(parm);
case List<object> subQuery:
return BuildLINQ(subQuery, parm);
default:
throw new Exception();
}
}
static Expression BuildLINQ(List<object> query, ParameterExpression parm) {
Expression body = null;
for (int queryIndex = 0; queryIndex < query.Count; ++queryIndex) {
var term = query[queryIndex];
switch (term) {
case string op:
var rhs = BuildOneLINQ(query[++queryIndex], parm);
var eop = (op == "AND") ? ExpressionType.AndAlso : ExpressionType.OrElse;
body = Expression.MakeBinary(eop, body, rhs);
break;
default:
body = BuildOneLINQ(term, parm);
break;
}
}
return body;
}
public static Func<IItem, bool> BuildLINQ(List<object> query) {
var parm = Expression.Parameter(TItems, "i");
return Expression.Lambda<Func<IItem, bool>>(BuildLINQ(query, parm), parm).Compile();
}
}
Once you have this, you can pass in a List<object> expression and then filter your collection. Given a query q and a collection of IItems cs, you can do:
var ans = cs.Where(LinqBuilder.BuildLINQ(q));
I would approach this problem little differently, since what you are already having is List<object>, which internally contain a QueryClass containing all the relevant fields containing information, FieldName,Operator and Value, where you are aware which of the binary expressions have to be bundled in a parentheses. Important point is how can you create a Run-time Expression to take care of all kinds of scenarios.
Following is a sample to mimic your scenario:
Sample Class
public class Car
{
public string Make {get; set;}
public string Model {get; set;}
public int Year {get; set;}
}
Query
((c.Make.Equals("Honda") AndAlso c.Model.Contains("Civic")) Or (c.Year >= 2015))
Linqpad Code
void Main()
{
var cars = new List<Car>();
Expression<Func<Car,bool>> e1 = c => c.Make.Equals("Honda");
Expression<Func<Car,bool>> e2 = c => c.Model.Contains("Civic");
Expression<Func<Car,bool>> e3 = c => c.Year >= 2015;
var expr1 = Expression.AndAlso(e1.Body,e2.Body);
var expr2 = e3;
var finalExpression = Expression.Or(expr1,expr2.Body);
finalExpression.Dump();
}
Purpose
As it can be seen that I have manually constructed the Expressions and finally Dump the final expression, since in Linqpad it provides a graphical representation of how shall the Expression be constructed dynamically, overall image is too big and deep to be pasted here (you may try yourself using LinqPad), but following are the relevant details:
Create a ParameterExpression, this acts as a lambda parameter representing the Car class object (this is independent of the fields of the Query class)
var parameterExpression = Expression.Parameter(typeof(Car),"c");
Create MemberExpression to access each relevant field of the Car class, which is used in the Query (this one needs Field property of the Query class)
var makeMemberAccessExpression = Expression.MakeMemberAccess(parameterExpression, typeof(Car).GetProperty("Make"));
var modelMemberAccessExpression = Expression.MakeMemberAccess(parameterExpression, typeof(Car).GetProperty("Model"));
var yearMemberAccessExpression = Expression.MakeMemberAccess(parameterExpression, typeof(Car).GetProperty("Year"));
Completing the Expression:
a.) c => c.Make.Equals("Honda") we create as follows: (this one needs Value property of the QueryClass)
var makeConstantExpression = Expression.Constant("Honda");
var makeEqualExpression = Expression.Equal(makeMemberAccessExpression, makeConstantExpression);
b.) c.Model.Contains("Civic") can be represented as follows here we need supply the MethodInfo for the string Contains method and create a MethodCallEXpression
var modelConstantExpression = Expression.Constant("Civic");
var stringContainsMethodInfo = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var modelContainsMethodExpression = Expression.Call(modelMemberAccessExpression, stringContainsMethodInfo, modelConstantExpression);
c.) c.Year >= 2015 can simply projected as:
var yearConstantExpression = Expression.Constant(2015);
var yearGreaterThanEqualExpression = Expression.GreaterThanOrEqual(yearMemberAccessExpression, yearConstantExpression);
Combining all together to form the Composite Expression:
Expressions a.) and b.) are combined together as follows:
((c.Make.Equals("Honda") AndAlso c.Model.Contains("Civic"))
var firstExpression = Expression.AndAlso(makeEqualExpression,modelContainsMethodExpression);
Expression c.) is independent:
c.Year >= 2015
var secondExpression = yearGreaterThanEqualExpression;
Final Combined Expression and Creation a Func Delegate
// Expressions combined via Or (||)
var finalCombinedExpression = Expression.Or(firstExpression,secondExpression);
// Create Lambda Expression
var lambda = Expression.Lambda<Func<Car,bool>>(finalCombinedExpression, parameterExpression);
// Create Func delegate via Compilation
var func = lambda.Compile();
func delegate thus can be used in any of the where clause in the Linq, which expects Func<Car,bool>
Design Suggestions
Using the explanation above and values from the Query Class placeholders or directly from the Dictionary it is feasible to create any number of Expressions to be used dynamically in the code, to be compiled and used as a Func delegate
Binary expressions like Equal, GreaterThan, LessThan, LessThanOrEqual,GreaterThanOrEqual are all exposed by the Expression trees to be used directly, For method like Contains available by default you need Reflection to get theb MethodInfo, similarly it can be done for the Static methods, just that there's object expressions
All Expressions expect values to be supplied Left to Right in the correct order, it cannot be random or incorrect order, else it will fail at run-time.
In your case, since you have few queries combined in parentheses and few independent, I would recommend creating multiple List<Expression>, where each List is combined in parentheses using AndAlso or OrElse and each list can thus be combined using And / Or
By this approach you shall be able to construct very complex requirements at runtime using Linq Expressions.
You should be able to use Linq Expressions (System.Linq.Expressions) and leverage predicates to handle your filtering.
public IQueryable<Car> GetCars(Expression<Func<Car, bool>> filter)
{
return context.Cars.Where(filter);
}
That said, the challenge will be to build your predicate expressions based off of your custom QueryClass object. To handle the filter on each Dictionary you can create a method to handle each:
public Expression<Func<Car, bool>> GetModelFilter(QueryClass modelQuery)
{
return modelQuery.Operator == "CONTAINS"? car => car.Model.Contains(modelQuery.Value) : car => car.Model == modelQuery.Value;
}
Considering you have a limited amount of filters, the above may be acceptable. However, when dealing with a large set this can also be done more dynamically, using reflection or a dynamic predicate builder but for simplicity you can follow the above.
HTH

Searching for records in several tables using EF TPT

I have a project with TPT inheritance mapping, now I need to add it a search functionality that will find records in several tables. That's what I currently have:
public abstract class Customer
{
public int Id { get; set; }
public string Memo { get; set; }
...
}
public class Person : Customer
{
public string GivenName { get; set; }
public string Surname { get; set; }
...
}
public class Company : Customer
{
public string Name { get; set; }
...
}
I also have a unit of work with bunch of repositories, and I need to add the filtering functionality to several methods of CustomerRepository. Let's say I have a Count method with the following signature
public int Count(System.Linq.Expressions.Expression<Func<Customer, bool>> filter = null)
Now I need to get the quantity of customers whose GiveName or Surname contains searchTerm in case the customer is a Person or the same searchTerm in Name field in case it is a Company.
TL;DR
How a view with a single, searchable, paged list of Customers (containing both Person and Company types) should be implemented? I mean in terms of method with signature like public IHttpActionResult Get(string searchTerm, int pageSize, int pageNumber)...
That's what I tried:
I added to each of the classes a static method that would generate an Expression to search that specific class, that's how it looks for the Person class:
public static System.Linq.Expressions.Expression<Func<Person, bool>> GetFilter(string searchTerm)
{
if (String.IsNullOrWhiteSpace(searchTerm))
{
return null;
}
var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(Person));
System.Reflection.MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
return System.Linq.Expressions.Expression.Lambda<Func<Person, bool>>(
System.Linq.Expressions.Expression.OrElse(
System.Linq.Expressions.Expression.Call(
System.Linq.Expressions.Expression.PropertyOrField(parameterExpression, "GivenName"),
method,
System.Linq.Expressions.Expression.Constant(searchTerm, typeof(string))
),
System.Linq.Expressions.Expression.Call(
System.Linq.Expressions.Expression.PropertyOrField(parameterExpression, "Surname"),
method,
System.Linq.Expressions.Expression.Constant(searchTerm, typeof(string))
)
), parameterExpression);
}
And tried to build an Expression that would check the type of the customer and then make an appropriate data check, but here I stumped... That's what I have right now:
var parameterExpression = System.Linq.Expressions.Expression.Parameter(typeof(Customer));
var typeIsPerson = System.Linq.Expressions.Expression.TypeIs(parameterExpression, typeof(Person));
var typeIsCompany = System.Linq.Expressions.Expression.TypeIs(parameterExpression, typeof(Company));
var q = System.Linq.Expressions.Expression.Block(
System.Linq.Expressions.Expression.IfThen(typeIsPerson, Person.GetFilter(searchTerm)),
System.Linq.Expressions.Expression.IfThen(typeIsCompany, Company.GetFilter(searchTerm)),
System.Linq.Expressions.Expression.Constant(false));
var a = System.Linq.Expressions.Expression.Lambda<Func<Customer, bool>>(
q, parameterExpression);
Here I have two problems(at least?), first when I try to call Count, I get a very unpleasant NotSupportedException exception that says Unknown LINQ expression of type 'Block'. The second is that I don't know how to return the result of execution for each of GetFilters, I suspect that I will get false for any record since it is default value that is the last Expression in my Block...
May be I'm on a wrong track and this is something that should be done in a completely different manner?
Expression blocks are generally unsupported in LINQ to Entities. And normally you don't need them because you could build almost any expression just using C# conditional operator ? : (which maps to Expression.Condition).
But before even trying to build an expression dynamically, you need to find a EF supported construct working with TPT (and other EF inheritance models) polymorphic query. Which is not so easy because all the examples use OfType method which is only applicable when you need to filter a concrete derived entities. With some trial and error, luckily there are two supported constructs - is and as (important: as, not cast!).
So the statically built predicate expression in question could be like this:
Expression<Func<Customer, bool>> predicate = c =>
c is Person ?
((c as Person).GivenName.Contains(searchTerm) || (c as Person).Surname.Contains(searchTerm)) :
c is Company ?
(c as Company).Name.Contains(searchTerm) :
false;
(Frankly you don't want to look at the generated SQL, but it works)
Now you can build it dynamically if you wish. You already found the is expression method (Expression.TypeIs), for as operator the corresponding expression metod is Expression.TypeAs.
You do not need to do all that. Just create a generic method that you will close when you call it. Your generic method can be like this:
public static int Count<T>(Expression<Func<T, bool>> filter = null)
{
var ctx = new StackContext();
return ctx.Customers.OfType<T>().Where(filter).Count();
}
You can call that like this:
// Here you are closing the generic to be of type Person
var personsCount = Count<Person>(x => x.GivenName == "George");
// Here you are closing the generic to be of type Customer
var companyCount = Count<Company>(x => x.Name == "Test");

LINQ filter combining exact matches like SQL IN and StartsWith matches

I'm having product entity:
public class Product : DomainBase
{
public virtual string Name { get; set; }
}
And there should be option to select products by filter, which contains an array of names, like:
public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
using (var session = Database.OpenSession())
{
var products = session.Query<Product>();
var result = products.Where(product => names.Any(name => product.Name.Contains(name)));
return result.ToList();
}
}
but it throws
System.NotSupportedException: Specified method is not supported.
What is right approach, to accomplish such filtering?
Without knowing more about what database you're connecting to or what library (is it RavenDB.. having done a quick Google?) then it's hard to be completely sure what the problem is.
However, what I think is happening is that you are giving an expression to the IQueryable "Where" extension method and the library is trying to turn that into search criteria to run against the db.. and failing because "Any" is not supported in nested criteria like that (again, I'm guessing).
The LINQ expressions that may or may not be translated into the database language (eg. SQL) vary by the library that performs the translation and vary by the database being talked to.
For example, the following (which is basically what you want to do) works fine with Entity Framework:
private static void Test(IEnumerable<string> names)
{
using (var context = new NORTHWNDEntities())
{
foreach (var product in context.Products.Where(product => names.Any(name => product.ProductName.Contains(name))))
{
Console.WriteLine(product.ProductName);
}
}
Console.ReadLine();
}
One easy option for you is to change your code to
public static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
using (var session = Database.OpenSession())
{
var products = session.Query<Product>();
return result = products.ToList().Where(product => names.Any(name => product.Name.Contains(name)));
}
}
This should work.. however, it will get all Products from the database and perform the filtering in-memory. This is less efficient than getting the database to perform the search.
An alternative would be to generate an "Expression<Func<Product, bool>>" filter yourself that is easier for the library that you're using to translate. If, instead, of a nested "Any" criteria, you could generate a simple set of "OR" name checks then there is a better change of it working. The following will achieve that - but it's quite a lot of code. If this is something that you need to do in several places then some of the code could be made more general and reused.
private static IEnumerable<Product> SearchArrayQueryLinq(IEnumerable<string> names)
{
using (var context = new NORTHWNDEntities())
{
return context.Products.Where(GetCombinedOrFilter(names)).ToList();
}
}
private static Expression<Func<Product, bool>> GetCombinedOrFilter(IEnumerable<string> names)
{
var filter = GetNameFilter(names.First());
foreach (var name in names.Skip(1))
filter = CombineFiltersAsOr(filter, GetNameFilter(name));
return filter;
}
private static Expression<Func<Product, bool>> GetNameFilter(string name)
{
return product => product.ProductName.Contains(name);
}
private static Expression<Func<Product, bool>> CombineFiltersAsOr(Expression<Func<Product, bool>> x, Expression<Func<Product, bool>> y)
{
// Combine two separate expressions into one by combining as "Or". In order for this to work, instead of there being a parameter
// for each expression, the parameter from the first expression must be shared between them both (otherwise things will go awry
// when this is translated into a database query) - this is why ParameterRebinder.ReplaceParameters is required.
var expressionParameter = x.Parameters.Single();
return Expression.Lambda<Func<Product, bool>>(
Expression.Or(x.Body, ParameterRebinder.ReplaceParameters(y.Body, toReplace: y.Parameters.Single(), replaceWith: expressionParameter)),
expressionParameter
);
}
// Borrowed and tweaked from https://blogs.msdn.microsoft.com/meek/2008/05/02/linq-to-entities-combining-predicates/
public sealed class ParameterRebinder : ExpressionVisitor
{
public static Expression ReplaceParameters(Expression expression, ParameterExpression toReplace, ParameterExpression replaceWith)
{
return new ParameterRebinder(toReplace, replaceWith).Visit(expression);
}
private readonly ParameterExpression _toReplace, _replaceWith;
private ParameterRebinder(ParameterExpression toReplace, ParameterExpression replaceWith)
{
_toReplace = toReplace;
_replaceWith = replaceWith;
}
protected override Expression VisitParameter(ParameterExpression p)
{
if (p == _toReplace)
p = _replaceWith;
return base.VisitParameter(p);
}
}
Update: I didn't notice your nhibernate tag - whoops! Using the criteria combining methods that nhibernate has is probably easier than all this.. :) I would have commented on your answer rather than updating my own but I haven't got the requisite 50 rep yet..
You are trying to mix both kinds of conditions and applying IEnumerable methods on string properties.
Your query should look like this:
var result = products.Where(product => names.Contains(product.Name));
to find exact matches.
For a combination of exact matches and StartsWith it should look like this:
var results = products.Where(product => (names.Contains(product.Name) || names.Any(name => name.StartsWith(product.Name))));
As I did a dive into NHibenrate documentation, it contains CriteriaAPI, so I came up to this
using (var session = Database.OpenSession())
{
var products = session.CreateCriteria<Product>();
if (names == null)
{
return products.List<Product>();
}
var orClause = Expression.Disjunction();
foreach (var name in names)
{
orClause.Add(Expression.Like(nameof(Product.Name), name, MatchMode.Start));
}
products.Add(orClause);
return products.List<Product>();
}

How to store Linq where condition in property

I'd like to know the most maintainable way (if exists) to store an entity condition in a parameter so to reuse it in every linq where conditions.
Suppose to have a Product entity:
public class Product
{
public bool IsDiscontinued { get; set; }
public int UnitsInStock { get; set; }
}
I'd like to add to the Product class a property IsOnSale that contains the logic to determine whether the product is on sale or not.
In this simple example the logic could be:
IsOnSale = IsDiscontinued == false && UnitsInStock > 0
Then I should be able to write a linq query of this type:
from p in context.Products
where p.IsOnSale == true
select p
The purpose of the solution should be that, if in the future the logic to determine whether the product is on sale or not changes (e.g. adding a property IsBackOrderAllowed), I don't have to edit the linq queries everywhere but simply have to change the IsOnSale property.
A similar question has been posted here but seems to address a more specific problem.
You could make a method that returns an IQueryable filtered by your conditions:
public IQueryable<Product> WhereOnSale(this IQueryable<Product> source)
{
return source.Where(p => p.IsDiscontinued == false && p.UnitsInStock > 0);
}
and you would use it like this:
from p in context.Products.WhereOnSale()
select p
If you want to do it with an expression like in Yakimych's answer then this would work:
Expression<Func<Product, bool>> isOnSale =
(p => p.IsDiscontinued == false && p.UnitsInStock > 0);
and you could use it like so:
context.Products.Where(isOnSale)
It is important that it is declared as an expression, otherwise Entity Framework won't be able to translate it into SQL because the lambda will be compiled into IL instead of an Expression Tree.
You can do something like this:
Func<Product, bool> isOnSaleFunc =
(p => p.IsDiscontinued == false && p.UnitsInStock > 0);
Then in your query you do:
context.Products.Where(isOnSaleFunc)
Update
As a result of the comment-discussion with #DoctaJonez - the filtering with such an approach will be performed on the client-side (which is or course inefficient), thus Expression<Func<Product, bool>> should be used instead of Func<Product,bool>.
First problem here is that linq to entities cannot work with properties which are not part of the model (= it can't work with custom properties).
You must define expression. If you define only Func it will be executed as Linq to objects:
public class Product
{
...
public static Expression<Func<Product, bool>> IsOnSale
{
get
{
return p => (p.IsDiscontinued == false && p.UnitsInStock > 0);
}
}
}
Now you must call the query this way:
var query = context.Products.Where(Product.IsOnSale);
Another approach is using model defined function.
I think you are looking for the Specification Pattern.
An article on using it with EF that includes a base implementation is available at http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/ .
An example of how to implement this would be to make your query
from p in context.Products
where ProductSpecifications.IsOnSale.Predicate
select p
and to use the following helper and specification definition.
public static class ProductSpecifications{
public static readonly Specification<Product> IsOnSale = new Specification<Product>(x => !x.IsDiscontinued && x.UnitsInStock > 0);
}
public class Specification<TEntity> : ISpecification<TEntity>
{
public Specification(Expression<Func<TEntity, bool>> predicate)
{
_predicate = predicate;
}
public bool IsSatisfiedBy(TEntity entity)
{
return _predicate.Compile().Invoke(entity);
}
private Expression<Func<TEntity, bool>> _predicate;
public Expression<Func<TEntity,bool>> Predicate{
get{ return _predicate; }
}
}
You can do a lot more with this pattern too, so I suggest looking into it!

Categories