2

Some of my entities have IEnabledEntity interface. I want to check in repository if entity implements interface then add some predicate. I have the following code:

public class Repository<T> : IRepository<T> where T : class, IEntity, new()
{
   public IQueryable<T> Get(Expression<Func<T, bool>> predicate, params string[] includes)
     IQueryable<T> query = Context.Set<T>();
     foreach (var include in includes)
     {
        query = query.Include(include);
     }

     query = query.Where(predicate);

     var isEnabledEntity = typeof(IEnabledEntity).IsAssignableFrom(typeof(T));

     if (isEnabledEntity)
     {
        query = query.Where(e => ((IEnabledEntity) e).IsEnabled);
     }

     return query;
}

 public interface IEnabledEntity
 {
    bool IsEnabled { get; set; }
 }

 public class Test : IBaseEntity, IEnabledEntity
 {
    // ...
    public bool IsEnabled { get; set; }
 }

But, I get exception about casting:

Unable to cast the type 'Domain.Test' to type 'Domain.Interfaces.IEnabledEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types.

How to make it work?

user348173
  • 8,818
  • 18
  • 66
  • 102
  • You need to materialize first; then cast: `query.ToList().Where(e => ((IEnabledEntity) e).IsEnabled).AsQueryable();` – mshsayem Jan 19 '16 at 08:49
  • @mshsayem I can't, because I also have pagination after which is located in extension class. – user348173 Jan 19 '16 at 08:52
  • Not sure it will work, but I would go for somethinkg like `query = query.OfType().Where(e => e.IsEnabled).Cast();` Not sure if the last `Cast` is needed or will raise an exception... – Raphaël Althaus Jan 19 '16 at 09:24
  • @RaphaëlAlthaus doesn't work. Now I have `IEnabledEntity` is not a valid metadata type for type filtering operations. Type filtering is only valid on entity types and complex types. – user348173 Jan 19 '16 at 09:35
  • L2E wont allow you to do this (the exception message ensures that). – mshsayem Jan 19 '16 at 10:10
  • @mshsayem it's possible if you bypass the cast – Alexander Derck Jan 19 '16 at 12:18

2 Answers2

2

Linq-to-Entities only knows models which are classes, that's why an expression can't contain an interface type. However clearly it's possible runtime to access the IsEnabled property if T implements it, so if you do the check yourself with IsAssignableFrom() (like you do), it's possible to use the ExpressionVisitor class to bypass the casting:

internal class IgnoreCast : ExpressionVisitor
{
    protected override Expression VisitUnary(UnaryExpression e)
    {
      if(e.NodeType == ExpressionType.Convert && e.Type.IsAssignableFrom(typeof(e.Operand))
         return e.Operand;
      else
         return e;
    }
}

Then you need to create your filter with an extensionmethod which implements the IgnoreCast class:

internal static class LocalExtensions
{
   internal static IgnoreCast ic = new IgnoreCast();

   internal static IQueryable<T> FilterEnabled<T>(this IQueryable<T> query) where T: class
   {
      Expression<Func<T,bool>> expr = e => ((IEnabledEntity)e).IsEnabled;
      expr = (Expression<Func<T,bool>>)ic.Visit(e);
      return query.Where(expr);
   }
}

Then you can just use that method in your program:

if(typeof(IEnabledEntity).IsAssignableFrom(T))
   query = query.FilterEnabled();

The base method Visit(Expression e) will pass each node of the expression to a more specialized Visit method for that kind of node. The Convert nodetype is a UnaryExpression so this method will be overriden in the derived class. If the unaryexpression is of the Convert nodetype and the operand implements the type it will just return the operand, thus removing the casting.

Alexander Derck
  • 13,818
  • 5
  • 54
  • 76
2

The type parameter in IQueryable<T> is covariant, so instead of worrying about casting the entity in your expression, just safe-cast the entire query itself and then use Cast<T>() to get it back to your entity type:

    public IQueryable<T> Get(Expression<Func<T, bool>> predicate, params string[] includes)
    {
        IQueryable<T> query = Context.Set<T>();
        foreach (var include in includes)
        {
            query = query.Include(include);
        }

        query = query.Where(predicate);

        var enabledQuery = query as IQueryable<IEnabledEntity>;

        if (enabledQuery != null)
            query = enabledQuery.Where(e => e.IsEnabled).Cast<T>();

        return query;
    }
Tobias J
  • 19,813
  • 8
  • 81
  • 66