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!
Related
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});
If I have multiple dbSets within my dbContext, for example:
public DbSet Persons { get; set; }
public DbSet Companies { get; set; }
public DbSet<...>
How could I than create a generic function to check if the value of a dynamically passed field already occurs?
Function in pseudo code:
bool IsFieldValueUnique<T>(field, fieldValue)
{
return DbSet<T>.Any(x => x.field == fieldValue)
}
And function calls could then be something like:
var result1 = IsFieldValueUnique<Person>(Person.Email, somePersonObject.Email)
var result2 = IsFieldValueUnique<Company>(Company.Identification, someCompanyObject.Identification)
When searching around, I find approaches like using '<Func<T, bool>>', but can't connect the dots together.
Func<T, bool> - Sounds like the right approach to what you want.
bool IsFieldValueUnique<T>(Func<T, bool> checkFunction)
{
var alreadyExists = DbSet<T>.Any(checkFunction);
return !alreadyExists;
//Other ways to do this, but this is just for clarity.
}
Then you call it like:
IsFieldValueUnique<Person>(person => person.Email == somePersonObject.Email)
What you have now is a method IsFieldValueUnique that takes a check function.
This check function takes in a T (of the same type as you use in the DbSet) and returns a boolean value.
person => person.Email == somePersonObject.Email is an anonymous function that for a given instance of a Person compare the Email property with somePersonObject.Email.
If you look at the signature for Any you will see similarities with the checkFunction signature.
If you want to try it on Customer you can:
IsFieldValueUnique<Customer>(cust => person.Name == someCustomer.Name);
Note:
Calling Any like this: DbSet<T>.Any(checkFunction);
Is the same as this: DbSet<T>.Any(item => checkFunction(item));
bool IsFieldValueUnique<T>(Expression<Func<T, bool>> predicate)
{
return DbSet<T>.AsQueryable().Any(predicate.Compile());
}
And use it like this:
bool unique = IsFieldValueUnique<Person>(p => p.Email == something.Email);
I want to use LinqKit's PredicateBuilder and pass the predicate into .Any method for related model.
So I want to build a predicate:
var castCondition = PredicateBuilder.New<CastInfo>(true);
if (movies != null && movies.Length > 0)
{
castCondition = castCondition.And(c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
castCondition = castCondition.And(c => c.RoleId == roleType);
}
And then use it to filter model that has relation to model in predicate:
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
But this causes a System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(Convert(__castCondition_0, Func``2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
I saw similar question and answer there suggests to use .Compile. Or one more question that build an extra predicate.
So I tried to use extra predicate
var tp = PredicateBuilder.New<Name>(true);
tp = tp.And(n => n.CastInfo.Any(castCondition.Compile()));
IQueryable<Name> result = _context.Name.AsExpandable().Where(tp);
Or use compile directly
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castCondition.Compile()));
But I have an error about Compile: System.NotSupportedException: Could not parse expression 'n.CastInfo.Any(__Compile_0)'
So is it possible to convert the result from PredicateBuilder to pass into Any?
Note: I was able to build the desired behavior combining expressions, but I don't like that I need extra variables.
System.Linq.Expressions.Expression<Func<CastInfo,bool>> castExpression = (c => true);
if (movies != null && movies.Length > 0)
{
castExpression = (c => movies.Contains(c.MovieId));
}
if (roleType > 0)
{
var existingExpression = castExpression;
castExpression = c => existingExpression.Invoke(c) && c.RoleId == roleType;
}
IQueryable<Name> result = _context.Name.AsExpandable().Where(n => n.CastInfo.Any(castExpression.Compile()));
return await result.OrderBy(n => n.Name1).Take(25).ToListAsync();
So I assume I just miss something about builder.
Update about versions: I use dotnet core 2.0 and LinqKit.Microsoft.EntityFrameworkCore 1.1.10
Looking at the code, one will assume that the type of castCondition variable is Expression<Func<CastInfo, bool>> (as it was in earlier versions of PredicateBuilder).
But if that was the case, then n.CastInfo.Any(castCondition) should not even compile (assuming CastInfo is a collection navigation property, so the compiler will hit Enumerable.Any which expects Func<CastInfo, bool>, not Expression<Func<CastInfo, bool>>). So what's going on here?
In my opinion, this is a good example of C# implicit operator abuse. The PredicateBuilder.New<T> method actually returns a class called ExpressionStarter<T>, which has many methods emulating Expression, but more importantly, has implicit conversion to Expression<Func<T, bool>> and Func<CastInfo, bool>. The later allows that class to be used for top level Enumerable / Queryable methods as replacement of the respective lambda func/expression. However, it also prevents the compile time error when used inside the expression tree as in your case - the complier emits something like n.CastInfo.Any((Func<CastInfo, bool>)castCondition) which of course causes exception at runtime.
The whole idea of LinqKit AsExpandable method is to allow "invoking" expressions via custom Invoke extension method, which then is "expanded" in the expression tree. So back at the beginning, if the variable type was Expression<Func<CastInfo, bool>>, the intended usage is:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.Invoke(c)));
But now this doesn't compile because of the reason explained earlier. So you have to convert it first to Expression<Func<T, bool> outside of the query:
Expression<Func<CastInfo, bool>> castPredicate = castCondition;
and then use
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castPredicate.Invoke(c)));
or
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(castPredicate.Compile()));
To let compiler infer the expression type, I would create a custom extension method like this:
using System;
using System.Linq.Expressions;
namespace LinqKit
{
public static class Extensions
{
public static Expression<Func<T, bool>> ToExpression<T>(this ExpressionStarter<T> expr) => expr;
}
}
and then simply use
var castPredicate = castCondition.ToExpression();
It still has to be done outside of the query, i.e. the following does not work:
_context.Name.AsExpandable().Where(n => n.CastInfo.Any(c => castCondition.ToExpression().Invoke(c)));
It may not be exactly related to the original question, but considering the following model :
public Class Music
{
public int Id { get; set; }
public List<Genre> Genres { get; set; }
}
public Class Genre
{
public int Id { get; set; }
public string Title { get; set; }
}
List<string> genresToFind = new() {"Pop", "Rap", "Classical"};
If you are trying to find all Musics that their genres exist in genresToFind list, here's what you can do:
Create PredicateBuilder expressions chain on Genre model :
var pre = PredicateBuilder.New<Genre>();
foreach (var genre in genresToFind)
{
pre = pre.Or(g => g.Title.Contains(genre));
}
Then execute your query like this :
var result = await _db.Musics.AsExpandable()
.Where(m => m.Genres
.Any(g => pre.ToExpression().Invoke(g)))
.ToListAsync();
ToExpression() is a generic extension method that we've created to convert ExpressionStarter<Genre> type to Expression<Func<Genre, bool>> :
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> ToExpression<T> (this
ExpressionStarter<T> exp) => exp;
}
Also, you'll need LinqKit.Microsoft.EntityFrameworkCore package for efcore.
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");
I have a set of POCOs, all of which implement the following simple interface:
interface IIdObject
{
int Id { get; set; }
}
A subset of these POCOs implement this additional interface:
interface IDeletableObject : IIdObject
{
bool IsDeleted { get; set; }
}
I have a repository hierarchy that looks something like this:
IRepository<T> <: BasicRepository<T> <: ValidatingRepository<T> (where T is IIdObject)
I'm trying to add a FilteringRepository to the hierarchy such that all of the POCOs that implement IDeletableObject have a Where(p => p.IsDeleted == false) filter applied before any other queries take place. My goal is to avoid duplicating the hierarchy solely for IDeletableObjects.
My first attempt looked like this:
public override IQueryable<T> Query()
{
return base.Query().Where(t => ((IDeletableObject)t).IsDeleted == false);
}
This works well with LINQ to Objects, but when I switch to an EF backend I get: "LINQ to Entities only supports casting Entity Data Model primitive types."
I went on to try some fancier parameterized solutions, but they ultimately failed because I couldn't make T covariant in the following case for some reason I don't quite understand:
interface IQueryFilter<out T> // error
{
Expression<Func<T, bool>> GetFilter();
}
I'd be happy to go into more detail on my more complicated solutions if it would help, but I think I'll stop here for now in hope that someone might have an idea for me to try.
Thanks very much in advance!
This is too big for comment, so...
You can create expressions dynamically. I've created helper methods:
public static class ExpressionHelper
{
public static MemberExpression PropertyExpression(this Expression expr,string propertyName)
{
var properties = propertyName.Split('.');
MemberExpression expression = null;
foreach (var property in properties)
{
if (expression == null)
expression = Expression.Property(expr, property);
else
expression = Expression.Property(expression, property);
}
return expression;
}
public static BinaryExpression EqualExpression<T>(this Expression expr, string propertyName, T value)
{
return Expression.Equal(expr.PropertyExpression(propertyName), Expression.Constant(value, typeof(T)));
}
}
Then you can use:
//Checking if T implements IDeletableObject
if (typeof(IDeletableObject).IsAssignableFrom(typeof(T)))
{
//a
var parameter = Expression.Parameter(typeof(T), "a");
//a.IsDeleted == false
var where = parameter.EqualExpression("IsDeleted", false);
//a => a.IsDeleted == false
var condition = Expression.Lambda<Func<T, bool>>(where, parameter);
list = list.Where(condition);
}
EDIT
You can also use Dynamic Linq Library. It uses expressions too, but doesn't force you to think about how it all works, just write simple conditions as string. I don't know how it handles bool values.