Mih*_*iP. 7 c# linq extension-methods entity-framework entity-framework-6
有人可以向我解释为什么EF引擎在以下情况下失败了吗?
它可以使用以下表达式正常工作:
var data = context.Programs
.Select(d => new MyDataDto
{
ProgramId = d.ProgramId,
ProgramName = d.ProgramName,
ClientId = d.ClientId,
Protocols = d.Protocols.Where(p => p.UserProtocols.Any(u => u.UserId == userId))
.Count(pr => pr.Programs.Any(pg => pg.ProgramId == d.ProgramId))
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
但是,如果我将一些封装到扩展方法中:
public static IQueryable<Protocol> ForUser(this IQueryable<Protocol> protocols, int userId)
{
return protocols.Where(p => p.UserProtocols.Any(u => u.UserId == userId));
}
Run Code Online (Sandbox Code Playgroud)
结果查询:
var data = context.Programs
.Select(d => new MyDataDto
{
ProgramId = d.ProgramId,
ProgramName = d.ProgramName,
ClientId = d.ClientId,
Protocols = d.Protocols.ForUser(userId)
.Count(pr => pr.Programs.Any(pg => pg.ProgramId == d.ProgramId))
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
失败但异常:LINQ to Entities无法识别方法'System.Linq.IQueryable1 [DAL.Protocol] ForUser(System.Linq.IQueryable1 [DAL.Protocol],Int32)'方法,并且此方法无法转换为商店表达.
我希望EF Engine能够构建整个表达式树,链接必要的表达式然后生成SQL.为什么不这样做?
发生这种情况是因为调用ForUser()是在C#编译器在看到传递给Select的lambda时构建的表达式树内部进行的.实体框架试图弄清楚如何将该函数转换为SQL,但由于某些原因(例如d.Protocols目前不存在),它无法调用该函数.
适用于这种情况的最简单方法是让你的助手返回一个标准lambda表达式,然后.Where()自己将其传递给方法:
public static Expression<Func<Protocol, true>> ProtocolIsForUser(int userId)
{
return p => p.UserProtocols.Any(u => u.UserId == userId);
}
Run Code Online (Sandbox Code Playgroud)
...
var protocolCriteria = Helpers.ProtocolIsForUser(userId);
var data = context.Programs
.Select(d => new MyDataDto
{
ProgramId = d.ProgramId,
ProgramName = d.ProgramName,
ClientId = d.ClientId,
Protocols = d.Protocols.Count(protocolCriteria)
})
.ToList();
Run Code Online (Sandbox Code Playgroud)
当你在表达式树之外调用LINQ方法时(就像你一样context.Programs.Select(...)),Queryable.Select()实际上会调用扩展方法,并且它的实现返回一个IQueryable<>表示在原始方法上调用的扩展方法IQueryable<>.这是Select的实现,例如:
public static IQueryable<TResult> Select<TSource,TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector) {
if (source == null)
throw Error.ArgumentNull("source");
if (selector == null)
throw Error.ArgumentNull("selector");
return source.Provider.CreateQuery<TResult>(
Expression.Call(
null,
GetMethodInfo(Queryable.Select, source, selector),
new Expression[] { source.Expression, Expression.Quote(selector) }
));
}
Run Code Online (Sandbox Code Playgroud)
当queryable的Provider必须从中生成实际数据时IQueryable<>,它会分析表达式树并尝试找出如何解释这些方法调用.实体框架已经内置的知识很多LINQ相关的功能像.Where()和.Select(),因此它知道这些方法如何将调用到SQL.但是,它不知道如何处理您编写的方法.
那么为什么这样呢?
var data = context.Programs.ForUser(userId);
Run Code Online (Sandbox Code Playgroud)
答案是您的ForUser方法没有像Select上面的方法那样实现:您没有在查询中添加表达式来表示调用ForUser.相反,您将返回.Where()呼叫的结果.从这个IQueryable<>角度来看,它好像Where()被直接调用了,而这种呼唤ForUser()从未发生过.
您可以通过捕获以下Expression属性来证明这一点IQueryable<>:
Console.WriteLine(data.Expression.ToString());
Run Code Online (Sandbox Code Playgroud)
...会产生这样的东西:
Programs.Where(u => (u.UserId == value(Helpers<>c__DisplayClass1_0).userId))
ForUser()那个表达中没有任何地方可以打电话.
另一方面,如果您ForUser()在表达式树中包含调用,如下所示:
var data = context.Programs.Select(d => d.Protocols.ForUser(id));
Run Code Online (Sandbox Code Playgroud)
...然后该.ForUser()方法实际上永远不会被调用,因此它永远不会返回IQueryable<>知道被调用的.Where()方法的方法.相反,可查询的表达式树显示.ForUser()了被调用.输出其表达式树看起来像这样:
Programs.Select(d => d.Protocols.ForUser(value(Repository<>c__DisplayClass1_0).userId))
实体框架不知道ForUser()应该做什么.就它而言,你可以编写ForUser()一些在SQL中无法做的事情.所以它告诉你这不是一个受支持的方法.
| 归档时间: |
|
| 查看次数: |
1472 次 |
| 最近记录: |