Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Типы ссылок с поддержкой null (NRT) в C# позволяют аннотировать ссылочные типы, указывая, допустимо ли для них содержать null или нет. Если вы не знакомы с этой функцией, рекомендуется ознакомиться с ним, прочитав документы C#. Ссылочные типы, допускающие значение NULL, включены по умолчанию в новых шаблонах проектов, но остаются отключенными в существующих проектах, если явно не будет разрешено.
На этой странице представлена поддержка EF Core для ссылочных типов, допускающих значение NULL, и описаны рекомендации по работе с ними.
Обязательные и необязательные свойства
Основная документация по обязательным и необязательным свойствам и их взаимодействию с типами ссылок, допускающих значение NULL, — это страница "Обязательные и необязательные свойства ". Сначала рекомендуется начать чтение этой страницы.
Примечание.
Соблюдайте осторожность при включении ссылочных типов, допускающих значение NULL в существующем проекте: свойства ссылочного типа, которые ранее были настроены как необязательные, теперь будут настроены в соответствии с обязательными требованиями, если они явно не помечены как значения NULL. При управлении схемой реляционной базы данных это может привести к созданию миграций, которые изменяют наличие значения null для столбца базы данных.
Свойства, не допускающие значения null, и инициализация
Если включены ссылочные типы, допускающие значение NULL, компилятор C# выдает предупреждения для любого неинициализированного ненулевого свойства, так как они будут содержать null. В результате нельзя использовать следующий распространенный способ написания типов сущностей:
public class Customer
{
public int Id { get; set; }
// Generates CS8618, uninitialized non-nullable property:
public string Name { get; set; }
}
Если вы используете C# 11 или более поздней версии, обязательные члены предоставляют идеальное решение для этой проблемы.
public required string Name { get; set; }
Компилятор теперь гарантирует, что при создании экземпляра объекта Customer всегда инициализируется его свойство Name. И так как столбец базы данных, сопоставленный с свойством, не допускает значения NULL, все экземпляры, загруженные EF, всегда содержат ненулевое имя.
Если вы используете более раннюю версию C#, привязка конструктора является альтернативным способом гарантировать, что ваши ненулевые свойства инициализированы:
public class CustomerWithConstructorBinding
{
public int Id { get; set; }
public string Name { get; set; }
public CustomerWithConstructorBinding(string name)
{
Name = name;
}
}
К сожалению, в некоторых сценариях привязка конструктора не является вариантом; Например, нельзя инициализировать свойства навигации таким образом. В таких случаях можно просто инициализировать свойство null с помощью оператора с прощением null (но см. ниже дополнительные сведения):
public Product Product { get; set; } = null!;
Обязательные свойства навигации
Необходимые навигационные свойства представляют собой дополнительную сложность: хотя зависимые всегда существуют для заданного объекта, они могут загружаться или не загружаться определенным запросом в зависимости от потребностей программы на данном этапе (см. различные шаблоны загрузки данных). В то же время может быть нежелательно делать эти свойства допускающими значение null, так как это приведет к принудительной проверке null доступа в каждом случае, даже если известно, что навигация загружена и, следовательно, не может быть null.
Это не обязательно проблема! Если требуемая зависимость правильно загружена (например, через Include), гарантируется, что доступ к свойству навигации всегда будет возвращать ненулевое значение. С другой стороны, приложение может проверить, загружается ли связь, проверяя, является ли навигация null. В таких случаях разумно сделать навигацию обнуляемой. Это означает, что необходимые навигации от зависимого к главному:
- Следует делать его ненулевым, если считается ошибкой программиста обращаться к навигации, когда она не загружена.
- Значение NULL должно быть допустимым, если оно допустимо для кода приложения, чтобы проверить навигацию, чтобы определить, загружается ли связь.
Если вы хотите более строгий подход, вы можете иметь ненулевое свойство с полем резервной копии, допускающим значение NULL:
private Address? _shippingAddress;
public Address ShippingAddress
{
set => _shippingAddress = value;
get => _shippingAddress
?? throw new InvalidOperationException("Uninitialized property: " + nameof(ShippingAddress));
}
Если навигация загружена должным образом, зависимая сущность будет доступна через свойство. Однако если свойство обращается без первой правильной загрузки связанной сущности, создается исключение, InvalidOperationException так как контракт API использовался неправильно.
Примечание.
Навигации коллекции, содержащие ссылки на несколько связанных сущностей, всегда должны быть не допускающими значения NULL. Пустая коллекция означает, что связанные сущности не существуют, но сам список никогда не должен быть null.
DbContext и DbSet
В EF обычно используются неинициализированные свойства DbSet для типов контекстов:
public class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set;}
}
Хотя это обычно вызывает предупреждение компилятора, EF Core 7.0 и выше подавляет это предупреждение, так как EF автоматически инициализирует эти свойства с помощью отражения.
В более старой версии EF Core эту проблему можно обойти следующим образом:
public class MyContext : DbContext
{
public DbSet<Customer> Customers => Set<Customer>();
}
Другая стратегия заключается в использовании не допускающих значение null автоматических свойств, но инициализировать их значением null, используя оператор подавления null (!), чтобы убрать предупреждение компилятора. Базовый конструктор DbContext гарантирует, что все свойства DbSet будут инициализированы, и значение NULL никогда не будет наблюдаться на них.
Навигация и включение связей, допускающих значение NULL
При работе с необязательными связями можно столкнуться с предупреждениями компилятора, в которых фактическое null исключение ссылки будет невозможным. При переводе и выполнении запросов LINQ EF Core гарантирует, что если необязательная связанная сущность не существует, навигация к ней просто будет игнорироваться, а не вызывать. Однако компилятор не знает об этой гарантии EF Core и выдает предупреждения, как если бы запрос LINQ был выполнен в памяти с LINQ to Objects. В результате необходимо использовать оператор null-forgiving (!) для информирования компилятора о том, что фактическое null значение невозможно:
var order = await context.Orders
.Where(o => o.OptionalInfo!.SomeProperty == "foo")
.ToListAsync();
Аналогичная проблема возникает при включении нескольких уровней связей между необязательными навигациями:
var order = await context.Orders
.Include(o => o.OptionalInfo!)
.ThenInclude(op => op.ExtraAdditionalInfo)
.SingleAsync();
Если вы часто сталкиваетесь с такой ситуацией, и рассматриваемые типы сущностей в основном (или исключительно) используются в запросах EF Core, подумайте о том, чтобы сделать свойства навигации не допускающими значения NULL, а также настраивать их как необязательные с помощью Fluent API или аннотаций данных. Это приведет к удалению всех предупреждений компилятора при сохранении связи необязательной; однако если ваши сущности используются за пределами EF Core, вы можете увидеть значения null, хотя свойства аннотируются как не допускающие значения NULL.
Ограничения в более ранних версиях
До EF Core 6.0 применяются следующие ограничения:
- Область общедоступного API не была помечена для работы с нулевыми значениями (общедоступный API игнорировал нулевые значения), что иногда затрудняет использование, когда функция работы с нулевыми значениями включена. Это особенно включает асинхронные операторы LINQ, предоставляемые EF Core, например FirstOrDefaultAsync. Общедоступный API полностью аннотирован для nullability, начиная с EF Core 6.0.
- Обратная инженерия не поддерживает nullable типы ссылок C# 8 (NRTs): EF Core всегда создает код C#, предполагая, что эта функция отключена. Например, шаблон текстовых столбцов, допускающих значение NULL, формировался как свойство с типом
string, а неstring?. При этом использовались текучий API или аннотации к данным, чтобы определить, является ли свойство обязательным. Если вы используете более раннюю версию EF Core, вы по-прежнему можете изменить сформированный шаблон кода и заменить его заметками о допустимости значений NULL в C#.