簡體   English   中英

Entity Framework Core 2.2 編譯查詢結構參數在本地評估

[英]Entity Framework Core 2.2 Compiled Query Struct Parameter Evaluating Locally

我一直在研究使用 Entity Framework Core 編譯的查詢。 我使用的是當前最新的穩定版本 2.2.2。 我正在閱讀這篇文章( https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities )以了解編譯查詢,並且我試圖了解這是 EF Core 中的錯誤還是他們尚未完成的問題。 我知道這篇文章是為 EF6 編寫的,但我希望編譯后的查詢以相同的方式工作並且找不到任何相反的內容。

這是我的 DbContext 設置和一個簡單的分頁選項結構:

public class BuggyDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
}

public struct PagingOptions
{
    public int Skip;
    public int Take;
}

[Table("User")]
public class User
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }
}

這是我編譯的查詢。 第一個根據結構參數選擇用戶的“頁面”(與文章中的示例非常相似)。 第二個做同樣的事情,但接受“skip”和“take”作為基本 int32 類型的單獨參數。

var badQuery = EF.CompileQuery<BuggyDbContext, PagingOptions, IEnumerable<User>>((context, paging) =>
     context.Users
         .OrderBy(u => u.LastName)
         .Skip(paging.Skip)
         .Take(paging.Take));

var goodQuery = EF.CompileQuery<BuggyDbContext, int,int, IEnumerable<User>>((context, skip, take) =>
     context.Users
         .OrderBy(u => u.LastName)
         .Skip(skip)
         .Take(take));

這是演示問題的用法:

 using (var db = new BuggyDbContext())
 {

     var pagingOptions = new PagingOptions {
         Skip = 0,
         Take = 25
     };
     var firstPage = badQuery.Invoke(db, pagingOptions).ToList();
     var alternateFirstPage = goodQuery.Invoke(db, pagingOptions.Skip, pagingOptions.Take).ToList();
 }

goodQuery運行時,一切都按預期進行。 以下內容在日志中顯示為我期望的生成的 SQL:

SELECT [u].[UserId], [u].[FirstName], [u].[LastName]
FROM [User] AS [u]
ORDER BY [u].[LastName]
OFFSET @__skip ROWS FETCH NEXT @__take ROWS ONLY

但是,當badQuery運行時,它會選擇所有記錄,然后在內存中評估 Skip 和 Take,這將導致糟糕的性能。

SELECT [u].[UserId], [u].[FirstName], [u].[LastName]
FROM [User] AS [u]
ORDER BY [u].[LastName]

warn: Microsoft.EntityFrameworkCore.Query[20500]
  => Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor
  The LINQ expression 'Skip(__paging.Skip)' could not be translated 
and will be evaluated locally.
warn: Microsoft.EntityFrameworkCore.Query[20500]
  => Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor
  The LINQ expression 'Take(__paging.Take)' could not be translated 
and will be evaluated locally.

由於幾個非常重要的原因,我更喜歡使用復雜類型(引用或值結構,不關心)作為編譯查詢的參數:

  1. Lambda 函數具有最大數量的輸入參數。 如果我有一個需要多個輸入的復雜過濾、排序和分組查詢,我將被迫走其他路線。
  2. 對於調用查詢的開發人員來說,輸入參數更加清晰。 即使在這個例子中,調用查詢的開發人員也會開始輸入 query.Invoke,然后在智能感知中盯着 2 個未命名的整數參數。 他們知道他們的意思的唯一方法是查看查詢。 如果輸入參數更改順序或含義,則對查詢的更改將是極其危險的。

EF Core 3.0 路線圖 ( https://docs.microsoft.com/en-us/ef/core/what-is-new/roadmap ) 確實表示他們正在研究他們的 LINQ 查詢策略(以避免這種可怕的運行查詢,或者至少讓您在運行之前意識到或在您的日志中捕獲警告)但我希望 struct 參數起作用。

如果我做錯了什么,或者這是正在進行中的事情,有人在這里有任何見解嗎? 你也會認為這是一個錯誤嗎?

我在https://github.com/aspnet/EntityFrameworkCore/issues/14857向 EF 團隊提交了錯誤報告它已關閉並標記為https://github.com/aspnet/EntityFrameworkCore/issues/13976

被轉移到積壓。 這是回應:“根據正常分類,這是一個有合理解決方法的功能,我們尚未看到大量需求,因此我們現在將其移至積壓工作。”

我相信這是由於表達式的存儲和評估方式。 這絕對是一個錯誤,但至於將來是否會修復我不確定。

我在我的項目上設置分頁的方式是使用一個通用類,最終從值類型構建一個表達式。 (請注意,有未使用的屬性和字段,因為我在邏輯中遺漏了一些特定於域的代碼)

public class Pagination<T>
{
    public IQueryable<T> Items;

    public int CurrentPageNumber { get; }

    public int PageSize { get; }

    public int StartPage { get; }

    public int TotalPages { get; set; }

    public Pagination(IQueryable<T> items, int pageNumber, int pageSize)
    {
        if (pageNumber <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(pageNumber));
        }

        if (pageSize <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(pageSize));
        }

        if (((decimal)DisplayPages % 2) == 0)
        {
            throw new ArgumentOutOfRangeException(nameof(DisplayPages), "Number of pages to render must be odd.");
        }

        Items = items;
        CurrentPageNumber = pageNumber;
        PageSize = pageSize;
        StartPage = 1;

        if (items.Any())
        {
            var rowCount = items.Count();
            TotalPages = (int)Math.Ceiling((decimal)rowCount / PageSize);
        }
        else
        {
            TotalPages = 1;
        }

    }

    public IQueryable<T> GetPageData()
    {
        return Items.Skip((CurrentPageNumber - 1) * PageSize).Take(PageSize) ?? new List<T>().AsQueryable();
    }

}

然后你可以像這樣使用它:

var paginatedObjects = new Pagination<Type>(query, 1, 10)
{
    //Options if nessasary
};
paginatedObjects.GetPageData();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM