Specification inside LINQ with EF 4.3 - c#

I stumbled trying to use my specification inside a LINQ query. The trouble here is with my specification with params.
Let's fake a simple scenario:
public class Car {
public Guid Id { get; set; }
public string Color { get; set; }
public int UsedPieces { get; set; }
// whatever properties
}
public class Piece {
public Guid Id { get; set; }
public string Color { get; set; }
// whatever properties
}
public static class PieceSpecifications : ISpecification<Piece> {
public static ISpecification<Piece> WithColor(string color) {
return new Specification<Piece>(p => p.Color == color);
}
}
what I'm actually trying to do
// Get accepts ISpecification and returns IQueryable<Car> to force just one call to database
var carWithPieces = _carRepository.Get(CarSpecifications.UsedPiecesGreaterThan(10));
var piecesWithColor = from p in _pieceRepository.Get()
let car = carWithPieces.FirstOrDefault() // entire query will does one call to database
where PieceSpecifications.WithColor(car.Color).IsSatisfiedBy(p) // unfortunately it isn't possible
// where p.Color == car.Color -> it works, but it's not what I want
select p;
I know it's a little bit confusing, but I'm trying to avoid a lot of roundtrips inside my real(big) scenario and I know that actually it's impossible to do using raw LINQ with entity framework. I'm tired to try so many blogs and failed(mine) approaches.
Someone knows some real good approach. There's another way to do that?
Error
System.NotSupportedException: LINQ to Entities does not recognize the
method 'Boolean IsSatisfiedBy(App.Model.Piece)' method, and this
method cannot be translated into a store expression.
UPDATE
Basic Specification Pattern
public class Specification<T> : ISpecification<T> {
private readonly Expression<Func<T, bool>> _predicate;
public Specification(Expression<Func<T, bool>> predicate) {
_predicate = predicate;
}
public Expression<Func<T, bool>> Predicate {
get { return _predicate; }
}
public bool IsSatisfiedBy(T entity) {
return _predicate.Compile().Invoke(entity);
}
}
UPDATE
It's pretty easy neat if I do this
// call to database
var car = _carRepository
.Get(CarSpecifications.UsedPiecesGreaterThan(10))
.FirstOrDefault();
// Whoah! look I'm working, but calling to database again.
var piecesWithColor = _pieceRepository
.Get(PieceSpecifications.WithColor(car.Color))
.ToArray();
Repository
// The Get function inside repository accepts ISpecification<T>.
public IQueryable<T> Get(ISpecification<T> specification) {
return Set.Where(specification.Predicate);
}

You can't compile and invoke expression if you want to use it in LINQ-to-entities query. Try to use Predicate directly because LINQ-to-entities builds expression tree which is evaluated by EF LINQ provider and translated to SQL.
IMHO using specification this way doesn't make sense. LINQ-to-entities query is a composite specification. So either use Linq-to-entities or build your own query language using specification and let your repository translate your query to LINQ query.

Take a look at using AsExpandable extension method.
http://www.albahari.com/nutshell/linqkit.aspx

Maybe make IsSatisfiedBy() and extension method to IQueryable. Here is K. Scott Allen's approach:
http://odetocode.com/Blogs/scott/archive/2012/03/19/avoiding-notsupportedexception-with-iqueryable.aspx

Related

Nested expression building with linq and Entity Framework

I'm trying to make a service that returns a catalog based on the filters.
I've seen a few results on the internet, but not quite my issue. I hope you can help me with mine.
The issue is that this query build cannot be translated into a store expression:
'LINQ to Entities does not recognize the method 'System.Linq.IQueryable'1[App.Data.Models.Subgroup] HasProductsWithState[Subgroup](System.Linq.IQueryable'1[App.Data.Models.Subgroup], System.Nullable`1[System.Boolean])' method, and this method cannot be translated into a store expression.'
How can I make it so the query can be translated into a store expression.
Please don't suggest .ToList() as a answer as I don't want this to run in memory.
So what I have is:
bool? isActive = null;
string search = null;
DbSet<Maingroup> query = context.Set<Maingroup>();
var result = query.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(x => x.SortOrder)
.Select(x => new CatalogViewModel.MaingroupViewModel()
{
Maingroup = x,
Subgroups = x.Subgroups.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(y => y.SortOrder)
.Select(y => new CatalogViewModel.SubgroupViewModel()
{
Subgroup = y,
Products = y.Products.AsQueryable()
.HasProductsWithState(isActive)
.HasChildrenWithName(search)
.OrderBy(z => z.SortOrder)
.Select(z => new CatalogViewModel.ProductViewModel()
{
Product = z
})
})
});
return new CatalogViewModel() { Maingroups = await result.ToListAsync() };
In the code below you can see that I recursively call the extension to try and stack the expression. But when I walk through my code at runtime it does not enter the function again when
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
is called.
public static class ProductServiceExtensions
{
public static IQueryable<TEntity> HasProductsWithState<TEntity>(this IQueryable<TEntity> source, bool? state)
{
if (source is IQueryable<Maingroup> maingroups)
{
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Subgroup> subgroups)
{
return subgroups.Where(x => x.Products.AsQueryable().HasProductsWithState(state).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Product> products)
{
return products.Where(x => x.IsActive == state) as IQueryable<TEntity>;
}
return source;
}
public static IQueryable<TEntity> HasChildrenWithName<TEntity>(this IQueryable<TEntity> source, string search)
{
if (source is IQueryable<Maingroup> maingroups)
{
return maingroups.Where(x => search == null || x.Name.ToLower().Contains(search) || x.Subgroups.AsQueryable().HasChildrenWithName(search).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Subgroup> subgroups)
{
return subgroups.Where(x => search == null || x.Name.ToLower().Contains(search) || x.Products.AsQueryable().HasChildrenWithName(search).Any()) as IQueryable<TEntity>;
}
else if (source is IQueryable<Product> products)
{
return products.Where(x => search == null || x.Name.ToLower().Contains(search)) as IQueryable<TEntity>;
}
return source;
}
}
UPDATE
Missing classes:
public class Maingroup
{
public long Id { get; set; }
public string Name { get; set; }
...
public virtual ICollection<Subgroup> Subgroups { get; set; }
}
public class Subgroup
{
public long Id { get; set; }
public string Name { get; set; }
public long MaingroupId { get; set; }
public virtual Maingroup Maingroup { get; set; }
...
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public long SubgroupId { get; set; }
public virtual Subgroup Subgroup { get; set; }
...
public bool IsActive { get; set; }
}
The cause of your problem
You have to be aware between an IEnumerable and an IQueryable. An IEnumerable object has everything in it to enumerate over all the elements: you can ask for the first element of the sequence, and once you've got an element, you can ask for the next element, until there are no more elements.
An IQueryable seems similar, however, the IQueryable does not hold everything to enumerate the sequence. It holds an Expression and a Provider. The Expression is a generic form of what must be queried. The Provider knows who must execute the query (usually a database management system), how to communicate with this executor and which language to use (usually something SQL-like).
As soon as you start enumerating, either explicitly by calling GetEnumerator and MoveNext, or implicitly by calling foreach, ToList, FirstOrDefault, Count, etc, the Expression is sent to the Provider, who will translate it into SQL and call the DBMS. The returned data is presented as an IEnumerable object, which is enumerated, using GetEnumerator
Because the Provider has to translate the Expression into SQL, the Expression may only call functions that can be translated into SQL. Alas, the Provider does not know HasProductsWithState, nor any of your own defined functions, and thus can't translate it into SQL. In fact, the entity framework provider also does not know how to translate several standard LINQ functions, and thus they can't be used AsQueryable. See Supported and Unsupported LINQ methods.
So you'll have to stick to functions that return an IQueryable where the Expression contains only supported functions.
Class Description
Alas you forgot to give us your entity classes, so I'll have to make some assumptions about them.
Apparently have a DbContext with at least three DbSets: MainGroups, SubGroups and Products.
There seems to be a one-to-many (or possible many-to-many) relation between MaingGroups and SubGroups: every MainGroup has zero or more SubGroups.
It seems that there is also a one-to-many relation between SubGroups and Products: every SubGroup has zero or more Products.
Alas you forgot to mentions that return relation: does every Product belong to exactly one SubGroup (one-to-many), or does every Product belong to zero or more SubGroups (many-to-many`)?
If you've followed the entity framework code first conventions, you will have classes similar to this:
class MainGroup
{
public int Id {get; set;}
...
// every MainGroup has zero or more SubGroups (one-to-many or many-to-many)
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
class SubGroup
{
public int Id {get; set;}
...
// every SubGroup has zero or more Product(one-to-many or many-to-many)
public virtual ICollection<Product> Products{get; set;}
// alas I don't know the return relation
// one-to-many: every SubGroup belongs to exactly one MainGroup using foreign key
public int MainGroupId {get; set;}
public virtual MainGroup MainGroup {get; set;}
// or every SubGroup has zero or more MainGroups:
public virtual ICollection<MainGroup> MainGroups {get; set;}
}
Something similar for Product:
class Product
{
public int Id {get; set;}
public bool? IsActive {get; set;} // might be a non-nullable property
...
// alas I don't know the return relation
// one-to-many: every Productbelongs to exactly one SubGroup using foreign key
public int SubGroupId {get; set;}
public virtual SubGroup SubGroup {get; set;}
// or every Product has zero or more SubGroups:
public virtual ICollection<SubGroup> SubGroups {get; set;}
}
And of cours your DbContext:
class MyDbContext : DbContext
{
public DbSet<MainGroup> MainGroups {get; set;}
public DbSet<SubGroup> SubGroups {get; set;}
public DbSet<Product> Products {get; set;}
}
This is all that entity framework needs to know to detect your tables, the columns in your tables and the relations between the tables (one-to-many, many-to-many, one-to-zero-or-one). Only if you want to deviate from the standard naming you'll need attributes of fluent API.
In entity framework the columns of the tables are represented by non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many).
Note that although the SubGroups of a MainGroup is declared as a collection, if you query the SubGroups of the MaingGroup with Id 10 you'll still get an IQueryable.
Requirements
Given a queryable sequence of Products and a nullable Boolean State, HasProductsWithState(products, state) should return the queryable sequence of Products that have a value of IsActive equal to State
Given a queryable sequence of SubGroups and a nullable Boolean State, HasProductsWithState(subGroups, state) should return the queryable sequence of SubGroups that have at least one Product that "HasProductsWithState(Product, State)1
Given a queryable sequence of MainGroups and a nullable Boolean State, HasProductsWithState(mainGroups, state) should return the queryable sequence of MainGroups, that contains all MainGroups that have at least one SubGroup that HasProductsWithState(SubGroup, State)
Solution
Well If you write the requirements like this, the extension methods are easy:
IQueryable<Product> WhereHasState(this IQueryable<Product> products, bool? state)
{
return products.Where(product => product.IsActive == state);
}
Because this function does not check whether a Product has this state, but returns all Product that have this state, I chose to use a different name.
bool HasAnyWithState(this IQueryable<Product> products, bool? state)
{
return products.WhereHasState(state).Any();
}
Your code will be slightly different if IsActive is a non-nullable property.
I'll do something similar with SubGroups:
IQueryable<SubGroup> WhereAnyProductHasState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subgroups.Where(subGroup => subGroup.Products.HasAnyWithState(state));
}
bool HasProductsWithState(this IQueryable<SubGroup> subGroups, bool? state)
{
return subGroups.WhereAnyProductHasState(state).Any();
}
Well, you'll know the drill by now for MainGroups:
IQueryable<MainGroup> WhereAnyProductHasState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return maingroups.Where(mainGroup => mainGroup.SubGroups.HasProductsWithState(state));
}
bool HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups.WhereAnyProductHasState(state).Any();
}
If you look really closely, you'll see that I didn't use any self-defined function. My function calls will only change the Expression. The changed Expression can be translated into SQL.
I've separated the function into a lot of smaller functions, because you didn't say whether you want to use HasProductsWithState(this IQueryable<SubGroup>, bool?) and HasProductsWithState(this IQueryable<Product>, bool?).
TODO: do something similar for similar for HasChildrenWithName: separate into smaller functions that contain only LINQ functions, and nothing else
If you'll only call HasProductsWithState(this IQueryable<MainGroup>, bool?) you can do it in one function, using `SelectMany:
IQueryable<MainGroup> HasProductsWithState(this IQueryable<MainGroup> mainGroups, bool? state)
{
return mainGroups
.Where(mainGroup => mainGroup.SelectMany(mainGroup.SubGroups)
.SelectMany(subGroup => subGroup.Products)
.Where(product => product.IsActive == state)
.Any() );
}
But when I walk through my code at runtime it does not enter the function again when
return maingroups.Where(x => x.Subgroups.AsQueryable().HasProductsWithState(state)
Welcome to the world of expression trees!
x => x.Subgroups.AsQueryable().HasProductsWithState(state)
is lambda expression (Expression<Func<...>) with body
x.Subgroups.AsQueryable().HasProductsWithState(state)
The body is expression tree, in other words - code as data, hence is never executed (except if compiled to delegate as in LINQ to Objects).
It's easily overlooked since visually lambda expressions look like delegates. Even Harald in their answer after all explanations that one should not use custom methods, as a solution actually provides several custom methods with the rationale "I didn't use any self-defined function. My function calls will only change the Expression. The changed Expression can be translated into SQL". Sure, but if your functions are called! Which of course does not happen when they are inside expression tree.
With that being said, there is no good general solution. What I can offer is solution for your particular problem - transforming custom methods which receive IQueryable<T> plus other simple parameters and return IQueryable<T>.
The idea is to use custom ExpressionVisitor which identifies the "calls" to such method inside expression tree, actually calls them and replaces them with the result of the call.
The problem is to call
x.Subgroups.AsQueryable().HasProductsWithState(state)
when we have no actual x object. The trick is to call them with fake queryable expression (like LINQ to Objects Enumerable<T>.Empty().AsQueryble()) and then use another expression visitor to replace the fake expression with the original expression in the result (pretty much like string.Replace, but for expressions).
Here is the sample implementation of the above:
public static class QueryTransformExtensions
{
public static IQueryable<T> TransformFilters<T>(this IQueryable<T> source)
{
var expression = new TranformVisitor().Visit(source.Expression);
if (expression == source.Expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class TranformVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name.StartsWith("Has")
&& node.Type.IsGenericType && node.Type.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& node.Arguments.Count > 0 && node.Arguments.First().Type == node.Type)
{
var source = Visit(node.Arguments.First());
var elementType = source.Type.GetGenericArguments()[0];
var fakeQuery = EmptyQuery(elementType);
var args = node.Arguments
.Select((arg, i) => i == 0 ? fakeQuery : Evaluate(Visit(arg)))
.ToArray();
var result = (IQueryable)node.Method.Invoke(null, args);
var transformed = result.Expression.Replace(fakeQuery.Expression, source);
return Visit(transformed); // Apply recursively
}
return base.VisitMethodCall(node);
}
static IQueryable EmptyQuery(Type elementType) =>
Array.CreateInstance(elementType, 0).AsQueryable();
static object Evaluate(Expression source)
{
if (source is ConstantExpression constant)
return constant.Value;
if (source is MemberExpression member)
{
var instance = member.Expression != null ? Evaluate(member.Expression) : null;
if (member.Member is FieldInfo field)
return field.GetValue(instance);
if (member.Member is PropertyInfo property)
return property.GetValue(instance);
}
throw new NotSupportedException();
}
}
static Expression Replace(this Expression source, Expression from, Expression to) =>
new ReplaceVisitor { From = from, To = to }.Visit(source);
class ReplaceVisitor : ExpressionVisitor
{
public Expression From;
public Expression To;
public override Expression Visit(Expression node) =>
node == From ? To : base.Visit(node);
}
}
Now all you need is to call .TransformFilters() extension methods at the end of your queries, for instance in your sample
var result = query.AsQueryable()
// ...
.TransformFilters();
You can also call it on intermediate queries. Just make sure the call is outside expression tree :)
Note that the sample implementation is processing static methods having first parameter IQueryable<T>, returning IQueryable<T> and name starting with Has. The last is to skip Queryable and EF extension methods. In the real code you should use some better criteria - for instance the type of the defining class, or custom attribute etc.

Using a Query Object In A C# Expression to Return A different type

I currently have a data object for the sake of argument I'll call it foo...
public class Foo
{
public int IndexedKey { get; set;}
public string NonIndexedData {get; set;}
}
I have a generic repository that I want to query this object with, however due to permissions to the database I'm not allowed to do a full table scan. I've therefore been tasked with creating safe query objects.
public class FooQuery
{
public int IndexedKey
}
The generic repository allows an arbitrary predicate and it currently has an implementaation similar to the following...
public class FooRepo : IGenericRepo<Foo>
{
private ICollection<Foo> _allFooRecords; //Imagine this is populated
public ICollection<Foo> GetWhere(Expression<Func<Foo, bool>> criteria)
{
return _allFooRecords.Where(criteria.Compile())
}
}
I want to be able to do the following...
public class FooRepo : IGenericRepo<Foo, FooQuery>
{
private ICollection<Foo> _allFooRecords; //Imagine this is populated
public ICollection<Foo> GetWhere(Expression<Func<FooQuery, bool>> criteria)
{
return _allFooRecords.Where(criteria.Compile())
}
}
The above won't compile. I know that FooQuery's properties definitely contain the right fields that match what is the indexed property of the Foo class, but I can no longer user the criteria.Compile because it will return a function that is incompatible with searching a Foo collection. Is there a way to make this work with the above signature, and what would I need to change about my implementation to get this to work correctly.
Many Thanks
your requirement is: Automatically translate view model expression Expression<Func<FooQuery, bool>> to real model expression Expression<Func<Foo, bool>>. FooQuery is actually your ViewModel, but Foo is real Model.
Automapper could do the magic.
//wrap the translate in base class, so you don't have to do it in each repo
public class BaseRepo<TEntity, TViewMode> : IGenericRepo<TEntity, TViewMode>
{
....
public IWhatever<TEntity> Where(Expression<Func<TViewMode, bool>> vmExpression)
{
var expression = Mapper.Map<Expression<Func<TEntity, bool>>>(vmExpression);
return whateverDataOrQuery.Where(expression);
}
}
Full official document is here:
Expression Translation: https://github.com/AutoMapper/AutoMapper/blob/master/docs/Expression-Translation-(UseAsDataSource).md

LINQ to Entities and polymorphism

Let's say I have a simple model:
public class Movie
{
public int ID { get; set; }
public string Name { get; set; }
}
And a DbContext:
public class MoviesContext : DbContext
{
...
public DbSet<Movie> Movies { get; set; }
}
Also I have a method in MoviesContext class that filters Movies by substring like this:
return Movies.Where(m => m.Name.Contains(filterString)).Select(m => m);
Now suppose I'd like to add a new model, say:
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string FullName { get { return FirstName + (MiddleName?.Length > 0 ? $" {MiddleName}" : "") + $" {LastName}"; } }
}
I also want to filter persons (DbSet Persons) by name (i.e. FullName). I'd like DRY, so it is preferrable to generalize a filter method of MoviesContext. And, what is important, I'd like to do filtering on the database level. So I have to deal with LINQ for Entities.
If not for this, the task is pretty simple. I could use an abstract class and add a virtual method that do the "contains substring" logic. Alternatively, I could use an interface. Unfortunately, because of LINQ for Entities, I can't use a FullName property (which is not convenient but bearable) and I can't write something like this:
return dbset.Where(ent => ent.NameContains(filterString)).Select(ent => ent);
So, how to solve this problem? I've found some solution (almost have my head broken), but I am not very happy with it. I'll post my solution separately, but I hope there is a more elegant one.
Reading your code a little more closely, instead of your NameFilterable abstract class, could you not do something like this:
public interface IHasPredicateGetter<T> {
[NotNull] Expression<Func<T, bool>> GetPredicateFromString([NotNull] string pValue);
}
public class Movie : IHasPredicateGetter<Movie> {
public int ID { get; set; }
public string Name { get; set; }
public Expression<Func<Movie, bool>> GetPredicateFromString(string pValue) {
return m => m.Name.Contains(pValue);
}
}
This prevents you from needing a cast, for example. It's so hard to grasp just what you're trying to do here, so I'm not sure this is the whole thing or not. You're still stuck with an instance method that should probably be a static method, but it couldn't implement an interface otherwise.
My solution looks like this.
[1] The base class:
public abstract class NameFilterable
{
protected static Expression<Func<T, bool>> False<T>() { return f => false; }
public virtual Expression<Func<T, bool>> GetNameContainsPredicate<T>(string filterString)
{
return False<T>();
}
}
[2] The Person class (I'll omit the Movie class, it is more simple):
public class Person : NameFilterable
{
...
public override Expression<Func<T, bool>> GetNameContainsPredicate<T>(string filterString)
{
return entity =>
String.IsNullOrEmpty(filterString) ||
(entity as Person).LastName.Contains(filterString) ||
(entity as Person).FirstName.Contains(filterString) ||
(((entity as Person).MiddleName != null) && (entity as Person).MiddleName.Contains(filterString))
;
}
}
[3] The filter methods in MoviesContext:
private static IQueryable<T> _filterDbSet<T>(DbSet<T> set, Expression<Func<T, bool>> filterPredicate) where T : class
{
return set
.Where(filterPredicate)
.Select(ent => ent);
}
private static IQueryable<T> _filterDbSet<T>(DbSet<T> set, string search = null) where T : NameFilterable, new()
{
T ent = new T();
return _filterDbSet<T>(set, (ent as NameFilterable).GetNameContainsPredicate<T>(search));
}
public static ICollection<T> Filter<T>(DbSet<T> set, string search = null) where T : NameFilterable, new()
{
return _filterDbSet(set, search).ToList();
}
And it seems that all this works pretty well. But I can't say it is very elegant.
[1] I have to use a generic T, though on the Person level I always work with Person objects (or descendants). So I have to convert T to Person (as Person).
[2] In GetNameContainsPredicate method, I can't write (because of LINQ for Entities):
return entity =>
{
Person p = entity as Person;
String.IsNullOrEmpty(filterString) ||
p.LastName.Contains(filterString) ||
p.FirstName.Contains(filterString) ||
((p.MiddleName != null) && p.MiddleName.Contains(filterString))
};
[3] I can't use static methods (statics couldn't be overridden), so I have to create a dummy T object (T ent = new T();).
[4] I still can't use a FullName.Contains(filterString)
So, the question remains: Maybe I miss something and there is a more elegant solution to the problem?
You could create a method that is responsible for search if a type has a particular property and filter for that property,if the object has not the property simply return null. Whith this you can create an expression that filters for this property
//gets the property info of the property with the giving name
public static PropertyInfo GetPropetyInfo<T>(string name)
{
var type = typeof(T);
var property = type.GetProperty(name);
return property;
}
//Creates an expression thats represents the query
public static Func<T, bool> GetFilterExpression<T>( string propertyName, object propertyValue)
{
var prop = GetPropetyInfo<T>(propertyName);
if(prop==null)return t=>false;
var parameter = Expression.Parameter(typeof(T), "t");
Expression expression = parameter;
var left = Expression.Property(expression, prop);
if (prop.PropertyType == typeof(string))
{
var toLower = typeof(string).GetMethods().FirstOrDefault(t => t.Name.Equals("ToLower"));
var tlCall = Expression.Call(left, toLower);
var right = Expression.Constant(propertyValue.ToString().ToLower());
var contains = Expression.Call(tlCall, typeof(string).GetMethod("Contains"), right);
var containsCall = Expression.IsTrue(contains);
expression = Expression.AndAlso(Expression.NotEqual(left, Expression.Constant(null)), containsCall);
}
else
{
if (prop.PropertyType.ToString().ToLower().Contains("nullable"))
{
var getValue = prop.PropertyType.GetMethods().FirstOrDefault(t => t.Name.Equals("GetValueOrDefault"));
var getValueCall = Expression.Call(left, getValue);
var right = Expression.Constant(propertyValue);
expression = Expression.Equal(getValueCall, right);
}
else
{
var value = Convert.ChangeType(propertyValue,prop.PropertyType);
var right = Expression.Constant(value);
expression = Expression.Equal(left, right);
}
}
return Expression.Lambda<Func<T, bool>>(expression, new ParameterExpression[] { parameter }).Compile();
}
The you can use it as follow
var expression = YOURCLASS.GetFilterExpression<Person>("LastName", "Jhon");
var result=dbset.Where(expression);
There are a few things I've done to get polymorphism with EF, but in your specific case of wanting reusable filters, I'm not sure it's worth the trouble. I've basically tried to do the same exact thing, but every time I end up realizing that there's no point. Ask yourself: what exactly are the benefits of doing this, and how is it any more flexible than what a Where clause already offers?
There are two issues. One is that it's hard or nigh impossible to get a filter to be used between two separate classes by using a shared interface (INamedObject for example). This is because you need a strongly typed expression. You can make a function that returns a strongly typed expression, but why would you not have just wrote the expression in the first place? The other issue is that you need a new filter expression for every search value, which is pretty close to where we are already.
If you perfected this, what would you have? The ability to infer type, specify a search value, and get an expression you could use? Isn't that essentially what we already have? The way Where clauses already are, they already have strong typing, and the ability to use dynamic search values. While it might feel a tiny bit redundant to say x => x.Name == value in more than one place, really the ability to specify such a concise and powerful filter statement is already a pretty amazing place to be.

Using an extension method on a base class in a LINQ query

Apologies in advance for my naivety.
I am using Entity Framework to persist entities I have defined in my domain model. My domain model entities all inherit from my EntityBase class. This has properties I wish to be common to all my entities:
public class EntityBase
{
public string CreatedBy { get; set; }
public DateTime? Created { get; set; }
public int ModifiedBy { get; set; }
public DateTime? Modified { get; set; }
public bool Enabled { get; set; }
public bool Deleted { get; set; }
}
Now when I want to query EF using LINQ it would be nice if I didn't have to include elements to check if a particular entity is Enabled or Deleted. Every query would involve code, for example:
var messages = _db.Memberships.Where(m => m.UserId.Equals(userId))
.SelectMany(m => m.Group.Messages)
.Include(m => m.Group.Category)
.Select(m => m.Enabled && !m.Deleted)
.ToList();
Rather than doing this each time, I thought I would write an extension method which would act on IQueryable
public static IQueryable<EntityBase> Active(this IQueryable<EntityBase> entityCollection)
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}
In my naivety I then thought I could just include this in any LINQ query which returns my entities which inherit from the EntityBase class - like so:
var messages = _db.Memberships.Where(m => m.UserId.Equals(userId))
.SelectMany(m => m.Group.Messages)
.Include(m => m.Group.Category)
.Active() <============================= Extension Methd
.ToList();
return Mapper.Map<List<Message>,List<MessageDto>>(messages);
However, the compiler now complains that:
Error 2 Argument 1: cannot convert from
'System.Collections.Generic.List<Diffusr.Business.Entities.EntityBase>' to
'System.Collections.Generic.List<Diffusr.Business.Entities.Message>'
Question : Can I achieve what I want to achieve, i.e. a common method for all my entities to return only Enabled and not Deleted? If so, how?
Instead of specifying a concrete class, use generics, as most extension methods do:
public static IQueryable<T> Active<T>(this IQueryable<T> entityCollection) where T:EntityBase
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}
I assume you are using some version of .NET earlier than 4.0. Generic covariance wasn't allowed before 4.0 (ie passing an enumerable of a child type when an enumerable of the base type was expected).
Even after 4.0, it's not the absolute best idea to use covariance as the compiler ends up doing a lot of extra checks to do to ensure type safety whenever you try to store some new value to the List. Jon Skeet has a nice article about this
You can by changing the extension method:
public static IQueryable<T> Active(this IQueryable<T> entityCollection)
where T : EntityBase
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}

How to code this generic type C# snippet?

Simplified repeated code for each entity type is
public IList<entity1> GetEntity1(.. query params ..)
{
IQueryable<entity1> query = context.entity1;
query = from refDataType in query
where refDataType.Id > 0
select refDataType;
.
. plus more changes to query same for each entity
.
return query.ToList();
}
I wanted to create a generic function that creates the query, but not sure how to go about it?
ie in the following snippet, How do I code for ReturnAGenericQuery?
public IList<entity1> GetEntity1(.. query params ..)
{
IQueryable<entity1> query = context.entity1;
query = ReturnAGenericQuery of type entity1
return query.ToList();
}
public IList<entity2> GetEntity2(.. query params ..)
{
IQueryable<entity2> query = context.entity2;
query = ReturnAGenericQuery of type entity2
return query.ToList();
}
private IQueryable<T> ReturnAGenericQuery<T> ()
{
return IQueryable of entity1 or entity2
}
Your example is a little vague, but it looks like you need something along the lines of:
private IQueryable<T> ReturnAGenericQuery<T>(IQueryable<T> source)
where T : SomeBaseTypeForAllYourEntities
{
IQueryable<T> result =
from refDataType in source
where refDataType.Id > 0
select refDataType;
// Other stuff here
return result;
}
public IList<Entity1> GetEntity1( ... )
{
return ReturnAGenericQuery(context.entity1).ToList();
}
The reason you need the 'where T :' clause is because T needs to be a type that has a property 'Id' for your LINQ where-clause to work ... so you'd need to derive Entity1 and Entity2 from a base class that defines that property. If you need any other properties for the 'other stuff' these will need to be added to the base class too.
Addendum:
If context.entity1 (whatever collection that refers to) is not an IQueryable<entity1>, then you may need to use context.entity1.AsQueryable() instead.
Originally my query was wrong, it was supposed to query from refDataType in source rather than in result... duh.
Provided you have the right kind of inheritance structure (see below), this compiles fine.
public class SomeBaseTypeForAllYourEntities
{
public int Id { get; set; }
}
sealed public class Entity1 : SomeBaseTypeForAllYourEntities
{
... other properties, etc. ...
}
You need an interface.
private IQueryable<T, R> ReturnAGenericQuery<T> (T entity) where T : IQueryable, IHasRefDataType
{
return from DataType refDataType in entity
where refDataType.Id > 0
select refDataType;
}
struct DataType
{
public int Id;
}
public interface IHasRefDataType
{
DataType refDataType;
}

Categories