Оценка клиента и сервера

Как правило, Entity Framework Core пытается выполнить как можно большую часть работы по вычислению запроса на сервере. Некоторые части запроса преобразуются в параметры, которые могут вычисляться на стороне клиента. Остальные части запроса (вместе с созданными параметрами) передаются поставщику базы данных, который должен определить эквивалентный запрос к базе данных для вычисления на сервере. EF Core поддерживает вычисление на стороне клиента в проекции верхнего уровня (в основном это последний вызов Select()). Если проекцию верхнего уровня в запросе невозможно преобразовать на стороне сервера, EF Core получает необходимые данные с сервера и вычисляет оставшиеся части запроса на стороне клиента. Если EF Core обнаруживает выражение в любом месте, кроме проекции верхнего уровня, которую невозможно преобразовать на стороне сервера, то возникает исключение времени выполнения. Сведения о том, как EF Core определяет, что нельзя преобразовать на стороне сервера, см. в статье Как работают запросы.

Примечание.

До версии 3.0 платформа Entity Framework Core поддерживала вычисление любой части запроса на стороне клиента. Дополнительные сведения см. в разделе Предыдущие версии.

Совет

Вы можете скачать используемый в этой статье пример из репозитория GitHub.

Вычисление на стороне клиента в проекции верхнего уровня

В приведенном ниже примере вспомогательный метод используется для стандартизации URL-адресов блогов, которые возвращаются из базы данных SQL Server. Так как поставщик SQL Server не знает, как реализован этот метод, его невозможно преобразовать в SQL. Все остальные аспекты запроса выполняются в базе данных, но передача возвращаемого URL с помощью этого метода выполняется в клиенте.

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(
        blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog.Url) })
    .ToList();
public static string StandardizeUrl(string url)
{
    url = url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

Неподдерживаемое вычисление на стороне клиента

Хотя вычисление на стороне клиента полезно, иногда оно может приводить к снижению производительности. Рассмотрим приведенный ниже запрос, в котором вспомогательный метод теперь используется в фильтре Where. Так как фильтр невозможно применить в базе данных, все данные необходимо извлечь в память, чтобы применить фильтр в клиенте. В зависимости от фильтра и объема данных на сервере вычисление на стороне клиента может привести к низкой производительности. Поэтому платформа Entity Framework Core блокирует такое вычисление и выдает исключение во время выполнения.

var blogs = context.Blogs
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

Явное вычисление на стороне клиента

В некоторых случаях, например, в указанных ниже, может потребоваться явное принудительное вычисление на стороне клиента.

  • Объем данных мал, поэтому вычисление в клиенте не приводит к значительному снижению производительности.
  • Используемый оператор LINQ не преобразуется на стороне сервера.

В таких случаях можно явно согласиться на вычисление в клиенте, вызвав такой метод, как AsEnumerable или ToList (AsAsyncEnumerable или ToListAsync при асинхронном выполнении). При использовании AsEnumerable результаты передаются в потоковом режиме, но при использовании ToList производится буферизация путем создания списка, который также занимает дополнительную память. Однако если перечисление выполняется несколько раз, сохранять результаты в списке выгоднее, так как запрос к базе данных только один. Вам следует оценить, какой метод полезнее в конкретном случае.

var blogs = context.Blogs
    .AsEnumerable()
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

Совет

Если вы используете AsAsyncEnumerable и хотите дополнительно составить запрос на стороне клиента, вы можете использовать библиотеку System.Interactive.Async, в которой определены операторы для асинхронных перечислимых операций. Дополнительные сведения см. в разделе об операторах LINQ, используемых на клиентской стороне.

Возможная утечка памяти при вычислении на стороне клиента

Так как преобразование и компиляция запросов требуют много ресурсов, EF Core кэширует скомпилированный план запроса. Кэшированный делегат может использовать клиентский код при вычислении проекции верхнего уровня на стороне клиента. EF Core создает параметры для вычисляемых клиентом частей дерева и повторно использует план запроса, заменяя значения параметров. Однако некоторые константы в дереве выражения невозможно преобразовать в параметры. Если кэшированный делегат содержит такие константы, эти объекты не могут быть собраны сборщиком мусора, так как на них по-прежнему есть ссылки. Если такой объект содержит DbContext или другие службы, это может привести к росту потребления памяти приложением с течением времени. Такое поведение обычно является признаком утечки памяти. EF Core создает исключение всякий раз, когда обнаруживает константы типа, который нельзя сопоставить с помощью текущего поставщика базы данных. Ниже приведены распространенные причины и способы решения.

  • Использование метода экземпляра: при использовании методов экземпляра в клиентской проекции дерево выражений содержит константу экземпляра. Если метод не использует какие бы то ни было данные из экземпляра, рекомендуется сделать его статическим. Если в теле метода требуются данные экземпляра, передайте их в метод в качестве аргумента.
  • Передача аргументов константы в метод. Обычно такое происходит при использовании this в аргументе для клиентского метода. Рекомендуем разделить аргумент на несколько скалярных аргументов, которые могут быть сопоставлены поставщиком базы данных.
  • Другие константы: если константа возникает в любом другом случае, можно оценить, требуется ли константа в обработке. Если константа необходима или если ни одно из предыдущих решений не подходит, создайте локальную переменную для хранения значения и используйте ее в запросе. EF Core преобразует локальную переменную в параметр.

предыдущих версий

Следующий раздел относится к версиям EF Core до 3.0.

Более старые версии EF Core поддерживали вычисление на стороне клиента в любой части запроса, а не только в проекции верхнего уровня. Поэтому запросы, наподобие приведенного в разделе Неподдерживаемое вычисление на стороне клиента, работали правильно. Так как такое поведение могло приводить к неочевидным проблемам с производительностью, платформа EF Core добавляла в журнал предупреждение о вычислении в клиенте. Дополнительные сведения о просмотре выходных данных журнала см. в статье Ведение журнала.

Кроме того, платформа EF Core позволяла изменять поведение по умолчанию: либо создавать исключение, либо ничего не делать при вычислении в клиенте (исключая вычисление в проекции). При выборе создания исключения поведение становилось таким же, как в версии 3.0. Чтобы изменить поведение, необходимо настроить предупреждения при настройке параметров для контекста — обычно в DbContext.OnConfiguring или в Startup.cs, если используется ASP.NET Core.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
        .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}