Search code examples
entity-frameworkentity-framework-4notsupportedexception

Entity Framework Common Where


I have two entities with common properties. I need to apply some where in my query using the common properties. So I decide to do this:

public interface IContract
{
    string Name{get;set;}
}

public class Entity1 : IContract
{
    public string Name{get;set;}
}

public class Entity2 : IContract
{
    public string Name{get;set;}
}

public class Repository
{
    public IQueryable<T> Filter<T>(IQueryable<T> query, Request request) where T : IContract
    {
        return query.Where(x => x.Name== request.Name);
    }
    public IQueryable<Entity1> GetEntitity1()
    {
        return Filter(entities.Entity1, new Request { Name = "X" };
    }

    public IQueryable<Entity2> GetEntitity2()
    {
        return Filter(entities.Entity2, new Request { Name = "X" };
    }
}

The problem is using this way and after apply .ToList() I receive: NotSupportedException. LINQ to Entities only supports casting EDM primitive or enumeration types.

I there a way to fix it or I need to use the Where(string) of ObjectQuery?

Thanks


Solution

  • The problem is, that your type constraint for T is an interface. The result of the filter method is basically a query like this.

    entities.Entity1.Where(p => ((IContract)p).Name == request.Name);
    

    This ((IContract)p) cast cannot be translated into a sql statement. As this cast is more or less useless in your case it can simply be removed from the query expression. The best way to do this is an ExpressionVisitor.

    public IQueryable<T> Filter<T>(IQueryable<T> query, Request request) where T : IContract
    {
        var result =  query.Where(x => x.Name == request.Name);
        result = RemoveContract<T, IContract>(result);
        return result;
    }
    
    public IQueryable<T> RemoveContract<T, TContract>(IQueryable<T> query) where T : TContract
    {
        var exp = query.Expression;
        exp = new RemoveConvertExpressionVisitor<TContract>().Visit(exp);
        return query.Provider.CreateQuery<T>(exp);
    }
    
    private class RemoveConvertExpressionVisitor<TContract> : ExpressionVisitor{
        public override Expression Visit(Expression node)
        {
            var unary = node as UnaryExpression;
            if (unary != null && unary.Type == typeof(TContract)) {
                return unary.Operand;
            }
            return base.Visit(node);
        }
    }
    

    After the RemoveContract method is called the query looks like this.

    entities.Entity1.Where(p => p.Name == request.Name);