Saját entitástípusok

Az EF Core lehetővé teszi olyan entitástípusok modellezését, amelyek csak más entitástípusok navigációs tulajdonságain jelennek meg. Ezeket saját entitástípusoknak nevezzük. Az entitás, amely tartalmaz egy tulajdonolt entitástípust, annak tulajdonosa.

A tulajdonban lévő entitások lényegében a tulajdonos részei, és nélkülük nem létezhetnek, elméletileg hasonlóak az összesítésekhez. Ez azt jelenti, hogy a tulajdonosi entitás definíció szerint a tulajdonossal fennálló kapcsolat függő oldalán található.

Típusok konfigurálása saját tulajdonúként

A legtöbb szolgáltatónál az entitástípusok soha nem konfigurálhatók konvenció szerint – a típus tulajdonosként való konfigurálásához explicit módon kell használnia a OwnsOne metódust OnModelCreating , vagy megjegyzéseket kell fűznie a típushoz OwnedAttribute . Ez alól kivételt képez az Azure Cosmos DB-szolgáltató. Mivel az Azure Cosmos DB egy dokumentumadatbázis, a szolgáltató alapértelmezés szerint az összes kapcsolódó entitástípust saját tulajdonúként konfigurálja.

Ebben a példában StreetAddress egy identitástulajdonság nélküli típus. A rendeléstípus tulajdonságaként egy adott rendelés szállítási címét adhatja meg.

Ezt használhatjuk OwnedAttribute saját entitásként való kezelésre, ha egy másik entitástípusból hivatkozunk rá:

[Owned]
public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}
public class Order
{
    public int Id { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

A metódussal OwnsOneOnModelCreating azt is megadhatja, hogy a ShippingAddress tulajdonság az entitástípus tulajdonában lévő entitás-e Order , és szükség esetén további aspektusokat is konfigurálhat.

modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);

Ha a ShippingAddress tulajdonság privát a Order típusban, a metódus sztringverzióját használhatja OwnsOne :

modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");

A fenti modell a következő adatbázisséma szerint van leképezve:

Képernyőkép a saját referenciát tartalmazó entitás adatbázismodelléről

További információért tekintse meg a teljes mintaprojekt.

Jótanács

A tulajdonos entitástípus kötelezőként jelölhető meg, lásd Kötelező egy-az-egyhez függők a további információkért.

Implicit kulcsok

A referencianavigációval OwnsOne konfigurált vagy felderített saját típusok mindig egy-az-egyhez viszonyban vannak a tulajdonossal. Ezért nincs szükségük saját kulcsértékekre, mivel az idegen kulcsértékek egyediek. Az előző példában a StreetAddress típusnak nem kell kulcstulajdonságot definiálnia.

Annak megértéséhez, hogy az EF Core hogyan követi nyomon ezeket az objektumokat, hasznos tudni, hogy az elsődleges kulcs a saját típus árnyéktulajdonságaként jön létre. Az alárendelt típusú példány kulcsának értéke megegyezik a tulajdonos példány kulcsának értékével.

Birtokolt típusok gyűjteményei

Saját típusok gyűjteményének konfigurálásához használja a OwnsMany helyet OnModelCreating.

A tulajdonban lévő típusoknak elsődleges kulcsra van szükségük. Ha a .NET típuson nincsenek jó jelölttulajdonságok, az EF Core megpróbálhat létrehozni egyet. Ha azonban az önálló típusok gyűjteményen keresztül vannak definiálva, nem elég csak egy árnyéktulajdonságot létrehozni ahhoz, hogy az idegen kulcsként működjön a tulajdonoshoz és a tulajdonolt példány elsődleges kulcsaként is, ahogyan tesszük OwnsOne esetén: minden tulajdonoshoz több önálló típusú példány is tartozhat, ezért a tulajdonos kulcsa nem elég ahhoz, hogy egyedi identitást biztosítson az egyes tulajdonolt példányok számára.

Erre a két legegyszerűbb megoldás a következő:

  • Az elsődleges kulcs meghatározása egy új tulajdonságon, amely független a tulajdonosra mutató idegen kulcstól. A tartalmazott értékeknek minden tulajdonosnál egyedinek kell lenniük (például ha a szülő {1} gyermekkel {1}rendelkezik, akkor a Szülő {2} nem rendelkezhet gyermekkel {1}), így az értéknek nincs semmilyen eredendő jelentése. Mivel az idegen kulcs nem része az elsődleges kulcsnak, az értékei módosíthatók, így a gyermek áthelyezhető az egyik szülőből a másikba, de ez általában az összesített szemantikával szemben fordul elő.
  • Az idegen kulcs és egy további tulajdonság használata összetett kulcsként. A további tulajdonságértéknek most már csak egy adott szülőhöz kell egyedinek lennie (tehát ha a szülő {1} gyermekkel {1,1} rendelkezik, akkor a szülő {2} továbbra is rendelkezhet Gyermek {2,1}értékkel). Ha az idegen kulcs az elsődleges kulcs részévé válik, a tulajdonos és a tulajdonolt entitás közötti kapcsolat megváltoztathatatlanná válik, és jobban tükrözi az aggregátum szemantikát. Az EF Core alapértelmezés szerint ezt teszi.

Ebben a példában az osztályt Distributor fogjuk használni.

public class Distributor
{
    public int Id { get; set; }
    public ICollection<StreetAddress> ShippingCenters { get; set; }
}

Alapértelmezés szerint a navigációs tulajdonságon keresztül a ShippingCenters hivatkozott tulajdonolt típushoz használt elsődleges kulcs formátuma a ("DistributorId", "Id"), ahol a "DistributorId" az FK, és a "Id" egy egyedi int érték.

A HasKey hívásával állíthat be másik elsődleges kulcsot.

modelBuilder.Entity<Distributor>().OwnsMany(
    p => p.ShippingCenters, a =>
    {
        a.WithOwner().HasForeignKey("OwnerId");
        a.Property<int>("Id");
        a.HasKey("Id");
    });

A fenti modell a következő adatbázisséma szerint van leképezve:

Képernyőkép a saját gyűjteményt tartalmazó entitás adatbázismodelléről

Tulajdonolt típusok leképezése táblafelosztással

Referencia tulajdonosi típusok esetén relációs adatbázisok használatakor alapértelmezés szerint ezek ugyanabba a táblába vannak leképezve, mint a tulajdonos. Ehhez két oszlopra kell felosztani a táblát: egyes oszlopok a tulajdonos adatainak tárolására, egyes oszlopok pedig a tulajdonos entitás adatainak tárolására szolgálnak. Ez egy gyakori funkció, más néven táblafelosztás.

Az EF Core alapértelmezés szerint a Navigation_OwnedEntityProperty mintát követve adja meg a tulajdonos entitástípus tulajdonságainak adatbázisoszlopait. Ezért a StreetAddress tulajdonságok megjelennek az "Orders" táblában a "ShippingAddress_Street" és a "ShippingAddress_City" névvel.

A metódus használatával átnevezheti ezeket az HasColumnName oszlopokat.

modelBuilder.Entity<Order>().OwnsOne(
    o => o.ShippingAddress,
    sa =>
    {
        sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
        sa.Property(p => p.City).HasColumnName("ShipsToCity");
    });

Megjegyzés:

Az olyan normál entitástípus-konfigurációs módszerek többsége, mint a Mellőzés , ugyanúgy hívható meg.

.NET-típus megosztása több tulajdonolt típus között

A tulajdonban lévő entitástípus lehet ugyanaz a .NET típusú, mint egy másik tulajdonban lévő entitástípus, ezért előfordulhat, hogy a .NET-típus nem elegendő egy saját típus azonosításához.

Ezekben az esetekben a tulajdonostól a birtokolt entitásra mutató tulajdonság lesz a birtokolt entitás-típus meghatározó navigációja. Az EF Core szempontjából a meghatározó navigáció a típus identitásának része a .NET-típus mellett.

Például a következő osztálybanShippingAddress, és BillingAddress mindkettő azonos .NET típusú. StreetAddress

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Annak megértéséhez, hogy az EF Core hogyan fogja megkülönböztetni ezeknek az objektumoknak a nyomon követett példányait, hasznos lehet azt gondolni, hogy a meghatározó navigáció a példány kulcsának részévé vált a tulajdonos kulcsának értéke és a tulajdonosi típusú .NET-típus mellett.

Beágyazott tulajdonú típusok

Ebben a példában a OrderDetails a tulajdonosa a BillingAddress és a ShippingAddress elemeknek, amelyek mind StreetAddress típusúak. Ezután a OrderDetails a DetailedOrder típus tulajdonában van.

public class DetailedOrder
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
    public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
    Pending,
    Shipped
}

Minden saját típusú navigáció különálló entitástípust határoz meg teljesen független konfigurációval.

A beágyazott tulajdonú típusok mellett egy tulajdonosi típus hivatkozhat egy normál entitásra, amely lehet tulajdonos vagy másik entitás, feltéve, hogy a tulajdonos entitás a függő oldalon van. Ez a képesség a saját entitástípusokat az EF6 összetett típusaitól eltekintve állítja be.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Saját típusok konfigurálása

A modell konfigurálásához a metódust egy fluent-hívásban lehet láncra fűzni OwnsOne :

modelBuilder.Entity<DetailedOrder>().OwnsOne(
    p => p.OrderDetails, od =>
    {
        od.WithOwner(d => d.Order);
        od.Navigation(d => d.Order).UsePropertyAccessMode(PropertyAccessMode.Property);
        od.OwnsOne(c => c.BillingAddress);
        od.OwnsOne(c => c.ShippingAddress);
    });

Figyelje meg a navigációs tulajdonságot, amely a tulajdonosra mutat, és amelynek meghatározásához a WithOwner hívást használták. A tulajdonosi entitástípusra való navigálás meghatározásához, amely nem része a tulajdonosi kapcsolatnak, a WithOwner() meghívását argumentumok nélkül kell végrehajtani.

Lehetőség van elérni ezt az eredményt a OwnedAttribute alkalmazásával mind a OrderDetails-n, mind a StreetAddress-n.

Emellett figyelje meg a Navigation hívást. A saját típusok navigációs tulajdonságai tovább konfigurálhatók , mint a nem saját tulajdonú navigációs tulajdonságok esetében.

A fenti modell a következő adatbázisséma szerint van leképezve:

Képernyőkép a beágyazott tulajdonú hivatkozásokat tartalmazó entitás adatbázismodelléről

Saját típusok tárolása külön táblákban

Az EF6 komplex típusoktól eltérően a tulajdonolt típusok a tulajdonosuktól eltérő táblában is tárolhatók. Ha felül szeretné bírálni azt a konvenciót, amely egy saját típust a tulajdonoséval megegyező táblára képez le, egyszerűen meghívhat ToTable és megadhat egy másik táblanevet. Az alábbi példa OrderDetails és annak két címét külön táblára fogja képezni DetailedOrder:

modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });

Ezt is használhatja a TableAttribute művelet végrehajtására, de vegye figyelembe, hogy ez sikertelen lenne, ha a tulajdonolt típusra több navigáció történik, mivel ebben az esetben több entitástípus lenne leképezve ugyanarra a táblára.

Saját típusok lekérdezése

A tulajdonos lekérdezésekor alapértelmezés szerint a tulajdonosi típusok is megjelennek. Nem szükséges a Include metódust használni, még akkor sem, ha a tulajdonban lévő típusok külön táblában vannak tárolva. A korábban ismertetett modell alapján a következő lekérdezés ki fogja nyerni az adatbázisból a Order, OrderDetails és a két tulajdonolt StreetAddresses elemet.

var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");

Korlátozások

Ezen korlátozások némelyike alapvető fontosságú a saját entitástípusok működéséhez, mások azonban olyan korlátozások, amelyeket a jövőbeli kiadásokban el lehet távolítani:

Tervezési korlátozások

  • Nem lehet létrehozni DbSet<T> egy tulajdonolt típushoz.
  • Nem hívható meg a Entity<T>() birtokolt típussal a ModelBuilder.
  • A tulajdonban lévő entitástípusok példányait nem oszthatja meg több tulajdonos (ez egy jól ismert forgatókönyv olyan értékobjektumok esetében, amelyek nem implementálhatók saját entitástípusok használatával).

Jelenlegi hiányosságok

  • A tulajdonban lévő entitástípusok nem rendelkeznek öröklési hierarchiával

Korábbi verziók hibái

  • Az EF Core 2.x-ben a saját entitástípusokra mutató hivatkozási navigációk csak akkor lehetnek null értékűek, ha kifejezetten a tulajdonostól eltérő táblára vannak leképezve.
  • Az EF Core 3.x-ben a tulajdonossal azonos táblára leképezett saját entitástípusok oszlopai mindig null értékűként vannak megjelölve.