繁体   English   中英

为什么Linq“where”表达式在通过泛型方法创建时在本地进行评估?

[英]Why Linq “where” expression after Select gets evaluated locally when created through a generic method?

我正在使用泛型实现规范模式,并尝试将标准动态应用于映射实体的投影简单(未映射)版本。 一般情况下,它工作正常,但只要我添加SelectWhere其后应用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而不是本地评估?

我怀疑我需要某种铸造,但不确定在哪里。 someCriteriasomeCriteria2在调试器中看起来完全相同,所以我不知道为什么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查询。 因此,它与someCriteria2Select投影的泛型有关 - 由于某种原因,即使编译器(和调试器监视)认为它们是完全相同的类型,Linq也不someCriteria2两个变量都视为相同。

问题类似于LINQ表达式无法转换为基本属性以及如何在EF Core表达式中使用继承属性? ,但在这种情况下,无论是DeclaringTypeReflectedType的的MemberInfoISomeable接口,而不是实际的类。

再次,这在某种程度上使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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM