Como devo declarar relacionamentos de chave estrangeira usando Code First Entity Framework (4.1) em MVC3?

104

Tenho procurado recursos sobre como declarar relacionamentos de chave estrangeira e outras restrições usando o código EF 4.1 primeiro sem muita sorte. Basicamente, estou construindo o modelo de dados em código e usando MVC3 para consultar esse modelo. Tudo funciona via MVC, o que é ótimo (parabéns para a Microsoft!), Mas agora eu quero que NÃO funcione porque preciso ter restrições de modelo de dados.

Por exemplo, eu tenho um objeto Order que tem uma tonelada de propriedades que são objetos externos (tabelas). No momento posso criar um pedido sem problemas, mas sem ser capaz de adicionar a chave estrangeira ou objetos externos. MVC3 configura isso sem problemas.

Percebo que poderia simplesmente adicionar os objetos sozinho na classe do controlador antes de salvar, mas gostaria que a chamada para DbContext.SaveChanges () falhasse se os relacionamentos de restrição não fossem atendidos.

NOVA INFORMAÇÃO

Portanto, especificamente, gostaria que ocorresse uma exceção quando tento salvar um objeto Order sem especificar um objeto de cliente. Este não parece ser o comportamento se eu apenas compor os objetos conforme descrito na maioria da documentação do Code First EF.

Código mais recente:

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

Este é o erro que recebo agora ao acessar a visualização gerada pelo VS para o paciente:

MENSAGEM DE ERRO

O ForeignKeyAttribute na propriedade 'Paciente' no tipo 'PhysicianPortal.Models.Order' não é válido. O nome da chave estrangeira 'Parent' não foi encontrado no tipo dependente 'PhysicianPortal.Models.Order'. O valor Name deve ser uma lista separada por vírgulas de nomes de propriedades de chave estrangeira.

Saudações,

Guido

Guido Anselmi
fonte

Respostas:

164

Se você tem uma Orderclasse, adicionar uma propriedade que faz referência a outra classe em seu modelo, por exemplo, Customerdeve ser o suficiente para que EF saiba que há um relacionamento lá:

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

    // Some other properties

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

Você sempre pode definir a FKrelação explicitamente:

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

O ForeignKeyAttributeconstrutor recebe uma string como parâmetro: se você colocá-la em uma propriedade de chave estrangeira, ela representa o nome da propriedade de navegação associada. Se você colocá-lo na propriedade de navegação, ele representa o nome da chave estrangeira associada.

O que isso significa é que, se você colocar o ForeignKeyAttributena Customerpropriedade, o atributo levará CustomerIDno construtor:

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

EDITAR com base no código mais recente Você recebe esse erro por causa desta linha:

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

EF irá procurar uma propriedade chamada Parentpara usá-la como o executor de chave estrangeira. Você pode fazer 2 coisas:

1) Remova ForeignKeyAttributee substitua pelo RequiredAttributepara marcar a relação conforme necessário:

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

Decorar uma propriedade com o RequiredAttributetambém tem um efeito colateral interessante: a relação no banco de dados é criada com ON DELETE CASCADE.

Eu também recomendaria tornar a propriedade virtualpara ativar o Lazy Loading.

2) Crie uma propriedade chamada Parentque servirá como uma chave estrangeira. Nesse caso, provavelmente faz mais sentido chamá-lo por exemplo ParentID(você também precisará alterar o nome no ForeignKeyAttribute):

public int ParentID { get; set; }

Na minha experiência, neste caso, embora funcione melhor fazer o contrário:

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

public virtual Patient Patient { get; set; }
Sergi Papaseit
fonte
Obrigado Sergi - Eu adicionei algumas informações adicionais na citação em bloco.
Guido Anselmi,
@Guido - Eu atualizei minha resposta com base em sua última edição de código, espero que isso ajude.
Sergi Papaseit,
30

Você pode definir a chave estrangeira por:

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

Agora ParentId é uma propriedade de chave estrangeira e define a relação necessária entre o filho e o pai existente. Salvar a criança sem deixar os pais lançará uma exceção.

Se o nome da sua propriedade FK não consiste no nome da propriedade de navegação e nome do PK pai, você deve usar a anotação de dados ForeignKeyAttribute ou API fluente para mapear a relação

Anotação de dados:

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

API Fluent:

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

Outros tipos de restrições podem ser impostos por anotações de dados e validação de modelo .

Editar:

Você receberá uma exceção se não definir ParentId. É uma propriedade necessária (não anulável). Se você simplesmente não defini-lo, provavelmente tentará enviar o valor padrão para o banco de dados. O valor padrão é 0, portanto, se você não tiver um cliente com Id = 0, receberá uma exceção.

Ladislav Mrnka
fonte
Obrigado Ladislav - Eu adicionei algumas informações adicionais na citação do bloco.
Guido Anselmi,
@Ladislav. Portanto, para impor essa restrição, PRECISO ter a referência a Parent e a ParentId. Isso é correto? Vou adicionar a classe real acima para referência.
Guido Anselmi
@Guido: Essa é a nova informação. Você não está usando propriedades de chave estrangeira. Todas as suas propriedades de navegação são tratadas como opcionais por padrão. Use o mapeamento fluente para mapeá-los conforme necessário.
Ladislav Mrnka
@Ladislav: Obrigado novamente. Estou procurando entender as diferenças entre o uso de anotações de dados e a API Fluent. Fiz as alterações no código acima de acordo com o que acho que você está dizendo. É tudo o que tenho de fazer acima? Saudações.
Guido Anselmi
Nenhum atributo ForeignKey define propriedade de navegação relacionada à propriedade de chave estrangeira ou vice-versa. Você não tem propriedades de chave estrangeira, portanto não pode usar esse atributo. Tente usar o atributo Required nas propriedades de navegação (não testei).
Ladislav Mrnka