检查实体是否实现接口并在通用回购中添加谓词

Check if entity implements interface and add predicate in generic repo

我的一些实体有 IEnabledEntity 界面。 如果实体实现接口然后添加一些谓词,我想检查存储库。我有以下代码:

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; }
 }

但是,我得到关于转换的异常:

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

如何让它发挥作用?

Linq-to-Entities 只知道 classes 的模型,这就是表达式不能包含接口类型的原因。但是很明显,如果 T 实现它,则可以在运行时访问 IsEnabled 属性,因此如果您使用 IsAssignableFrom() 自己进行检查(就像您所做的那样),则可以使用ExpressionVisitor class 绕过转换:

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;
    }
}

然后您需要使用实现 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);
   }
}

然后你就可以在你的程序中使用那个方法了:

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

基本方法 Visit(Expression e) 会将表达式的每个节点传递给该类型节点的更专门的 Visit 方法。 Convert 节点类型是 UnaryExpression,因此该方法将在派生的 class 中被覆盖。如果 unaryexpression 是 Convert 节点类型并且操作数实现了该类型,它将只是 return 操作数,从而消除了强制转换。

IQueryable<T> 中的类型参数是协变的,因此不必担心在表达式中强制转换实体,只需安全强制转换整个查询本身,然后使用 Cast<T>() 将其返回到您的实体类型:

    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;
    }