Megosztás:


Nullálható hivatkozástípusok használata

A C# nullázható hivatkozástípusok (NRT) lehetővé teszik a hivatkozástípusok annotálását, amely jelzi, hogy érvényes-e, hogy tartalmazzák a null-t vagy sem. Ha még nem ismeri ezt a funkciót, javasoljuk, hogy a C#-dokumentumok elolvasásával ismerkedjen meg vele. A null értékű hivatkozástípusok alapértelmezés szerint engedélyezve vannak az új projektsablonokban, de a meglévő projektekben letiltva maradnak, kivéve, ha kifejezetten engedélyezték.

Ez az oldal bemutatja az EF Core null értékű referenciatípusok támogatását, és ismerteti a velük való munka ajánlott eljárásait.

Kötelező és nem kötelező tulajdonságok

A kötelező és nem kötelező tulajdonságok legfőbb dokumentációja és a nullával értékelhető referenciatípusokkal való interakciójuk a Kötelező és Opcionális Tulajdonságok oldalon található. Javasoljuk, hogy először olvassa el ezt az oldalt.

Megjegyzés:

Körültekintően járjon el, ha null értékű hivatkozástípusokat engedélyez egy meglévő projektben: a korábban nem kötelezőként konfigurált referenciatípus-tulajdonságok mostantól szükség szerint lesznek konfigurálva, kivéve, ha explicit módon null értékűre vannak állítva. Relációs adatbázisséma kezelésekor előfordulhat, hogy migrálások jönnek létre, amelyek megváltoztatják az adatbázisoszlop nullképességét.

Nem null értékű tulajdonságok és inicializálás

Ha engedélyezve vannak a null értékű hivatkozástípusok, a C#-fordító figyelmeztetéseket bocsát ki minden inicializálatlan, nem null értékű tulajdonságra, mivel ezek tartalmazhatják null-t. Ennek eredményeképpen az entitástípusok írásának gyakori módja nem használható:

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

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

Ha C# 11 vagy újabb verziót használ, a szükséges tagok tökéletes megoldást nyújtanak erre a problémára:

public required string Name { get; set; }

A fordító mostantól garantálja, hogy amikor a kód példányosít egy Vevőt, mindig inicializálja annak "Name" tulajdonságát. Mivel a tulajdonsághoz hozzárendelt adatbázisoszlop nem null értékű, az EF által betöltött példányok mindig tartalmaznak nem null értékű nevet is.

Ha a C# régebbi verzióját használja, a konstruktorkötés egy alternatív technika, amellyel biztosítható, hogy a nem null értékű tulajdonságok inicializálva legyenek:

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

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

Sajnos egyes helyzetekben a konstruktorkötés nem megoldás; a navigációs tulajdonságok például nem inicializálhatók így. Ezekben az esetekben egyszerűen inicializálhatja a tulajdonságot null a null-megbocsátó operátor segítségével (további részletekért lásd alább):

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

Szükséges navigációs tulajdonságok

A szükséges navigációs tulajdonságok további nehézséget okoznak: bár egy függő mindig létezik egy adott taghoz, előfordulhat, hogy egy adott lekérdezés betölti vagy sem, attól függően, hogy milyen igények vannak a program adott pontján (lásd az adatok betöltésének különböző mintáit). Ugyanakkor előfordulhat, hogy nem kívánatos, hogy ezek a tulajdonságok null értékűvé tehetők legyenek, mivel ez arra kényszerítené az összes hozzáférést, hogy az ellenőrzésüket null-ra végezze el, még akkor is, ha a navigációs útvonal ismerten betöltődik, és ezért nem lehet null.

Ez nem feltétlenül probléma! Ha egy szükséges függő megfelelően van betöltve (pl. keresztül Include), akkor a navigációs tulajdonság elérése garantáltan mindig nem nulla értéket ad vissza. Másrészt az alkalmazás dönthet úgy, hogy ellenőrzi, hogy a kapcsolat be van-e töltve, ha ellenőrzi, hogy a navigáció van-e null. Ilyen esetekben ésszerű a navigáció null értékűvé tétele. Ez azt jelenti, hogy szükséges navigációkat kellett végezni a függőről a főre:

  • Nem-null értékűnek kell lennie, ha programozói hibának tekintik a navigáció elérését, amikor az nincs betöltve.
  • Null értékűnek kell lennie, ha elfogadható, ha az alkalmazáskód ellenőrzi a navigációt annak megállapításához, hogy a kapcsolat be van-e töltve.

Ha szigorúbb megközelítést szeretne, akkor null értékű háttérmezővel rendelkező nem null értékű tulajdonsággal rendelkezhet:

private Address? _shippingAddress;

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

Amíg a navigáció megfelelően be van töltve, a függő elérhető lesz a tulajdonságon keresztül. Ha azonban a tulajdonság a kapcsolódó entitás megfelelő betöltése nélkül érhető el, a rendszer egy InvalidOperationException hibát ad ki, mivel az API-szerződést helytelenül használták.

Megjegyzés:

A több kapcsolódó entitásra mutató hivatkozásokat tartalmazó gyűjteménynavigációk mindig nem null értékűek kell legyenek. Az üres gyűjtemény azt jelenti, hogy nem léteznek kapcsolódó entitások, de maga a lista soha nem lehet null.

DbContext és DbSet

Az EF használata esetén gyakori gyakorlat, hogy a környezeti típusok kezdetben nem inicializált DbSet-tulajdonságokat tartalmaznak.

public class MyContext : DbContext
{
    public DbSet<Customer> Customers { get; set;}
}

Bár ez általában fordítói figyelmeztetést okoz, az EF Core 7.0 és újabb verziók letiltják ezt a figyelmeztetést, mivel az EF automatikusan inicializálja ezeket a tulajdonságokat tükröződés útján.

Az EF Core régebbi verziójában a következő módon megkerülheti ezt a problémát:

public class MyContext : DbContext
{
    public DbSet<Customer> Customers => Set<Customer>();
}

Egy másik stratégia a kötelező automatikus tulajdonságok használata, de azokat kezdetben null-ra inicializáljuk, a null-megbocsátó operátor (!) segítségével elnémítva a fordító figyelmeztetéseit. A DbContext alapkonstruktor biztosítja, hogy az összes DbSet-tulajdonság inicializálva legyen, és a null érték soha nem lesz megfigyelhető rajtuk.

Az opcionális kapcsolatok kezelésekor olyan fordítói figyelmeztetések is előfordulhatnak, amelyekben egy tényleges null hivatkozási kivétel lehetetlen lenne. A LINQ-lekérdezések lefordításakor és végrehajtásakor az EF Core garantálja, hogy ha nem létezik opcionális kapcsolódó entitás, a rendszer egyszerűen figyelmen kívül hagyja a hozzá tartozó navigációt a dobás helyett. A fordítóprogram azonban nem ismeri az EF Core garanciáját, ezért olyan figyelmeztetéseket generál, mintha a LINQ-lekérdezést memória szintjén, a LINQ to Objects-szel hajtanák végre. Ennek eredményeképpen a null-elbocsátó operátort (!) kell használni, hogy tájékoztassa a fordítót arról, hogy a tényleges null érték nem lehetséges:

var order = await context.Orders
    .Where(o => o.OptionalInfo!.SomeProperty == "foo")
    .ToListAsync();

Hasonló probléma akkor fordul elő, ha több kapcsolatszintet is belevesz a választható navigációkba:

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

Ha ezt gyakran tapasztalja, és a szóban forgó entitástípusokat elsősorban (vagy kizárólag) ef Core-lekérdezésekben használják, fontolja meg a navigációs tulajdonságok null értékűvé tételét, és konfigurálja őket opcionálisként a Fluent API-n vagy az Adatjegyzeteken keresztül. Ezzel eltávolítja az összes fordítói figyelmeztetést, miközben a kapcsolat nem kötelező; ha azonban entitásai kívül találhatók az EF Core-on, észlelhet null értékeket, annak ellenére, hogy a tulajdonságok nem null értékűként szerepelnek.

Korlátozások a régebbi verziókban

Az EF Core 6.0 előtt a következő korlátozások érvényesek:

  • A nyilvános API-felületet nem annotálták nullalitásra (a nyilvános API "null-oblivious" volt), ezért néha nehézkes volt használni, amikor az NRT funkció be van kapcsolva. Ide tartoznak az EF Core által közzétett aszinkron LINQ-operátorok, például a FirstOrDefaultAsync. A nyilvános API az EF Core 6.0-tól kezdve teljes egészében annotálva van a nullbilitás érdekében.
  • A fordított tervezés nem támogatja a C# 8 null értékű referenciatípusokat (NRT-ket):: Az EF Core mindig olyan C#-kódot generált, amely feltételezte, hogy a funkció ki van kapcsolva. Például a null értéket elfogadó szöveges oszlopok olyan tulajdonságként lettek létrehozva, amelynek típusa string, nem pedig string?, és a Fluent API vagy az Adatjegyzetek segítségével konfigurálható, hogy a tulajdonság kötelező-e vagy sem. Ha az EF Core egy régebbi verzióját használja, továbbra is szerkesztheti az állványozott kódot, és lecserélheti őket C#-alapú nullbilitási széljegyzetekre.