Jak zadeklarować relacje klucza obcego za pomocą Code First Entity Framework (4.1) w MVC3?

104

Szukałem zasobów dotyczących sposobu deklarowania relacji klucza obcego i innych ograniczeń przy użyciu najpierw kodu EF 4.1 bez powodzenia. Zasadniczo buduję model danych w kodzie i używam MVC3 do zapytania tego modelu. Wszystko działa przez MVC, co jest świetne (brawa dla Microsoft!), Ale teraz chcę, aby NIE działało, ponieważ muszę mieć ograniczenia modelu danych.

Na przykład mam obiekt Order, który ma mnóstwo właściwości, które są obiektami zewnętrznymi (tabelami). W tej chwili mogę utworzyć zamówienie bez problemu, ale bez możliwości dodania klucza obcego lub obiektów zewnętrznych. MVC3 ustawia to bez problemu.

Zdaję sobie sprawę, że przed zapisaniem mógłbym po prostu dodać obiekty do klasy kontrolera, ale chciałbym, aby wywołanie DbContext.SaveChanges () zakończyło się niepowodzeniem, jeśli relacje ograniczeń nie zostały spełnione.

NOWA INFORMACJA

W szczególności chciałbym, aby wystąpił wyjątek, gdy próbuję zapisać obiekt Order bez określania obiektu klienta. Wydaje się, że nie jest to zachowanie, jeśli po prostu skomponuję obiekty zgodnie z opisem w większości dokumentacji Code First EF.

Najnowszy kod:

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

Oto błąd, który otrzymuję podczas uzyskiwania dostępu do widoku wygenerowanego przez VS dla pacjenta:

KOMUNIKAT O BŁĘDZIE

Atrybut ForeignKeyAttribute we właściwości „Patient” typu „PhysicianPortal.Models.Order” jest nieprawidłowy. Nie znaleziono nazwy klucza obcego „Parent” w typie zależnym „PhysicianPortal.Models.Order”. Wartość Nazwa powinna być listą oddzielonych przecinkami nazw właściwości kluczy obcych.

Pozdrowienia,

Guido

Guido Anselmi
źródło

Odpowiedzi:

164

Jeśli masz Orderklasę, dodanie właściwości, która odwołuje się do innej klasy w modelu, na przykład Customerpowinno wystarczyć, aby EF wiedział, że istnieje tam relacja:

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

    // Some other properties

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

Zawsze możesz FKjawnie ustawić relację:

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

ForeignKeyAttributeKonstruktor pobiera ciąg jako parametr: jeśli umieścisz go na własność klucza obcego to reprezentuje nazwę skojarzonej właściwości nawigacji. Jeśli umieścisz go we właściwości nawigacji, reprezentuje nazwę powiązanego klucza obcego.

Oznacza to, że jeśli umieścisz ForeignKeyAttributewe Customerwłaściwości miejsce, atrybut przyjmie CustomerIDkonstruktor:

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

EDYTUJ na podstawie najnowszego kodu Otrzymujesz ten błąd z powodu tego wiersza:

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

EF będzie szukał właściwości wywoływanej w Parentcelu użycia jej jako egzekwowania klucza obcego. Możesz zrobić 2 rzeczy:

1) Usuń ForeignKeyAttributei zastąp go RequiredAttributeznakiem, aby zaznaczyć relację zgodnie z wymaganiami:

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

Dekorowanie właściwości za pomocą RequiredAttributerównież ma ładny efekt uboczny: relacja w bazie danych jest tworzona za pomocą ON DELETE CASCADE.

Poleciłbym również ustawienie właściwości tak, virtualaby umożliwić Lazy Loading.

2) Utwórz właściwość o nazwie, Parentktóra będzie służyć jako klucz obcy. W takim przypadku prawdopodobnie bardziej sensowne będzie na przykład nazywanie go ParentID(musisz również zmienić nazwę w ForeignKeyAttribute):

public int ParentID { get; set; }

Z mojego doświadczenia w tym przypadku wynika, że ​​lepiej jest mieć to na odwrót:

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

public virtual Patient Patient { get; set; }
Sergi Papaseit
źródło
Dzięki Sergi - dodałem dodatkowe informacje w cytacie blokowym.
Guido Anselmi
@Guido - zaktualizowałem moją odpowiedź w oparciu o Twoją ostatnią edycję kodu, mam nadzieję, że to pomoże.
Sergi Papaseit,
30

Możesz zdefiniować klucz obcy przez:

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

Teraz ParentId jest własnością klucza obcego i definiuje wymaganą relację między dzieckiem a istniejącym rodzicem. Uratowanie dziecka bez wychodzenia z rodzica spowoduje zgłoszenie wyjątku.

Jeśli nazwa właściwości FK nie składa się z nazwy właściwości nawigacji i nazwy nadrzędnej PK, musisz użyć adnotacji danych ForeignKeyAttribute lub interfejsu API Fluent, aby zmapować relację

Adnotacja danych:

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

Fluent API:

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

Inne typy ograniczeń mogą być wymuszane przez adnotacje danych i walidację modelu .

Edytować:

Otrzymasz wyjątek, jeśli nie ustawisz ParentId. Jest to właściwość wymagana (nie dopuszcza wartości null). Jeśli tego nie ustawisz, najprawdopodobniej spróbuje wysłać wartość domyślną do bazy danych. Wartość domyślna to 0, więc jeśli nie masz klienta o Id = 0, otrzymasz wyjątek.

Ladislav Mrnka
źródło
Dzięki Ladislav - dodałem dodatkowe informacje w cytacie bloku.
Guido Anselmi
@Ladislav. Aby więc wymusić to ograniczenie, MUSZĘ mieć zarówno odniesienie do ParentId, jak i odniesienie do ParentId. Czy to jest poprawne? Dodam aktualną klasę powyżej w celach informacyjnych.
Guido Anselmi
@Guido: To są nowe informacje. Nie używasz właściwości klucza obcego. Wszystkie właściwości nawigacji są domyślnie traktowane jako opcjonalne. Użyj płynnego mapowania, aby zmapować je zgodnie z wymaganiami.
Ladislav Mrnka
@Ladislav: Jeszcze raz dziękuję. Rozglądam się, aby zrozumieć różnice między używaniem adnotacji danych a interfejsem Fluent API. Wprowadziłem zmiany w powyższym kodzie zgodnie z tym, co myślę, że mówisz. Czy to wszystko, co muszę zrobić? Pozdrowienia.
Guido Anselmi
Żaden atrybut ForeignKey nie definiuje właściwości nawigacji związanej z właściwością klucza obcego i na odwrót. Nie masz właściwości klucza obcego, więc nie możesz użyć tego atrybutu. Spróbuj użyć atrybutu Required we właściwościach nawigacji (nie testowałem tego).
Ladislav Mrnka