Sök…


Introduktion

Det här ämnet diskuterar hur man kartlägger en-till-en-typ-förhållanden med hjälp av Entity Framework.

Kartlägga en till noll eller en

Så låt oss säga igen att du har följande modell:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }
}

public class MyDemoContext : DbContext
{
  public DbSet<Person> People { get; set; }
  public DbSet<Car> Cars { get; set; }
}

Och nu vill du ställa in det så att du kan uttrycka följande specifikation: en person kan ha en eller noll bil, och varje bil tillhör exakt en person (förhållanden är dubbelriktade, så om CarA tillhör PersonA, så personA 'äger "CarA).

Så låt oss ändra modellen lite: lägg till navigationsegenskaperna och de utländska nyckelegenskaperna:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }
  public int CarId { get; set; }
  public virtual Car Car { get; set; }
}

public class Car
{
  public int CarId { get; set; }
  public string LicensePlate { get; set; }
  public int PersonId { get; set; }
  public virtual Person Person { get; set; }
}

Och konfigurationen:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Person).WithOptional(p => p.Car);                        
  }
}    

Vid denna tid bör detta vara självförklarande. Bilen har en obligatorisk person ( HasRequired () ), med personen som har en valfri bil ( WithOptional () ). Återigen spelar det ingen roll vilken sida du konfigurerar detta förhållande från, var bara försiktig när du använder rätt kombination av Has / With och Required / Valfritt. Från Person ser det ut så här:

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
     this.HasOptional(p => p.Car).WithOptional(c => c.Person);                        
  }
}    

Låt oss nu kolla in db-schemat:

Titta noga: du kan se att det inte finns någon FK i People som hänvisar till Car . Dessutom är FK in Car inte PersonId utan CarId . Här är själva skriptet för FK:

ALTER TABLE [dbo].[Cars]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])

Så detta betyder att CarId och PersonId foregn nyckelegenskaper vi har i modellen i princip ignoreras. De finns i databasen, men de är inte utländska nycklar, som det kan förväntas. Det beror på att en-till-en-mappningar inte stödjer att lägga till FK i din EF-modell. Och det beror på att en-till-en-mappningar är ganska problematiska i en relationsdatabas.

Tanken är att varje person kan ha exakt en bil och att bilen bara kan tillhöra den personen. Eller det kan finnas personregister, som inte har bilar associerade med dem.

Så hur kan detta representeras med utländska nycklar? Uppenbarligen kan det finnas en PersonId i Car och en CarId i People . För att säkerställa att varje person bara kan ha en bil, PersonId vara unik i Car . Men om PersonId är unik i People , hur kan du lägga till två eller flera poster där PersonId är NULL (mer än en bil som inte har ägare)? Svar: det kan du inte (ja, faktiskt kan du skapa ett filtrerat unikt index i SQL Server 2008 och nyare, men låt oss glömma av denna teknik för ett ögonblick; för att inte nämna andra RDBMS). För att inte tala om fallet där du anger båda ändarna av förhållandet ...

Det enda verkliga sättet att verkställa denna regel om tabellerna People and Car har samma 'primära nyckel' (samma värden i de anslutna posterna). Och för att göra detta CarId in Car vara både en PK och en FK till PK of People. Och detta gör hela schemat till en röra. När jag använder detta PersonId jag snarare PK / FK i Car PersonId och konfigurerar det i enlighet därmed:

public class Person
{
  public int PersonId { get; set; }
  public string Name { get; set; }        
  public virtual Car Car { get; set; }
}

public class Car
{        
  public string LicensePlate { get; set; }
  public int PersonId { get; set; }
  public virtual Person Person { get; set; }
}

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
     this.HasRequired(c => c.Person).WithOptional(p => p.Car);
     this.HasKey(c => c.PersonId);
  }
}

Inte perfekt, men kanske lite bättre. Ändå måste du vara vaken när du använder den här lösningen, eftersom den strider mot de vanliga namnkonventionerna, vilket kan leda dig vilse. Här är schemat som genererats från den här modellen:

Så detta förhållande upprätthålls inte av databasschemat, utan av själva Entity Framework. Det är därför du måste vara mycket försiktig när du använder detta, för att inte låta någon temperera direkt med databasen.

Kartlägga en-till-en

Att kartlägga en-till-en (när båda sidor krävs) är också en svår sak.

Låt oss föreställa oss hur detta kan representeras med utländska nycklar. Återigen, en CarId in People som hänvisar till CarId in Car , och en PersonId in Car som hänvisar till PersonId in People .

Vad händer nu om du vill infoga en bilrekord? För att detta ska lyckas måste det finnas en PersonId anges i denna PersonId , eftersom den krävs. Och för att denna PersonId ska vara giltig måste motsvarande post i People finnas. OK, så låt oss gå vidare och infoga personposten. Men för att detta ska lyckas måste en giltig CarId vara i personregistret - men den bilen är inte insatt ännu! Det kan inte vara, för vi måste infoga den hänvisade personposten först. Men vi kan inte infoga den refererade personposten, för den hänvisar tillbaka till bilregistret, så den måste infogas först (utländsk nyckelmottagning :)).

Så detta kan inte heller representeras på det "logiska" sättet. Återigen måste du släppa en av de utländska nycklarna. Vilken du släpper är upp till dig. Den sida som finns kvar med en utländsk nyckel kallas 'beroende', den sida som finns kvar utan en utländsk nyckel kallas 'huvudman'. Och igen, för att säkerställa det unika i det beroende, måste PK vara FK, så att inte lägga till en FK-kolumn och importera den till din modell stöds inte.

Så här är konfigurationen:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
    this.HasRequired(c => c.Person).WithRequiredDependent(p => p.Car);
    this.HasKey(c => c.PersonId);
  }
}

Nu borde du verkligen ha fått logiken i det :) Kom bara ihåg att du kan välja den andra sidan också, var bara försiktig med att använda Dependent / Principal-versionerna av WithRequired (och du måste fortfarande konfigurera PK i bil).

public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
  public PersonEntityTypeConfiguration()
  {
    this.HasRequired(p => p.Car).WithRequiredPrincipal(c => c.Person);
  }
}

Om du kontrollerar DB-schemat kommer du att upptäcka att det är exakt samma som det var för lösningen en-till-en eller noll. Det beror på att detta inte verkställs av schemat utan av EF själv. Så igen, var försiktig :)

Kartlägga en eller noll till en eller noll

Och för att avsluta, låt oss kort titta på fallet när båda sidor är valfria.

Nu borde du bli riktigt uttråkad med dessa exempel :), så jag går inte in i detaljerna och spelar med tanken på att ha två FK-er och de potentiella problemen och varna dig om farorna med att inte upprätthålla dessa regler i schema men i bara EF själv.

Här är den konfigurering du behöver använda:

public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
  public CarEntityTypeConfiguration()
  {
    this.HasOptional(c => c.Person).WithOptionalPrincipal(p => p.Car);
    this.HasKey(c => c.PersonId);
  }
}

Återigen kan du också konfigurera från andra sidan, var noga med att använda rätt metoder :)



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow