LINQ to Entities 无法识别该方法(在相关实体上)
Posted
技术标签:
【中文标题】LINQ to Entities 无法识别该方法(在相关实体上)【英文标题】:LINQ to Entities does not recognize the method (on a related entity) 【发布时间】:2016-09-23 05:56:23 【问题描述】:我正在尝试使一些代码更具可读性,但并没有完全掌握如何构建扩展方法和/或表达式来做到这一点。我们目前有许多实体上都有RecordStatusTypeId
(从IRecordStatus
的接口实现)
public interface IRecordStatus
int RecordStatusTypeId get; set;
RecordStatusType RecordStatusType get; set;
这里的目标是将.Where(RecordStatusTypeId != (int)RecordStatusTypes.Deleted)
之类的语句替换为.ActiveRecords()
之类的扩展方法
我可以使用以下扩展方法来完成此操作:
public static IQueryable<T> ActiveRecords<T>(this DbSet<T> entitySet)
where T : class, IRecordStatus
return entitySet.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted);
*我有DbSet<T>
、IQueryable<T>
、ICollection<T>
和IEnumerable<T>
的扩展方法
这对于MyDbContext.Entities.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted)
之类的语句非常有效,但如果我尝试替换以下内容,则会收到错误“LINQ to Entities 无法识别该方法”:
MyDbContext.Entities.Where(e => e.RelatedEntity.Where(re => re.RecordStatusTypeId != (int)RecordStatusTypes.Deleted));
我想做的事:
MyDbContext.Entities.Where(e => e.RelatedEntity.ActiveRecords().Any());
如何更改我的扩展方法(或添加表达式),以便过滤 DbSet
以及 linq 子句内的相关实体上的活动记录?
【问题讨论】:
Entity Framework Plus 可以做到这一点:github.com/zzzprojects/EntityFramework-Plus @CyrilIselin,谢谢,我去看看。 您的方法无法转换为 T-SQL,Linq to Entities 无法识别。将AsEnumerable
添加到您的Entities
并重试:MyDbContext.Entities.AsEnumerable().Where...
。
AsEnumerable 会将所有实体从数据库存储中拉到内存中。对于任何大型商店,您都不想这样做。
阅读此***.com/questions/18813671/…
【参考方案1】:
看来您的转换可能有误。 应该:
MyDbContext.Entities.Where(e => e.RelatedEntity.Where(re => re.RecordStatusTypeId != (int)RecordStatusTypes.Deleted));
转换成这个:
MyDbContext.Entities.Where(e => e.RelatedEntity.ActiveRecords().Any());
【讨论】:
是的,你是对的。不幸的是,这只是我试图简化帖子的一个错误。我已经解决了这个问题。【参考方案2】:您收到异常是因为 Queryable.Where
需要一个可以翻译成 SQL 的表达式,而 ActiveRecords
不能翻译成 SQL。
您需要做的是更新表达式以将对ActiveRecords
的调用扩展为对.Where(e => e.RecordStatusTypeId != (int)RecordStatusTypes.Deleted
的调用。
我将为可行的解决方案提供草稿。它对您提供的示例非常具体。您可能应该努力使其具有通用性。
以下表达式访问者基本上会将对 ActiveRecords
的调用更改为对 Where
的调用,并使用适当的表达式作为参数:
public class MyVisitor : ExpressionVisitor
protected override Expression VisitMethodCall(MethodCallExpression m)
if (m.Method.Name == "ActiveRecords")
var entityType = m.Method.GetGenericArguments()[0];
var whereMethod = genericWhereMethod.MakeGenericMethod(entityType);
var param = Expression.Parameter(entityType);
var expressionToPassToWhere =
Expression.NotEqual(
Expression.Property(param, "RecordStatusTypeId"),
Expression.Constant((int)RecordStatusTypes.Deleted));
Expression newExpression =
Expression.Call(
whereMethod,
m.Arguments[0],
Expression.Lambda(
typeof(Func<,>).MakeGenericType(entityType, typeof(bool)),
expressionToPassToWhere,
param));
return newExpression;
return base.VisitMethodCall(m);
//This is reference to the open version of `Enumerable.Where`
private static MethodInfo genericWhereMethod;
static MyVisitor()
genericWhereMethod = typeof (Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(x => x.Name == "Where" && x.GetGenericArguments().Length == 1)
.Select(x => new Method = x, Parameters = x.GetParameters())
.Where(x => x.Parameters.Length == 2 &&
x.Parameters[0].ParameterType.IsGenericType &&
x.Parameters[0].ParameterType.GetGenericTypeDefinition() == typeof (IEnumerable<>) &&
x.Parameters[1].ParameterType.IsGenericType &&
x.Parameters[1].ParameterType.GetGenericTypeDefinition() == typeof (Func<,>))
.Select(x => x.Method)
.Single();
然后您可以创建一个特殊的WhereSpecial
方法来访问表达式,然后再将其传递给Queryable
的真正Where
方法:
public static class ExtentionMethods
public static IQueryable<T> WhereSpecial<T>(this IQueryable<T> queryable, Expression<Func<T,bool>> expression )
MyVisitor visitor = new MyVisitor();
var newBody = visitor.Visit(expression.Body);
expression = expression.Update(newBody, expression.Parameters);
return queryable.Where(expression);
然后你可以像这样使用它:
var result = MyDbContext.Entities.WhereSpecial(e => e.RelatedEntity.ActiveRecords().Any());
【讨论】:
这一切似乎违背了首先使用辅助 ActiveRecords 方法的全部目的:) @Evk,这可能是真的 :) 也许一个更通用的访问者接受方法名称和相应的 lambda 表达式的字典会让所有这些都值得。 @YacoubMassad,我还没有时间尝试探索这样的事情是否值得努力,但感谢您的回答。当我有时间重访时,我会回来更新。【参考方案3】:尝试将查询的主要部分设为 IQueryable,然后在 where 子句上添加标签...
类似:
IQueryable temp = from x in MyDbContext.Entities
select x;
temp = temp.Where(e => e.RelatedEntity.ActiveRecords().Any());
或评估主要部分,然后执行内存中的位置。
【讨论】:
感谢您的建议,但我已将此接口应用于一组不同的实体对象,因此如果不先过滤 ActiveRecords,我无法撤回查询的内容(至少在所有情况)。以上是关于LINQ to Entities 无法识别该方法(在相关实体上)的主要内容,如果未能解决你的问题,请参考以下文章