Start Debugging

Новый анализатор EF1004 в EF Core 11 ловит скрытую асинхронную ошибку

EF Core 11 Preview 5 включает анализатор EF1004. Он помечает вызов ToAsyncEnumerable() на IQueryable, чтобы вы случайно не выполнили запрос к базе данных синхронно внутри await foreach.

.NET 11 Preview 5, выпущенный 2026-06-09, добавляет новый анализатор EF Core с идентификатором диагностики EF1004. Он ловит ошибку, которую стало гораздо проще допустить с тех пор, как System.Linq.AsyncEnumerable вошёл в состав BCL в .NET 10: вызов ToAsyncEnumerable() на запросе EF Core с незаметным синхронным выполнением.

Как сюда попадает неправильный вызов

Теперь, когда System.Linq.AsyncEnumerable поставляется в среде выполнения, его методы расширения доступны почти везде. Один из них — ToAsyncEnumerable(), который адаптирует любой IEnumerable<T> к IAsyncEnumerable<T>. IQueryable<T> в EF Core тоже является IEnumerable<T>, поэтому вызов компилируется без проблем и выглядит правильно рядом с await foreach:

// Looks async. Is not.
await foreach (var blog in db.Blogs.ToAsyncEnumerable())
{
    Console.WriteLine(blog.Name);
}

Проблема в том, что ToAsyncEnumerable() оборачивает синхронное перечисление. Он обходит IQueryable с помощью блокирующего перечислителя, поэтому обращение к базе данных выполняется в вызывающем потоке. await foreach даёт вам синтаксис потоковой передачи без какого-либо асинхронного поведения. Под нагрузкой это именно та форма, которая истощает пул потоков и приводит к взаимным блокировкам, проявляющимся только в продакшене.

Чего вместо этого ожидает EF1004

EF Core предоставляет собственный метод AsAsyncEnumerable(). Он направляет запрос через асинхронный конвейер EF Core, поэтому каждая строка материализуется по мере поступления из DbDataReader без блокировки потока:

// Runs through EF Core's async query pipeline.
await foreach (var blog in db.Blogs.AsAsyncEnumerable())
{
    Console.WriteLine(blog.Name);
}

Эти два имени метода различаются на три символа, оба возвращают IAsyncEnumerable<T> и оба компилируются. До Preview 5 ничто не подсказывало, какой из них вы выбрали. EF1004 срабатывает на этапе сборки, как только вы вызываете ToAsyncEnumerable() на IQueryable<T>, указывая вам на AsAsyncEnumerable().

Как превратить это в ошибку

Анализатор поставляется в пакете анализаторов EF Core и работает во время обычной сборки, поэтому вы получаете предупреждение без дополнительной настройки в проекте, ссылающемся на Microsoft.EntityFrameworkCore 11.0.0. Если вы хотите гарантировать, что никто не выпустит синхронную версию, повысьте его уровень в файле проекта:

<PropertyGroup>
  <WarningsAsErrors>$(WarningsAsErrors);EF1004</WarningsAsErrors>
</PropertyGroup>

Это естественно сочетается с паттернами потоковой передачи, описанными в Как использовать IAsyncEnumerable<T> с EF Core 11: AsAsyncEnumerable() — это вызов, который заставляет await foreach действительно работать в потоковом режиме. EF1004 — просто страховка, которая не даёт похожему методу проскользнуть мимо код-ревью.

Источник: примечания к выпуску EF Core для .NET 11 Preview 5 и анонс .NET 11 Preview 5.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Назад