Zoeken…


Invoering

In dit onderwerp wordt beschreven hoe u relaties van het één-op-één-type kunt toewijzen met behulp van Entity Framework.

Eén op nul of één in kaart brengen

Laten we dus nogmaals zeggen dat je het volgende model hebt:

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; }
}

En nu wilt u het zo instellen dat u de volgende specificatie kunt uitdrukken: één persoon kan één of nul auto hebben, en elke auto behoort precies tot één persoon (relaties zijn bidirectioneel, dus als CarA behoort tot PersonA, dan bezit PersonA de eigendom 'CarA).

Laten we het model dus een beetje wijzigen: voeg de navigatie-eigenschappen en de eigenschappen van de externe sleutel toe:

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; }
}

En de configuratie:

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

Tegen die tijd zou dit zichzelf moeten verklaren. De auto heeft een vereiste persoon ( HasRequired () ), waarbij de persoon een optionele auto heeft ( WithOptional () ). Nogmaals, het maakt niet uit vanaf welke kant u deze relatie configureert, wees voorzichtig wanneer u de juiste combinatie van Has / With en Required / Option gebruikt. Van de Person kant ziet het er zo uit:

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

Laten we nu eens kijken naar het DB-schema:

Kijk goed: je kunt zien dat er geen FK in People om naar Car te verwijzen. Ook is de FK in Car niet de PersonId , maar de CarId . Hier is het eigenlijke script voor de FK:

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

Dus dit betekent dat de CarId en PersonId foregn belangrijkste eigenschappen die we hebben in het model in principe worden genegeerd. Ze staan in de database, maar het zijn geen externe sleutels, zoals te verwachten is. Dat komt omdat een-op-een-toewijzingen het toevoegen van de FK aan uw EF-model niet ondersteunen. En dat komt omdat een-op-een-afbeeldingen nogal problematisch zijn in een relationele database.

Het idee is dat elke persoon precies één auto kan hebben, en die auto kan alleen tot die persoon behoren. Of er kunnen persoonsrecords zijn waaraan geen auto's zijn gekoppeld.

Dus hoe kan dit worden weergegeven met buitenlandse sleutels? Het is duidelijk dat er een PersonId in Car en een CarId in People . Om af te dwingen dat elke persoon slechts één auto kan hebben, zou PersonId uniek moeten zijn in Car . Maar als PersonId uniek is in People , hoe kunt u dan twee of meer records toevoegen waarbij PersonId NULL (meer dan één auto zonder eigenaar)? Antwoord: dat kan niet (eigenlijk kunt u een gefilterde unieke index maken in SQL Server 2008 en nieuwer, maar laten we deze technische details even vergeten; om nog maar te zwijgen over andere RDBMS). Om nog maar te zwijgen van het geval waarin u beide uiteinden van de relatie opgeeft ...

De enige echte manier om deze regel af te dwingen als de tabellen People en Car dezelfde primaire sleutel hebben (dezelfde waarden in de gekoppelde records). En om dit te doen, moet CarId in Car zowel een PK als een FK voor de PK van mensen zijn. En dit maakt het hele schema een puinhoop. Wanneer ik dit gebruik, geef ik liever de PK / FK een naam in Car PersonId en configureer deze dienovereenkomstig:

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);
  }
}

Niet ideaal, maar misschien een beetje beter. Toch moet u alert zijn wanneer u deze oplossing gebruikt, omdat deze in strijd is met de gebruikelijke naamgevingsconventies, die u op een dwaalspoor kunnen brengen. Dit is het schema dat met dit model is gegenereerd:

Deze relatie wordt dus niet afgedwongen door het databaseschema, maar door Entity Framework zelf. Daarom moet je heel voorzichtig zijn wanneer je dit gebruikt, om niemand direct met de database te laten temperen.

Eén op één in kaart brengen

Eén-op-één in kaart brengen (wanneer beide zijden vereist zijn) is ook een lastige zaak.

Laten we ons voorstellen hoe dit kan worden weergegeven met buitenlandse sleutels. Nogmaals, een CarId in People die verwijst naar CarId in Car , en een PersonId in Car die verwijst naar de PersonId in People .

Wat gebeurt er als u een autorecord wilt invoegen? Om dit te laten slagen, moet er een PersonId opgegeven in dit PersonId , omdat dit vereist is. En om deze PersonId geldig te laten zijn, moet het bijbehorende record in People bestaan. OK, dus laten we doorgaan en het persoonsrecord invoegen. Maar om dit te laten slagen, moet een geldige CarId in het persoonsrecord staan - maar die auto is nog niet geplaatst! Dat kan niet, want we moeten eerst het genoemde persoonsrecord invoegen. Maar we kunnen het verwezen persoonsrecord niet invoegen, omdat het verwijst naar het autorecord, dus dat moet eerst worden ingevoerd (externe sleutel-onderscheiding :)).

Dus dit kan ook niet op de 'logische' manier worden weergegeven. Nogmaals, je moet een van de buitenlandse sleutels laten vallen. Welke je laat vallen, is aan jou. De zijde die met een externe sleutel wordt achtergelaten, wordt de 'afhankelijke' genoemd, de zijde die zonder een externe sleutel wordt achtergelaten, wordt de 'principaal' genoemd. En nogmaals, om het unieke in de afhankelijke te waarborgen, moet de PK de FK zijn, dus het toevoegen van een FK-kolom en het importeren van die naar uw model wordt niet ondersteund.

Dus hier is de configuratie:

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

Inmiddels zou je er echt de logica van moeten hebben begrepen :) Denk eraan dat je ook de andere kant kunt kiezen, wees voorzichtig met het gebruik van de afhankelijke / hoofdversies van WithRequired (en je moet de PK nog steeds in Car configureren).

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

Als u het DB-schema controleert, zult u zien dat het precies hetzelfde is als in het geval van de één-op-één- of nuloplossing. Dat komt omdat nogmaals, dit wordt niet afgedwongen door het schema, maar door EF zelf. Dus nogmaals, wees voorzichtig :)

Eén of nul-op-één of nul in kaart brengen

En om af te sluiten, laten we kort kijken naar het geval wanneer beide zijden optioneel zijn.

Inmiddels zou je je deze voorbeelden echt moeten vervelen :), dus ik ga niet in op de details en speel met het idee van twee FK-s en de mogelijke problemen en waarschuw je voor de gevaren van het niet handhaven van deze regels in de schema maar alleen in EF zelf.

Dit is de configuratie die u moet toepassen:

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

Nogmaals, u kunt ook vanaf de andere kant configureren, wees voorzichtig en gebruik de juiste methoden :)



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow