Как мне объявить отношения внешних ключей с помощью Code First Entity Framework (4.1) в MVC3?

104

Я без особого успеха искал ресурсы о том, как объявлять отношения внешнего ключа и другие ограничения, используя сначала код EF 4.1. В основном я создаю модель данных в коде и использую MVC3 для запроса этой модели. Все работает через MVC, и это здорово (слава Microsoft!), Но теперь я хочу, чтобы он НЕ работал, потому что мне нужны ограничения модели данных.

Например, у меня есть объект Order, который имеет множество свойств, которые являются внешними объектами (таблицами). Прямо сейчас я могу без проблем создать заказ, но без возможности добавления внешнего ключа или внешних объектов. MVC3 устанавливает это без проблем.

Я понимаю, что я мог бы просто добавить объекты в класс контроллера перед сохранением, но я бы хотел, чтобы вызов DbContext.SaveChanges () завершился ошибкой, если отношения ограничений не были соблюдены.

НОВАЯ ИНФОРМАЦИЯ

Поэтому, в частности, я хотел бы, чтобы возникло исключение, когда я пытаюсь сохранить объект Order без указания объекта клиента. Это не похоже на поведение, если я просто составляю объекты, как описано в большей части документации Code First EF.

Последний код:

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

Это ошибка, которую я получаю сейчас при доступе к представлению, сгенерированному VS для пациента:

СООБЩЕНИЕ ОБ ОШИБКЕ

Атрибут ForeignKeyAttribute для свойства "Пациент" типа "PhysicianPortal.Models.Order" недопустим. Имя внешнего ключа Parent не найдено в зависимом типе PhysicianPortal.Models.Order. Значение Name должно быть списком имен свойств внешнего ключа, разделенных запятыми.

С Уважением,

Гвидо

Гвидо Ансельми
источник

Ответы:

164

Если у вас есть Orderкласс, добавления свойства, которое ссылается на другой класс в вашей модели, например, Customerдолжно быть достаточно, чтобы EF узнал, что там есть связь:

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

    // Some other properties

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

Вы всегда можете установить FKотношение явно:

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

ForeignKeyAttributeКонструктор принимает строку в качестве параметра: если вы поместите его на ключевую собственности иностранной представляет имя соответствующего свойства навигации. Если вы поместите его в свойство навигации, он представляет имя связанного внешнего ключа.

Это означает, что если вы поместите свойство ForeignKeyAttributeв Customerсвойство, атрибут примет CustomerIDконструктор:

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

ИЗМЕНИТЬ на основе последнего кода. Вы получаете эту ошибку из-за этой строки:

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

EF будет искать свойство, вызываемое, Parentчтобы использовать его в качестве средства принудительного выполнения внешнего ключа. Вы можете сделать 2 вещи:

1) Удалите ForeignKeyAttributeи замените его, RequiredAttributeчтобы обозначить требуемую связь:

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

Украшение свойства с помощью RequiredAttributeтакже имеет приятный побочный эффект: отношение в базе данных создается с помощью ON DELETE CASCADE.

Я также рекомендовал бы сделать это свойство virtualдля включения отложенной загрузки.

2) Создайте свойство с именем, Parentкоторое будет служить внешним ключом. В этом случае, вероятно, имеет смысл назвать его, например ParentID(вам также нужно будет изменить имя в ForeignKeyAttribute):

public int ParentID { get; set; }

По моему опыту, в этом случае лучше сделать наоборот:

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

public virtual Patient Patient { get; set; }
Серги Папасейт
источник
Спасибо, Серги, я добавил дополнительную информацию в цитату блока.
Гвидо Ансельми
@Guido - Я обновил свой ответ на основе вашего последнего редактирования кода, надеюсь, это поможет.
Sergi Papaseit
30

Вы можете определить внешний ключ:

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

Теперь ParentId является свойством внешнего ключа и определяет требуемую связь между дочерним и существующим родителем. Сохранение ребенка без выхода из родительского вызовет исключение.

Если ваше имя свойства FK не состоит из имени свойства навигации и имени родительского PK, вы должны использовать аннотацию данных ForeignKeyAttribute или свободный API для сопоставления отношения

Аннотация к данным:

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

Свободный API:

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

Другие типы ограничений могут быть реализованы с помощью аннотаций данных и проверки модели .

Редактировать:

Вы получите исключение, если не установите ParentId. Это обязательное свойство (не допускающее значения NULL). Если вы просто не установите его, он, скорее всего, попытается отправить значение по умолчанию в базу данных. Значение по умолчанию - 0, поэтому, если у вас нет клиента с Id = 0, вы получите исключение.

Ладислав Мрнка
источник
Спасибо, Ладислав - я добавил дополнительную информацию в цитату блока.
Гвидо Ансельми
@ Ладислав. Итак, чтобы обеспечить соблюдение этого ограничения, я ДОЛЖЕН иметь как ссылку на Parent, так и ссылку на ParentId. Это правильно? Я добавлю фактический класс выше для справки.
Гвидо Ансельми
@Guido: Это новая информация. Вы не используете свойства внешнего ключа. Все ваши свойства навигации по умолчанию обрабатываются как необязательные. Используйте быстрое отображение, чтобы сопоставить их по мере необходимости.
Ладислав Мрнка
@Ladislav: Еще раз спасибо. Я смотрю вокруг, чтобы понять разницу между использованием аннотаций к данным и Fluent API. Я внес изменения в приведенный выше код в соответствии с тем, что, как мне кажется, вы говорите. Все, что я должен сделать? С уважением.
Гвидо Ансельми
Атрибут ForeignKey не определяет свойство навигации, относящееся к свойству внешнего ключа, или наоборот. У вас нет свойств внешнего ключа, поэтому вы не можете использовать этот атрибут. Попробуйте использовать обязательный атрибут в своих свойствах навигации (я его не тестировал).
Ладислав Мрнка