簡體   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