Práce s odkazovými typy s možnou hodnotou Null

C# 8 zavedl novou funkci s názvem typy odkazů s možnou hodnotou null (NRT), která umožňuje přidávání poznámek k typům odkazů, které označují, zda jsou platné pro ně, aby obsahovaly hodnotu null nebo ne. Pokud s touto funkcí začínáte, doporučujeme, abyste se s ní seznámili přečtením dokumentace jazyka C#. V nových šablonách projektů jsou ve výchozím nastavení povolené typy odkazů s možnou hodnotou Null, ale v existujících projektech zůstanou zakázané, pokud se k tomu explicitně nezavolíte.

Tato stránka představuje podporu ef Core pro typy odkazů s možnou hodnotou null a popisuje osvědčené postupy pro práci s nimi.

Požadované a volitelné vlastnosti

Hlavní dokumentace k požadovaným a volitelným vlastnostem a jejich interakce s odkazovými typy s možnou hodnotou null je stránka Povinné a Volitelné vlastnosti . Doporučujeme začít tím, že si nejprve přečtete tuto stránku.

Poznámka

Při povolování typů odkazů s možnou hodnotou null u existujícího projektu buďte opatrní: vlastnosti typu odkazu, které byly dříve nakonfigurovány jako volitelné, se teď nakonfigurují podle potřeby, pokud nejsou explicitně označeny jako null. Při správě schématu relační databáze může dojít k vygenerování migrací, které mění hodnotu null sloupce databáze.

Nenulovatelné vlastnosti a inicializace

Pokud jsou povolené typy odkazů s možnou hodnotou null, kompilátor jazyka C# vygeneruje upozornění pro všechny neinicializované neinicializované vlastnosti, které nemají hodnotu null, protože by obsahovaly hodnotu null. V důsledku toho nelze použít následující běžný způsob psaní typů entit:

public class CustomerWithWarning
{
    public int Id { get; set; }

    // Generates CS8618, uninitialized non-nullable property:
    public string Name { get; set; }
}

Vazba konstruktoru je užitečná technika, která zajistí inicializaci vlastností, které nemají hodnotu null:

public class CustomerWithConstructorBinding
{
    public int Id { get; set; }
    public string Name { get; set; }

    public CustomerWithConstructorBinding(string name)
    {
        Name = name;
    }
}

V některých scénářích bohužel není možné použít vazbu konstruktoru; například nelze inicializovat vlastnosti navigace tímto způsobem.

Požadované navigační vlastnosti představují další potíže: i když závislý objekt vždy existuje pro daný objekt zabezpečení, může nebo nemusí být načten konkrétním dotazem v závislosti na potřebách v tomto okamžiku v programu (podívejte se na různé vzory načítání dat). Současně je nežádoucí, aby tyto vlastnosti byly null, protože by to vynutilo veškerý přístup k nim kontrolovat hodnotu null, i když jsou požadovány.

Jedním ze způsobů, jak se vypořádat s těmito scénáři, je mít nenulovou vlastnost s backingovým polem s možnou hodnotou null:

private Address? _shippingAddress;

public Address ShippingAddress
{
    set => _shippingAddress = value;
    get => _shippingAddress
           ?? throw new InvalidOperationException("Uninitialized property: " + nameof(ShippingAddress));
}

Vzhledem k tomu, že navigační vlastnost není null, je nakonfigurována požadovaná navigace; a pokud je navigace správně načtena, bude závislá přístupná prostřednictvím vlastnosti. Pokud je však vlastnost přístup bez prvního správného načtení související entity, invalidOperationException je vyvolána, protože kontrakt rozhraní API byl použit nesprávně. Mějte na paměti, že EF musí být nakonfigurován tak, aby vždy přistupoval k záložnímu poli, a ne vlastnost, protože spoléhá na schopnost číst hodnotu i v případě, že není nastavena. Přečtěte si dokumentaci o tom , jak to provést, a zvažte zadání PropertyAccessMode.Field , aby byla konfigurace správná.

Jako alternativu terseru je možné jednoduše inicializovat vlastnost na hodnotu null pomocí operátoru null-forgiving (!):

public Product Product { get; set; } = null!;

Skutečná hodnota null se nikdy nepozoruje s výjimkou programovací chyby, například při přístupu k navigační vlastnosti bez správného načtení související entity předem.

Poznámka

Navigace kolekcí, které obsahují odkazy na více souvisejících entit, by vždy měly být nenulovatelné. Prázdná kolekce znamená, že neexistují žádné související entity, ale samotný seznam by neměl být null.

DbContext a DbSet

Běžný postup, jak mít neinicializované vlastnosti DbSet u typů kontextu, je také problematické, protože kompilátor pro ně nyní vygeneruje upozornění. Dá se to opravit takto:

public class NullableReferenceTypesContext : DbContext
{
    public DbSet<Customer> Customers => Set<Customer>();
    public DbSet<Order> Orders => Set<Order>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=EFNullableReferenceTypes;Trusted_Connection=True");
}

Další strategií je použít nenulové automatické vlastnosti, ale inicializovat je na hodnotu null pomocí operátoru null-forgiving (!) k mlčení upozornění kompilátoru. Základní konstruktor DbContext zajišťuje, že se všechny vlastnosti DbSet inicializují a hodnota null se u nich nikdy nebude sledovat.

Při práci s volitelnými relacemi je možné narazit na upozornění kompilátoru, kde by skutečná výjimka odkazu null nebylo možné. Při překladu a provádění dotazů LINQ ef Core zaručuje, že pokud volitelná související entita neexistuje, bude se žádná navigace na ni jednoduše ignorovat, a ne vyvolání. Kompilátor však nezná tuto záruku EF Core a generuje upozornění, jako by byl dotaz LINQ spuštěný v paměti s LINQ to Objects. V důsledku toho je nutné použít operátor null-forgiving (!) k informování kompilátoru, že skutečná hodnota null není možná:

Console.WriteLine(order.OptionalInfo!.ExtraAdditionalInfo!.SomeExtraAdditionalInfo);

K podobnému problému dochází v případě, že mezi volitelnými navigacemi zahrnete více úrovní relací:

var order = context.Orders
    .Include(o => o.OptionalInfo!)
    .ThenInclude(op => op.ExtraAdditionalInfo)
    .Single();

Pokud zjistíte, že se to provádí hodně a typy entit, které se v dotazech EF Core používají převážně (nebo výhradně), zvažte možnost nastavit navigační vlastnosti, které nejsou null, a nakonfigurovat je jako volitelné prostřednictvím rozhraní API Fluent nebo datových poznámek. Tím se odeberou všechna upozornění kompilátoru při zachování volitelné relace; Pokud však vaše entity procházejí mimo EF Core, můžete pozorovat hodnoty null, i když jsou vlastnosti anotovány jako nenulovatelné.

Omezení ve starších verzích

Před EF Core 6.0 platí následující omezení:

  • Veřejný povrch rozhraní API nebyl opatřen poznámkami k hodnotě null (veřejné rozhraní API bylo "nepochybné"), což někdy zneužito, když je funkce NRT zapnutá. To zahrnuje zejména asynchronní operátory LINQ vystavené EF Core, jako je FirstOrDefaultAsync. Veřejné rozhraní API je plně opatřeno poznámkami pro možnost null od EF Core 6.0.
  • Zpětná analýza nepodporuje referenční typy c# 8 s možnou hodnotou null (NRTs): EF Core vždy vygeneroval kód jazyka C#, který předpokládal, že funkce je vypnutá. Například textové sloupce s možnou hodnotou null byly vygenerovány jako vlastnost s typem string , ne string?s rozhraním API Fluent nebo datovými poznámkami použitými ke konfiguraci, zda je vlastnost povinná nebo ne. Pokud používáte starší verzi EF Core, můžete stále upravit vygenerovaný kód a nahradit je poznámkami k nullability jazyka C#.