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 && UnitsI开发者_运维知识库nStock > 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!
精彩评论