Wie soll ich Fremdschlüsselbeziehungen mit Code First Entity Framework (4.1) in MVC3 deklarieren?

104

Ich habe nach Ressourcen gesucht, wie Fremdschlüsselbeziehungen und andere Einschränkungen mit Code zuerst EF 4.1 ohne viel Glück deklariert werden können. Grundsätzlich baue ich das Datenmodell in Code und verwende MVC3, um dieses Modell abzufragen. Alles funktioniert über MVC, was großartig ist (ein großes Lob an Microsoft!), Aber jetzt möchte ich, dass es NICHT funktioniert, weil ich Datenmodelleinschränkungen haben muss.

Zum Beispiel habe ich ein Order-Objekt, das eine Menge Eigenschaften hat, die externe Objekte (Tabellen) sind. Im Moment kann ich problemlos einen Auftrag erstellen, ohne jedoch den Fremdschlüssel oder externe Objekte hinzufügen zu können. MVC3 richtet dies problemlos ein.

Mir ist klar, dass ich die Objekte vor dem Speichern einfach selbst in die Controller-Klasse einfügen kann, aber ich möchte, dass der Aufruf von DbContext.SaveChanges () fehlschlägt, wenn die Einschränkungsbeziehungen nicht erfüllt wurden.

NEUE INFORMATIONEN

Daher möchte ich insbesondere, dass eine Ausnahme auftritt, wenn ich versuche, ein Bestellobjekt ohne Angabe eines Kundenobjekts zu speichern. Dies scheint nicht das Verhalten zu sein, wenn ich nur die Objekte zusammenstelle, wie in den meisten Code First EF-Dokumentationen beschrieben.

Letzter Code:

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

Dies ist der Fehler, den ich jetzt beim Zugriff auf die VS-generierte Ansicht für Patient erhalte:

FEHLERMELDUNG

Das ForeignKeyAttribute für die Eigenschaft 'Patient' vom Typ 'PhysicianPortal.Models.Order' ist ungültig. Der Fremdschlüsselname 'Parent' wurde für den abhängigen Typ 'PhysicianPortal.Models.Order' nicht gefunden. Der Name-Wert sollte eine durch Kommas getrennte Liste von Fremdschlüsseleigenschaftsnamen sein.

Grüße,

Guido

Guido Anselmi
quelle

Antworten:

164

Wenn Sie eine OrderKlasse haben, sollte das Hinzufügen einer Eigenschaft, die auf eine andere Klasse in Ihrem Modell verweist, beispielsweise Customerausreichen, um EF wissen zu lassen, dass dort eine Beziehung besteht:

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

    // Some other properties

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

Sie können die FKBeziehung immer explizit festlegen :

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

Der ForeignKeyAttributeKonstruktor verwendet eine Zeichenfolge als Parameter: Wenn Sie sie in eine Fremdschlüsseleigenschaft einfügen, repräsentiert sie den Namen der zugeordneten Navigationseigenschaft. Wenn Sie es in der Navigationseigenschaft platzieren, repräsentiert es den Namen des zugeordneten Fremdschlüssels.

Dies bedeutet, dass das Attribut im Konstruktor enthalten ist, wenn Sie die Position ForeignKeyAttributeauf der CustomerEigenschaft platzieren möchten CustomerID:

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

BEARBEITEN basierend auf dem neuesten Code Sie erhalten diesen Fehler aufgrund dieser Zeile:

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

EF sucht nach einer Eigenschaft, die aufgerufen wird Parent, um sie als Fremdschlüssel-Enforcer zu verwenden. Sie können zwei Dinge tun:

1) Entfernen Sie das ForeignKeyAttributeund ersetzen Sie es durch das RequiredAttribute, um die Beziehung wie erforderlich zu markieren:

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

Das Dekorieren einer Eigenschaft mit dem RequiredAttributehat auch einen schönen Nebeneffekt: Die Beziehung in der Datenbank wird mit erstellt ON DELETE CASCADE.

Ich würde auch empfehlen, die Eigenschaft so virtualzu gestalten, dass Lazy Loading aktiviert wird.

2) Erstellen Sie eine Eigenschaft namens Parent, die als Fremdschlüssel dient. In diesem Fall ist es wahrscheinlich sinnvoller, es zum Beispiel aufzurufen ParentID(Sie müssen den Namen auch in ändern ForeignKeyAttribute):

public int ParentID { get; set; }

Nach meiner Erfahrung in diesem Fall funktioniert es jedoch besser, es umgekehrt zu haben:

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

public virtual Patient Patient { get; set; }
Sergi Papaseit
quelle
Danke Sergi - ich habe einige zusätzliche Informationen in das Blockzitat aufgenommen.
Guido Anselmi
@Guido - Ich habe meine Antwort basierend auf Ihrer neuesten Code-Bearbeitung aktualisiert. Ich hoffe, dies hilft.
Sergi Papaseit
30

Sie können Fremdschlüssel definieren durch:

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

Jetzt ist ParentId eine Fremdschlüsseleigenschaft und definiert die erforderliche Beziehung zwischen dem Kind und dem vorhandenen Elternteil. Wenn Sie das Kind speichern, ohne das Elternteil zu verlassen, wird eine Ausnahme ausgelöst.

Wenn Ihr FK-Eigenschaftsname nicht aus dem Namen der Navigationseigenschaft und dem übergeordneten PK-Namen besteht, müssen Sie entweder die Datenanmerkung ForeignKeyAttribute oder eine fließende API verwenden, um die Beziehung zuzuordnen

Datenanmerkung:

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

Fließende API:

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

Andere Arten von Einschränkungen können durch Datenanmerkungen und Modellvalidierung erzwungen werden .

Bearbeiten:

Sie erhalten eine Ausnahme, wenn Sie nicht festlegen ParentId. Es ist eine erforderliche Eigenschaft (nicht nullbar). Wenn Sie es einfach nicht festlegen, wird höchstwahrscheinlich versucht, einen Standardwert an die Datenbank zu senden. Der Standardwert ist 0. Wenn Sie also keinen Kunden mit der ID = 0 haben, wird eine Ausnahme angezeigt.

Ladislav Mrnka
quelle
Danke Ladislav - ich habe einige zusätzliche Informationen in das Blockzitat aufgenommen.
Guido Anselmi
@ Ladislav. Um diese Einschränkung durchzusetzen, MUSS ich sowohl den Verweis auf Parent als auch einen Verweis auf ParentId haben. Ist das korrekt? Ich werde die obige Klasse oben als Referenz hinzufügen.
Guido Anselmi
@ Guido: Das sind die neuen Informationen. Sie verwenden keine Fremdschlüsseleigenschaften. Alle Ihre Navigationseigenschaften werden standardmäßig als optional behandelt. Verwenden Sie eine fließende Zuordnung, um sie nach Bedarf zuzuordnen.
Ladislav Mrnka
@ Ladislav: Nochmals vielen Dank. Ich schaue mich um, um die Unterschiede zwischen der Verwendung von Datenanmerkungen und der Fluent-API zu verstehen. Ich habe die Änderungen am obigen Code in Übereinstimmung mit dem vorgenommen, was Sie meiner Meinung nach sagen. Muss ich das vor allem tun? Grüße.
Guido Anselmi
Kein ForeignKey-Attribut definiert die Navigationseigenschaft in Bezug auf die Fremdschlüsseleigenschaft oder umgekehrt. Sie haben keine Fremdschlüsseleigenschaften, daher können Sie dieses Attribut nicht verwenden. Versuchen Sie, das Attribut Erforderlich für Ihre Navigationseigenschaften zu verwenden (ich habe es nicht getestet).
Ladislav Mrnka