Новые возможности в Entity Framework Core 3.х

В следующем списке указаны основные новые возможности EF Core 3.х.

EF Core 3.х — это основной выпуск. Он содержит несколько критических изменений (улучшения API), которые могут негативно повлиять на работу существующих приложений.

Перепроектирование LINQ

LINQ позволяет писать запросы к базам данных с помощью любого языка .NET, используя различные типы форматированных данных, функции IntelliSense и проверку типа во время компиляции. Однако LINQ также поддерживает написание неограниченного числа сложных запросов, содержащих произвольные выражения (вызовы методов или операции). Обработка всех этих комбинаций — основная задача поставщиков LINQ.

В EF Core 3.х мы переопределили поставщик LINQ, чтобы включить преобразование дополнительных шаблонов запросов в SQL. При этом в большинстве случаев создаются более эффективные запросы, а появление неэффективных предотвращается. Новый поставщик LINQ открывает больше возможностей для создания запросов и повышения производительности в будущих выпусках. Использование этого поставщика никак не повлияет на работу существующих приложений и поставщиков данных.

Ограниченная оценка клиента

Самое важное изменение разработки внесено в способ обработки выражений LINQ, которые нельзя преобразовать в параметры или в SQL.

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

Например, если системе EF Core 2.2 не удавалось преобразовать предикат в вызов Where(), она выполняла инструкцию SQL без фильтра, передавала все строки из базы данных, а затем фильтровала их в памяти:

var specialCustomers = context.Customers
    .Where(c => c.Name.StartsWith(n) && IsSpecialCustomer(c));

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

В EF Core 3.х мы ограничили оценку клиентов, чтобы она осуществлялась только на проекции верхнего уровня (в основном последний вызов Select()). Когда EF Core 3.х обнаруживает выражения, которые нельзя преобразовать в любой другой точке запроса, вызывается исключение среды выполнения.

Чтобы оценить условие предиката в клиенте (как в предыдущем примере), разработчикам теперь нужно явно переключить вычисление запроса на LINQ to Objects:

var specialCustomers = context.Customers
    .Where(c => c.Name.StartsWith(n))
    .AsEnumerable() // switches to LINQ to Objects
    .Where(c => IsSpecialCustomer(c));

Дополнительные сведения о том, как это может повлиять на существующие приложения, см. в документации по критическим изменениям.

Одна инструкция SQL для каждого запроса LINQ

В версии 3.х существенно изменен еще один аспект разработки. Теперь для каждого запроса LINQ всегда создается отдельная инструкция SQL. В предыдущих версиях иногда создавалось несколько инструкций SQL. Например, для преобразования вызовов Include() к свойствам навигации коллекции и запросов на основе определенных шаблонов с вложенными запросами. В некоторых случаях это было удобно, а для Include() даже помогало предотвратить отправку избыточных данных по каналу. Но реализовать такой сценарий было сложно. Это приводило к неэффективному поведению (запросы N+1). Иногда после нескольких запросов возвращались несогласованные данные.

Если EF Core 3.х не может преобразовать запрос LINQ в одну инструкцию SQL, вызывается исключение среды выполнения, как и при оценке клиента. Но мы реализовали в EF Core возможность преобразовать много распространенных шаблонов, которые использовались для создания нескольких запросов, в один общий запрос с помощью инструкций JOIN.

Поддержка Cosmos DB

Поставщик Cosmos DB для EF Core позволяет разработчикам, знакомым с моделью программирования EF, легко использовать Azure Cosmos DB в качестве базы данных приложения. Мы хотим, чтобы такие преимущества Cosmos DB, как глобальное распределение, постоянная готовность, эластичная масштабируемость и низкая задержка, стали еще доступнее .NET-разработчикам. Поставщик позволяет использовать API-интерфейс SQL в Cosmos DB с большинством функций EF Core, включая автоматическое отслеживание изменений, LINQ и преобразование значений.

Дополнительные сведения см. в документации по поставщику Cosmos DB.

Поддержка C# 8.0

В EF Core 3.х используются преимущества некоторых новых возможностей в C# 8.0:

Асинхронные потоки

Асинхронные результаты запросов теперь предоставляются с помощью нового стандартного интерфейса IAsyncEnumerable<T> и могут использоваться с помощью await foreach.

var orders =
    from o in context.Orders
    where o.Status == OrderStatus.Pending
    select o;

await foreach(var o in orders.AsAsyncEnumerable())
{
    Process(o);
}

Дополнительные сведения см. в документации по асинхронным потокам в C#.

Ссылочные типы, допускающие значение null

Если эта новая функция включена в код, EF Core проверяет допустимость значений NULL в свойствах ссылочного типа и применяет ее к соответствующим столбцам и связям в базе данных. Свойства ссылочных типов, не допускающих значения NULL, обрабатываются так, как если бы в них использовался атрибут заметки к данным [Required].

Например, в указанном ниже классе свойства типа string? будут настроены как необязательные, а типа string — как обязательные:

public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string? MiddleName { get; set; }
}

Дополнительные сведения см. в статье о работе со ссылочными типами, допускающими значения NULL, в документации по EF Core.

Перехват операций базы данных

Новый API перехвата в EF Core 3.х позволяет автоматически вызывать пользовательскую логику, когда в рамках нормальной работы EF Core выполняются низкоуровневые операции с базами данных. Например, при открытии соединений, фиксации транзакций или выполнении команд.

Как и функции перехвата, использовавшиеся в EF 6, перехватчики позволяют перехватывать операции до или после того, как они происходят. Если перехватить их до выполнения, можно обойти его и предоставить альтернативные результаты на основе логики перехвата.

Например, для управления текстом команды можно создать DbCommandInterceptor:

public class HintCommandInterceptor : DbCommandInterceptor
{
    public override InterceptionResult<DbDataReader> ReaderExecuting(
        DbCommand command,
        CommandEventData eventData,
        InterceptionResult<DbDataReader> result)
    {
        // Manipulate the command text, etc. here...
        command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
        return result;
    }
}

Зарегистрировать перехватчик можно с помощью DbContext:

services.AddDbContext(b => b
    .UseSqlServer(connectionString)
    .AddInterceptors(new HintCommandInterceptor()));

Реконструирование представлений базы данных

Типы запросов, представляющие данные, которые можно считать из базы данных, но нельзя обновить, переименованы в типы сущностей без ключей. Так как в большинстве случаев они отлично подходят для сопоставления представлений базы данных, EF Core теперь автоматически создает типы сущностей без ключей при реконструировании представлений баз данных.

Например, с помощью программы командной строки dotnet для EF можно ввести:

dotnet ef dbcontext scaffold "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer

Теперь средство автоматически формирует типы шаблонов для представлений и таблиц без ключей:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Names>(entity =>
    {
        entity.HasNoKey();
        entity.ToView("Names");
    });

    modelBuilder.Entity<Things>(entity =>
    {
        entity.HasNoKey();
    });
}

Зависимые сущности, имеющие общую с субъектом таблицу, теперь являются необязательными

Начиная с EF Core 3.х, если OrderDetails принадлежит Order или явно сопоставляется с той же таблицей, можно добавлять Order без OrderDetails, и все свойства OrderDetails, за исключением первичного ключа, будут сопоставляться со столбцами, допускающими значения NULL.

При отправке запроса EF Core задаст OrderDetails значение null, если какому-либо его обязательному свойству не задано значение или если отсутствуют необходимые свойства, помимо первичного ключа, и все свойства имеют значение null.

public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public OrderDetails Details { get; set; }
}

[Owned]
public class OrderDetails
{
    public int Id { get; set; }
    public string ShippingAddress { get; set; }
}

EF 6.3 на платформе .NET Core

Это не совсем функция EF Core 3.х, но мы думаем, что такая возможность важна для многих наших клиентов.

Мы понимаем, что многие существующие приложения используют предыдущие версии EF и перенос этих приложений на EF Core исключительно для поддержки функций .NET Core может оказаться очень трудоемким. По этой причине мы решили перенести новую версию EF 6 в .NET Core 3.х.

Дополнительные сведения см. в статье о новых возможностях EF 6.

Отложенные функции

Некоторые функции, первоначально запланированные в EF Core 3.х, будут реализованы в будущих выпусках:

  • Возможность пропускать части модели при миграции (вопрос № 2725).
  • Сущности контейнера свойств, которым посвящены два отдельных вопроса: № 9914 об общих сущностях и № 13610 о поддержке сопоставления индексированных свойств.