检查实体是否实现接口并在泛型存储库中添加谓词
本文关键字:存储 添加 谓词 泛型 是否 实体 实现 接口 检查 | 更新日期: 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;
}