Come devo dichiarare le relazioni di chiavi esterne utilizzando Code First Entity Framework (4.1) in MVC3?


104

Ho cercato risorse su come dichiarare relazioni di chiavi esterne e altri vincoli utilizzando il codice prima EF 4.1 senza molta fortuna. Fondamentalmente sto costruendo il modello di dati nel codice e usando MVC3 per interrogare quel modello. Tutto funziona tramite MVC, il che è fantastico (complimenti a Microsoft!) Ma ora voglio che NON funzioni perché ho bisogno di vincoli sul modello di dati.

Ad esempio, ho un oggetto Order che ha un sacco di proprietà che sono oggetti esterni (tabelle). In questo momento posso creare un ordine senza problemi, ma senza essere in grado di aggiungere la chiave esterna o oggetti esterni. MVC3 lo imposta senza problemi.

Mi rendo conto che potrei semplicemente aggiungere gli oggetti io stesso nella classe controller prima del salvataggio, ma vorrei che la chiamata a DbContext.SaveChanges () fallisse se le relazioni di vincolo non sono state soddisfatte.

NUOVA INFORMAZIONE

Quindi, in particolare, vorrei che si verificasse un'eccezione quando tento di salvare un oggetto Order senza specificare un oggetto cliente. Questo non sembra essere il comportamento se compongo gli oggetti come descritto nella maggior parte della documentazione di Code First EF.

Ultimo codice:

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

    [ForeignKey( "Parent" )]
    public Patient Patient { get; set; }

    [ForeignKey("CertificationPeriod")]
    public CertificationPeriod CertificationPeriod { get; set; }

    [ForeignKey("Agency")]
    public Agency Agency { get; set; }

    [ForeignKey("Diagnosis")]
    public Diagnosis PrimaryDiagnosis { get; set; }

    [ForeignKey("OrderApprovalStatus")]
    public OrderApprovalStatus ApprovalStatus { get; set; }

    [ForeignKey("User")]
    public User User { get; set; }

    [ForeignKey("User")]
    public User Submitter { get; set; }

    public DateTime ApprovalDate { get; set; }
    public DateTime SubmittedDate { get; set; }
    public Boolean IsDeprecated { get; set; }
}

Questo è l'errore che ottengo ora quando accedo alla vista generata da VS per il paziente:

MESSAGGIO DI ERRORE

ForeignKeyAttribute sulla proprietà "Patient" sul tipo "PhysicianPortal.Models.Order" non è valido. Il nome della chiave esterna "Parent" non è stato trovato nel tipo dipendente "PhysicianPortal.Models.Order". Il valore Name dovrebbe essere un elenco separato da virgole di nomi di proprietà di chiavi esterne.

Saluti,

Guido

Risposte:


164

Se hai una Orderclasse, l'aggiunta di una proprietà che fa riferimento a un'altra classe nel tuo modello, ad esempio, Customerdovrebbe essere sufficiente per far sapere a EF che c'è una relazione lì dentro:

public class Order
{
    public int ID { get; set; }

    // Some other properties

    // Foreign key to customer
    public virtual Customer Customer { get; set; }
}

Puoi sempre impostare la FKrelazione in modo esplicito:

public class Order
{
    public int ID { get; set; }

    // Some other properties

    // Foreign key to customer
    [ForeignKey("Customer")]
    public string CustomerID { get; set; }
    public virtual Customer Customer { get; set; }
}

Il ForeignKeyAttributecostruttore prende una stringa come parametro: se la posizionate su una proprietà di chiave esterna rappresenta il nome della proprietà di navigazione associata. Se lo metti sulla proprietà di navigazione, rappresenta il nome della chiave esterna associata.

Ciò significa che, se si dove posizionare il ForeignKeyAttributesulla Customerproprietà, l'attributo prenderebbe CustomerIDnel costruttore:

public string CustomerID { get; set; }
[ForeignKey("CustomerID")]
public virtual Customer Customer { get; set; }

MODIFICA basata sull'ultimo codice Ottieni quell'errore a causa di questa riga:

[ForeignKey("Parent")]
public Patient Patient { get; set; }

EF cercherà una proprietà chiamata Parentper usarla come programma di applicazione della chiave esterna. Puoi fare 2 cose:

1) Rimuovere il ForeignKeyAttributee sostituirlo con il RequiredAttributeper contrassegnare la relazione come richiesto:

[Required]
public virtual Patient Patient { get; set; }

Decorare una proprietà con RequiredAttributeha anche un piacevole effetto collaterale: la relazione nel database viene creata con ON DELETE CASCADE.

Suggerirei anche di rendere la proprietà virtualper abilitare il caricamento lento.

2) Creare una proprietà chiamata Parentche fungerà da chiave esterna. In quel caso probabilmente ha più senso chiamarlo ad esempio ParentID(dovrai cambiare anche il nome in ForeignKeyAttribute):

public int ParentID { get; set; }

Nella mia esperienza in questo caso, però, funziona meglio fare il contrario:

[ForeignKey("Patient")]
public int ParentID { get; set; }

public virtual Patient Patient { get; set; }

Grazie Sergi - ho aggiunto alcune informazioni aggiuntive nella citazione in blocco.
Guido Anselmi

@Guido - Ho aggiornato la mia risposta in base alla tua ultima modifica del codice, spero che questo aiuti.
Sergi Papaseit

30

Puoi definire la chiave esterna in base a:

public class Parent
{
   public int Id { get; set; }
   public virtual ICollection<Child> Childs { get; set; }
}

public class Child
{
   public int Id { get; set; }
   // This will be recognized as FK by NavigationPropertyNameForeignKeyDiscoveryConvention
   public int ParentId { get; set; } 
   public virtual Parent Parent { get; set; }
}

Ora ParentId è una proprietà di chiave esterna e definisce la relazione richiesta tra il figlio e il genitore esistente. Salvare il bambino senza lasciare il genitore genera un'eccezione.

Se il nome della tua proprietà FK non è costituito dal nome della proprietà di navigazione e dal nome PK principale, devi utilizzare l'annotazione dei dati ForeignKeyAttribute o l'API fluente per mappare la relazione

Annotazione dei dati:

// The name of related navigation property
[ForeignKey("Parent")]
public int ParentId { get; set; }

API fluente:

modelBuilder.Entity<Child>()
            .HasRequired(c => c.Parent)
            .WithMany(p => p.Childs)
            .HasForeignKey(c => c.ParentId);

Altri tipi di vincoli possono essere applicati dalle annotazioni dei dati e dalla convalida del modello .

Modificare:

Otterrai un'eccezione se non imposti ParentId. È una proprietà richiesta (non annullabile). Se semplicemente non lo imposti, molto probabilmente proverà a inviare il valore predefinito al database. Il valore predefinito è 0, quindi se non hai un cliente con Id = 0 otterrai un'eccezione.


Grazie Ladislav - ho aggiunto alcune informazioni aggiuntive nella citazione in blocco.
Guido Anselmi

@Ladislav. Quindi, per applicare questo vincolo, DEVO avere sia il riferimento a Parent che un riferimento a ParentId. È corretto? Aggiungerò la classe attuale sopra come riferimento.
Guido Anselmi

@Guido: questa è la nuova informazione. Non stai utilizzando proprietà di chiave esterna. Tutte le proprietà di navigazione vengono gestite come facoltative per impostazione predefinita. Usa una mappatura fluida per mapparli come richiesto.
Ladislav Mrnka

@Ladislav: grazie ancora. Mi guardo intorno per capire le differenze tra l'utilizzo delle annotazioni dei dati e l'API Fluent. Ho apportato le modifiche al codice sopra in linea con quello che penso tu stia dicendo. È tutto quello che devo fare? Saluti.
Guido Anselmi

Nessun attributo ForeignKey definisce la proprietà di navigazione relativa alla proprietà della chiave esterna o viceversa. Non hai proprietà di chiave esterna quindi non puoi usare quell'attributo. Prova a utilizzare l'attributo obbligatorio nelle proprietà di navigazione (non l'ho testato).
Ladislav Mrnka
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.