检查实体是否实现接口并在通用回购中添加谓词
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;
}
我的一些实体有 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;
}