Entity Framework Core 2.2 Скомпилированный параметр структуры запроса, оцениваемый локально - PullRequest
0 голосов
/ 27 февраля 2019

Я изучал скомпилированные запросы с использованием 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; }
}

Вот мои скомпилированные запросы. Первый выбирает «страницу» пользователей на основе параметра структуры (очень похоже на пример из статьи). Второй выполняет то же самое, но принимает «пропустить» и «взять» в качестве отдельных параметровбазовый тип 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 , выберите ВСЕ иззаписи, а затем оценивает пропуск в памяти, что может привести к ужасной производительности.

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. Лямбда-функции имеют максимальное количество входных параметров.Если бы у меня был запрос с какой-то сложной фильтрацией, сортировкой и группировкой, который требовал нескольких входов, я был бы вынужден пойти по другому пути.
  2. Входные параметры намного понятнее разработчику, который вызываетзапрос.Даже в этом примере разработчик, вызывающий запрос, начнет вводить query.Invoke, а затем будет смотреть на 2 безымянных целочисленных аргумента в intellisense.Единственный способ узнать, что они имеют в виду, это посмотреть на запрос.Изменения в запросе были бы чрезвычайно опасны, если бы входные параметры изменили порядок или значение.

Дорожная карта EF Core 3.0 (https://docs.microsoft.com/en-us/ef/core/what-is-new/roadmap) говорит, что они работают над своей стратегией LINQ Query в целом (чтобы избежать таких ужасно выполняющихся запросов или, по крайней мере, дать вам знать об этом до выполнения иличто-то перехватывает предупреждение в ваших журналах), но я ожидал, что параметр struct будет работать.

У кого-нибудь есть понимание, если я что-то делаю неправильно или это что-то, что находится в разработке?это тоже ошибка?

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Я отправил отчет об ошибке команде EF на https://github.com/aspnet/EntityFrameworkCore/issues/14857 Он был закрыт и помечен как дубликат https://github.com/aspnet/EntityFrameworkCore/issues/13976

Был перемещен в очередь.Вот ответ: «Основываясь на обычной сортировке, это функция, для которой есть разумный обходной путь, и для которой у нас пока нет значительного спроса, поэтому мы пока перенесем ее в отставание».

0 голосов
/ 27 февраля 2019

Я полагаю, это связано с тем, как выражение хранится и оценивается.Это определенно ошибка, но я не уверен в том, будет ли это исправлено в будущем.

Способ, которым я настраивал разбиение на страницы в моем проекте, заключался в использовании универсального класса, который в итоге создает выражение из типов значений,(Обратите внимание, что есть неиспользуемые свойства и поля, поскольку я оставил некоторый специфичный для домена код из логики)

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...