2013-08-14 17 views
57

Vogliamo utilizzare la relazione opzionale uno ad uno utilizzando Entity Framework Code First. Abbiamo due entità.Relazione facoltativa uno ad uno utilizzando Entity Framework Fluent API

public class PIIUser 
{ 
    public int Id { get; set; } 

    public int? LoyaltyUserDetailId { get; set; } 
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; } 
} 

public class LoyaltyUserDetail 
{ 
    public int Id { get; set; } 
    public double? AvailablePoints { get; set; } 

    public int PIIUserId { get; set; } 
    public PIIUser PIIUser { get; set; } 
} 

PIIUser possono avere un LoyaltyUserDetail ma LoyaltyUserDetail deve avere un PIIUser. Abbiamo provato queste tecniche di approccio fluente.

modelBuilder.Entity<PIIUser>() 
      .HasOptional(t => t.LoyaltyUserDetail) 
      .WithOptionalPrincipal(t => t.PIIUser) 
      .WillCascadeOnDelete(true); 

Questo approccio non ha creato LoyaltyUserDetailId chiave esterna nella tabella PIIUsers.

Successivamente abbiamo provato il seguente codice.

modelBuilder.Entity<LoyaltyUserDetail>() 
      .HasRequired(t => t.PIIUser) 
      .WithRequiredDependent(t => t.LoyaltyUserDetail); 

Ma questa volta EF non ha creato chiavi esterne in queste 2 tabelle.

Avete qualche idea per questo problema? Come possiamo creare una relazione opzionale one to one usando entity framework fluent api?

risposta

78

Codice EF Prima supporta le relazioni 1:1 e 1:0..1. Quest'ultimo è quello che stai cercando ("uno a zero-o-uno").

I suoi tentativi di fluente hanno da dire richiesta su entrambe le estremità in un caso e optional su entrambe le estremità nell'altra.

Quello che vi serve è opzionale su un'estremità e richiesta dall'altro.

Ecco un esempio di programmazione dal codice E.F. Primo libro

modelBuilder.Entity<PersonPhoto>() 
.HasRequired(p => p.PhotoOf) 
.WithOptional(p => p.Photo); 

L'entità PersonPhoto ha una proprietà di navigazione denominata PhotoOf che punta a un tipo Person. Il tipo Person ha una proprietà di navigazione denominata Photo che punta al tipo PersonPhoto.

Nelle due classi correlate, si utilizza non chiavi esterne di chiave primaria, ogni tipo. Ad esempio, non utilizzerai le proprietà LoyaltyUserDetailId o PIIUserId. Invece, la relazione dipende dai campi Id di entrambi i tipi.

Se si utilizza l'API fluente come sopra, non è necessario specificare LoyaltyUser.Id come chiave esterna, EF lo individuerà.

Quindi, senza avere il vostro codice di mettermi alla prova (Odio fare questo dalla mia testa) ... Vorrei tradurre questo testo nel codice come

public class PIIUser 
{ 
    public int Id { get; set; }  
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; } 
} 

public class LoyaltyUserDetail 
{ 
    public int Id { get; set; } 
    public double? AvailablePoints { get; set; }  
    public PIIUser PIIUser { get; set; } 
} 

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    modelBuilder.Entity<LoyaltyUserDetail>() 
    .HasRequired(lu => lu.PIIUser) 
    .WithOptional(pi => pi.LoyaltyUserDetail); 
} 

che sta dicendo LoyaltyUserDetails PIIUser proprietà è richiesto e PIIUser di La proprietà LoyaltyUserDetail è facoltativa.

si potrebbe cominciare dall'altra estremità:

modelBuilder.Entity<PIIUser>() 
.HasOptional(pi => pi.LoyaltyUserDetail) 
.WithRequired(lu => lu.PIIUser); 

che ora dice di proprietà di PIIUser LoyaltyUserDetail è opzionale ed è richiesto proprietà di LoyaltyUser PIIUser.

È sempre necessario utilizzare il modello HAS/WITH.

HTH e FWIW, le relazioni uno a uno (o uno a zero/uno) sono una delle relazioni più confuse da configurare prima nel codice in modo da non essere soli! :)

+1

Non corrisponde a tale limite l'utente su quale campo FK scegliere? (Penso che lo voglia, ma non mi ha riferito in modo che non lo sappia), perché sembra che voglia avere un campo FK presente nella classe. –

+1

concordato. È una limitazione di come funziona EF. 1: 1 e 1: 0..1 dipendono dalle chiavi primarie. Altrimenti penso che ti stia sforzando di "FK univoco" che non è ancora supportato in EF. :((Sei più un esperto di db ... è corretto ... questo è davvero un FK unico?) E non sarà nel prossimo Ef6 come da: http://entityframework.codeplex.com/ workitem/299 –

+1

Sì, @FransBouma, volevamo utilizzare i campi PIIUserId e LoyaltUserId come chiavi esterne, ma EF ci limita in questa situta come menzionato tu e Julie.Grazie per le risposte –

2

Prova ad aggiungere l'attributo ForeignKey alla proprietà LoyaltyUserDetail:

public class PIIUser 
{ 
    ... 
    public int? LoyaltyUserDetailId { get; set; } 
    [ForeignKey("LoyaltyUserDetailId")] 
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; } 
    ... 
} 

e la proprietà PIIUser:

public class LoyaltyUserDetail 
{ 
    ... 
    public int PIIUserId { get; set; } 
    [ForeignKey("PIIUserId")] 
    public PIIUser PIIUser { get; set; } 
    ... 
} 
+1

Abbiamo provato dati annotazioni prima di tutti questi approcci API fluidi. Ma le annotazioni dei dati non hanno funzionato. se aggiungi annotazioni di dati che hai menzionato sopra, EF lancia questa eccezione => Impossibile determinare la fine principale di un'associazione tra i tipi "LoyaltyUserDetail" e "PIIUser". La fine principale di questa associazione deve essere configurata in modo esplicito utilizzando l'API della relazione fluente o le annotazioni di dati. –

+0

@ İlkayİlknur Cosa succede se si aggiunge l'attributo 'ForeignKey' a una sola estremità della relazione? cioè solo su 'PIIUser' * o *' LoyaltyUserDetail'. –

+0

Ef lancia la stessa eccezione. –

3

ci sono diverse cose sbagliate con il codice.

A 1: 1 rapporto è o: PK < -PK, in cui una parte PK è anche un FK, o PK < -FK + UC, in cui il lato FK è un non-PK e ha un UC. Il tuo codice mostra che hai FK < -FK, come si definiscono entrambi i lati per avere un FK ma questo è sbagliato. Riconosco PIIUser è il lato PK e LoyaltyUserDetail è il lato FK. Ciò significa che PIIUser non ha un campo FK, ma lo fa LoyaltyUserDetail.

Se la relazione 1: 1 è facoltativa, il lato FK deve avere almeno un campo nullable.

p.s.w.g. sopra ha risposto alla tua domanda ma ha commesso un errore che ha anche definito un FK in PIIUser, che è ovviamente sbagliato come ho descritto sopra. Definire quindi il campo FK Null in LoyaltyUserDetail, definire l'attributo in LoyaltyUserDetail per contrassegnarlo come campo FK, ma non specificare un campo FK in PIIUser.

Si ottiene l'eccezione che si descrive sopra al di sotto del post di p.s.w.g., perché nessun lato è il lato PK (fine principale).

EF non è molto buono a 1: 1 in quanto non è in grado di gestire i vincoli univoci. Non sono esperto di codice prima, quindi non so se è in grado di creare un UC o no.

(edit) BTW: A 1: 1 B (FK) significa che c'è solo 1 FK vincolo creato, il bersaglio di B che punta al PK di A, non 2.

17

Basta fare come se si dispone di uno-a-molti tra LoyaltyUserDetail e PIIUser così si mappatura dovrebbe essere

modelBuilder.Entity<LoyaltyUserDetail>() 
     .HasRequired(m => m.PIIUser) 
     .WithMany() 
     .HasForeignKey(c => c.LoyaltyUserDetailId); 

EF dovrebbe creare tutto chiave esterna che serve e basta don't care about WithMany!

+0

Eccellente, questa deve essere la risposta accettata! –

+0

La risposta di Julie Lerman è stata accettata (e dovrebbe essere accettata, IMHO) perché risponde alla domanda e va nei dettagli sul perché è il modo corretto con i dettagli di supporto e la discussione. Copia e incolla le risposte sono certamente disponibili in tutto SO e ne ho utilizzate alcune io stesso, ma come sviluppatori professionisti dovresti essere più interessato a diventare un programmatore migliore, non solo a ottenere il codice da costruire. Detto questo, ho capito bene, anche se un anno dopo. –

0
public class User 
{ 
    public int Id { get; set; } 
    public int? LoyaltyUserId { get; set; } 
    public virtual LoyaltyUser LoyaltyUser { get; set; } 
} 

public class LoyaltyUser 
{ 
    public int Id { get; set; } 
    public virtual User MainUser { get; set; } 
} 

     modelBuilder.Entity<User>() 
      .HasOptional(x => x.LoyaltyUser) 
      .WithOptionalDependent(c => c.MainUser) 
      .WillCascadeOnDelete(false); 

questo risolverà il problema su RIFERIMENTO e chiavi esterne

quando l'aggiornamento o CANCELLAZIONE un record

0

L'unica cosa che è fonte di confusione con le soluzioni di cui sopra è che la chiave primaria è definita come "ID" in entrambe le tabelle e se si dispone di chiave primaria sul nome della tabella non funzionerebbe, ho modificato le classi per illustrare lo stesso, cioè la tabella opzionale non dovrebbe definire la propria chiave primaria, invece dovrebbe usare lo stesso nome chiave dalla tabella principale.

public class PIIUser 
{ 
    // For illustration purpose I have named the PK as PIIUserId instead of Id 
    // public int Id { get; set; } 
    public int PIIUserId { get; set; } 

    public int? LoyaltyUserDetailId { get; set; } 
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; } 
} 

public class LoyaltyUserDetail 
{ 
    // Note: You cannot define a new Primary key separately as it would create one to many relationship 
    // public int LoyaltyUserDetailId { get; set; } 

    // Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table 
    public int PIIUserId { get; set; } 
    public double? AvailablePoints { get; set; } 

    public int PIIUserId { get; set; } 
    public PIIUser PIIUser { get; set; } 
} 

E poi seguita da

modelBuilder.Entity<PIIUser>() 
.HasOptional(pi => pi.LoyaltyUserDetail) 
.WithRequired(lu => lu.PIIUser); 

farebbe il trucco, la soluzione accettata non riesce a spiegare in modo chiaro questo, e mi ha buttato fuori per poche ore per trovare la causa