[英]Why Linq “where” expression after Select gets evaluated locally when created through a generic method?
我正在使用泛型实现规范模式,并尝试将标准动态应用于映射实体的投影简单(未映射)版本。 一般情况下,它工作正常,但只要我添加Select
并Where
其后应用Where
,Linq就会在本地评估表达式。
完全相同的Linq表达式产生正确的SQL查询,如果我将其构建为局部变量并传递给相同的Where
。
这是简化的相关代码段:
public interface ISomeable
{
string Some { get; set; }
}
public static Expression<Func<T, bool>> GetCriteria<T>() where T : class, ISomeable
{ return e => (e.Some == "Hello"); }
...
Expression<Func<MySimpleEntity, bool>> someCriteria = e => (e.Some == "Hello");
Expression<Func<MySimpleEntity, bool>> someCriteria2 = GetCriteria<MySimpleEntity>();
var query = db.Entities
.Select(s => new MySimpleEntity { Id = s.Id, Some = s.Some });
// if this Select is removed and MySimpleEntity in both expressions replaced with MyFullEntity,
// the issue disappears
// this succeeds
var filteredQueryResults = query.Where(someCriteria).ToList();
// at this point, someCriteria2 is set to the same e => (e.Some == "Hello");
// this fails: why is it evaluated locally and not in SQL? <-----
filteredQueryResults = query.Where(someCriteria2).ToList();
// results in a warning:
/*
* 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning:
* The LINQ expression 'where (new MySimpleEntity() {Id = [s].Id, Some = [s].Some}.Some == "Hello")'
* could not be translated and will be evaluated locally.'.
*/
如何让它为someCriteria2
生成正确的SQL而不是本地评估?
我怀疑我需要某种铸造,但不确定在哪里。 someCriteria
和someCriteria2
在调试器中看起来完全相同,所以我不知道为什么Linq对它们的处理方式不同。
我创建了一个最小的.Net Core Console应用程序来重现案例。 完整的要点在这里:
https://gist.github.com/progmars/eeec32a533dbd2e1f85e551db1bc53f8
NuGet依赖项:Microsoft.EntityFrameworkCore.SqlServer“Version =”2.2.6“Microsoft.Extensions.Logging”Version =“2.2.0”Microsoft.Extensions.Logging.Console“Version =”2.2.0“
一些解释:
它与同一查询执行两次的事实无关。 如果我注释掉第一个query.Where(someCriteria).ToList()
使用someCriteria2
的第二次调用仍然无法生成有效的SQL。 但是,如果我将someCriteria2
替换为someCriteria
用于第二个查询并让它运行,我会在控制台中获得两个完全有效的SQL查询。 因此,它与someCriteria2
和Select
投影的泛型有关 - 由于某种原因,即使编译器(和调试器监视)认为它们是完全相同的类型,Linq也不someCriteria2
两个变量都视为相同。
问题类似于LINQ表达式无法转换为基本属性以及如何在EF Core表达式中使用继承属性? ,但在这种情况下,无论是DeclaringType
和ReflectedType
的的MemberInfo
点ISomeable
接口,而不是实际的类。
再次,这在某种程度上使Select
方案中的EF Core感到困惑。 我已经检查了最新的EF Core 3.0预览版,它也无法正常工作。 您可以考虑将其发布到问题跟踪器。
到目前为止,我能提供的唯一解决方法是使用自定义ExpressionVisitor
对表达式进行后处理,并将成员访问器绑定到实际的类。 像这样的东西:
public static partial class ExpressionUtils
{
public static Expression<T> FixMemberAccess<T>(this Expression<T> source)
{
var body = new MemberAccessFixer().Visit(source.Body);
if (body == source.Body) return source;
return source.Update(body, source.Parameters);
}
class MemberAccessFixer : ExpressionVisitor
{
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != null && node.Expression.Type != node.Member.DeclaringType)
{
var member = node.Expression.Type.GetMember(node.Member.Name).Single();
if (member.ReflectedType != member.DeclaringType)
member = member.DeclaringType.GetMember(member.Name).Single();
return Expression.MakeMemberAccess(node.Expression, member);
}
return base.VisitMember(node);
}
}
}
现在
var someCriteria2 = GetCriteria<MySimpleEntity>().FixMemberAccess();
将生成精确的表达式作为工作编译时someCriteria
表达式并且没有客户端评估。
注意:您仍然需要class
约束,以避免上一个问题中的转换问题并使此解决方法起作用。
我认为您的代码存在问题
GetCriteria<MySimpleEntity>();
linq
无法直接将其翻译为sql
或者没有直接翻译。 如果你想使用它。 执行ToList()
然后添加.Where(someCriteria2).ToList();
。 在观察者中,它看到/评估它是相同的。 但是在查询本身中,生成sql似乎不会那样工作。
我也遇到过,在我DateTime
扩展方法甚至将其转换为string
在我Where
我有我的执行之外它linq
查询,并将其添加
var dateUtc = DateTime.UtcNow.ExtensionMethod()
;
...Where(x => x.Date >= dateUtc
)
或者我在select
和/或where
之前FirstorDefault, First, ToList()
执行FirstorDefault, First, ToList()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.