Ricerca…


introduzione

Questo argomento discute come mappare le relazioni di tipo one-to-one usando Entity Framework.

Mappatura da uno a zero o uno

Quindi diciamo ancora che hai il seguente modello:

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

E ora vuoi configurarlo in modo che tu possa esprimere le seguenti specifiche: una persona può avere una o zero auto, e ogni auto appartiene a una persona esattamente (le relazioni sono bidirezionali, quindi se CarA appartiene a PersonA, allora la proprietà di PersonA 'CarA).

Quindi modifichiamo un po 'il modello: aggiungiamo le proprietà di navigazione e le proprietà della chiave esterna:

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

E la configurazione:

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

A questo punto questo dovrebbe essere auto-esplicativo. L'auto ha una persona richiesta ( HasRequired () ), con la persona che ha una macchina opzionale ( WithOptional () ). Di nuovo, non importa da che parte si configura questa relazione, fai attenzione quando usi la giusta combinazione di Has / With e Required / Optional. Dal lato Person , sembrerebbe questo:

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

Ora diamo un'occhiata allo schema db:

Osserva attentamente: puoi vedere che non c'è FK in People per riferirsi a Car . Inoltre, l'FK in Car non è il PersonId , ma il CarId . Ecco lo script vero e proprio per l'FK:

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

Quindi questo significa che le proprietà chiave di CarId e PersonId foregn che abbiamo nel modello sono sostanzialmente ignorate. Sono nel database, ma non sono chiavi esterne, come ci si potrebbe aspettare. Questo perché i mapping uno-a-uno non supportano l'aggiunta dell'FK al modello EF. E questo perché i mapping uno-a-uno sono abbastanza problematici in un database relazionale.

L'idea è che ogni persona possa avere esattamente una macchina, e quella macchina può appartenere solo a quella persona. Oppure potrebbero esserci record di persone, che non hanno automobili associate con loro.

Quindi, come potrebbe essere rappresentato con chiavi esterne? Ovviamente, potrebbe esserci un PersonId in Car e un CarId in People . Per far sì che ogni persona possa avere una sola auto, PersonId dovrebbe essere unico in Car . Ma se PersonId è univoco in People , come è possibile aggiungere due o più record in cui PersonId è NULL (più di una macchina che non ha proprietari)? Risposta: non è possibile (beh, in realtà, è possibile creare un indice univoco filtrato in SQL Server 2008 e versioni successive, ma dimentichiamo per un momento questo aspetto tecnico, per non parlare degli altri RDBMS). Per non parlare del caso in cui si specificano entrambe le estremità della relazione ...

L'unico vero modo per applicare questa regola se le tabelle People e the Car hanno la stessa chiave primaria (stessi valori nei record connessi). E per fare questo, CarId in Car deve essere sia un PK che un FK per il PK di People. E questo rende tutto lo schema un disastro. Quando lo uso preferisco nominare il PK / FK in Car PersonId e configurarlo di conseguenza:

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

Non ideale, ma forse un po 'meglio. Tuttavia, devi stare attento quando usi questa soluzione, perché va contro le consuete convenzioni di denominazione, che potrebbero portare fuori strada. Ecco lo schema generato da questo modello:

Quindi questa relazione non viene applicata dallo schema del database, ma dallo stesso Entity Framework. Ecco perché devi stare molto attento quando lo usi, per non lasciare che qualcuno si dimostri direttamente con il database.

Mappatura one-to-one

Mappare uno-a-uno (quando sono richieste entrambe le parti) è anche una cosa complicata.

Immaginiamo come questo possa essere rappresentato con chiavi esterne. Ancora una volta, un CarId in People che fa riferimento a CarId in Car e un PersonId in auto che fa riferimento a PersonId in People .

Ora cosa succede se vuoi inserire un record della macchina? Affinché questo abbia successo, deve esserci un PersonId specificato in questo record auto, perché è richiesto. E affinché questo PersonId sia valido, deve esistere il record corrispondente in People . OK, quindi andiamo avanti e inserire il record della persona. Ma per CarId , un CarId valido deve essere registrato nella persona, ma quella macchina non è ancora inserita! Non può essere, perché dobbiamo prima inserire il record della persona indicata. Ma non possiamo inserire il record della persona riferita, perché fa riferimento al record della macchina, quindi deve essere inserito per primo (chiave-chiave esterna :)).

Quindi questo non può essere rappresentato anche dal modo "logico". Ancora una volta, devi rilasciare una delle chiavi esterne. Qual è la tua caduta dipende da te. Il lato che è rimasto con una chiave esterna è chiamato 'dipendente', il lato che rimane senza una chiave esterna è chiamato 'principale'. E ancora, per garantire l'unicità nel dipendente, il PK deve essere l'FK, quindi l'aggiunta di una colonna FK e l'importazione del modello non è supportata.

Quindi, ecco la configurazione:

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

Ormai dovresti davvero averne la logica :) Ricorda solo che puoi scegliere anche l'altro lato, fai solo attenzione a usare le versioni dipendenti / principale di WithRequired (e devi ancora configurare il PK in Car).

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

Se controlli lo schema del DB, scoprirai che è esattamente lo stesso come nel caso della soluzione one-to-one o zero. Questo perché ancora una volta, questo non è applicato dallo schema, ma dallo stesso EF. Quindi, di nuovo, fai attenzione :)

Mappatura di uno o zero-a-uno o zero

E per finire, esaminiamo brevemente il caso quando entrambi i lati sono opzionali.

Ormai dovresti essere davvero annoiato con questi esempi :), quindi non intendo entrare nei dettagli e giocare con l'idea di avere due FK-s e i potenziali problemi e metterti in guardia sui pericoli di non applicare queste regole nel schema ma solo EF stesso.

Ecco la configurazione che devi applicare:

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

Anche in questo caso, puoi configurare dall'altro lato, fai solo attenzione a usare i metodi giusti :)



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow