Entity Framework
Relazione di mappatura con Entity Framework Code First: One-to-one e variazioni
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 :)