检查实体是否实现接口并在泛型存储库中添加谓词

本文关键字:存储 添加 谓词 泛型 是否 实体 实现 接口 检查 | 更新日期: 2023-09-27 18:31:06

我的一些实体具有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只知道作为类的模型,这就是为什么表达式不能包含接口类型的原因。但是,很明显,如果T实现了 IsEnabled 属性,则运行时可以访问它,因此如果您自己使用 IsAssignableFrom() 进行检查(就像您所做的那样),则可以使用 ExpressionVisitor 类绕过强制转换:

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 类的扩展方法创建过滤器:

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因此此方法将在派生类中重写。如果一元表达式属于 Convert 节点类型,并且操作数实现该类型,则它只会返回操作数,从而删除强制转换。

IQueryable<T> 中的 type 参数是协变的,因此无需担心在表达式中强制转换实体,只需安全强制转换整个查询本身,然后使用 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;
    }