Codifique primeiro: associações independentes vs. associações de chave estrangeira?

102

Tenho um debate mental comigo mesmo toda vez que começo a trabalhar em um novo projeto e estou criando meus POCOs. Tenho visto muitos tutoriais / exemplos de código que parecem favorecer associações de chave estrangeira :

Associação de chave estrangeira

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; } // <-- Customer ID
    ...
}

Ao contrário de associações independentes :

Associação independente

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Já trabalhei com o NHibernate no passado e usei associações independentes, que não só parecem mais OO, mas também (com carregamento lento) têm a vantagem de me dar acesso a todo o objeto Cliente, em vez de apenas seu ID. Isso me permite, por exemplo, recuperar uma instância Order e, em seguida, fazer Order.Customer.FirstNamesem ter que fazer uma junção explicitamente, o que é extremamente conveniente.

Então, para recapitular, minhas perguntas são:

  1. Existem desvantagens significativas no uso de associações independentes? e...
  2. Se não houver nenhuma, qual seria a razão de usar associações de chave estrangeira?
Daniel Liuzzi
fonte

Respostas:

106

Se você quiser aproveitar ao máximo o ORM, com certeza usará a referência de entidade:

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Depois de gerar um modelo de entidade a partir de um banco de dados com FKs, ele sempre gerará referências de entidade. Se você não quiser usá-los, você deve modificar manualmente o arquivo EDMX e adicionar propriedades que representam FKs. Pelo menos esse era o caso no Entity Framework v1, onde apenas associações independentes eram permitidas.

O Entity Framework v4 oferece um novo tipo de associação chamada associação de chave estrangeira. A diferença mais óbvia entre a associação de chave independente e estrangeira está na classe Order:

public class Order
{
    public int ID { get; set; }
    public int CustomerId { get; set; }  // <-- Customer ID
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Como você pode ver, você tem uma propriedade FK e uma referência de entidade. Existem mais diferenças entre dois tipos de associações:

Associação independente

  • É representado como um objeto separado em ObjectStateManager. Tem seu próprio EntityState!
  • Ao construir uma associação, você sempre precisa de direitos de ambas as extremidades da associação
  • Essa associação é mapeada da mesma forma que a entidade.

Associação de chave estrangeira

  • Não é representado como um objeto separado em ObjectStateManager. Por isso você deve seguir algumas regras especiais.
  • Ao construir uma associação, você não precisa dos dois extremos da associação. É suficiente ter uma entidade filha e PK da entidade pai, mas o valor de PK deve ser único. Portanto, ao usar a associação de chaves estrangeiras, você também deve atribuir IDs exclusivos temporários a entidades recém-geradas usadas nas relações.
  • Essa associação não é mapeada, mas, em vez disso, define restrições referenciais.

Se quiser usar a associação de chave estrangeira, você deve marcar Incluir colunas de chave estrangeira no modelo no Assistente de modelo de dados de entidade.

Editar:

Descobri que a diferença entre esses dois tipos de associações não é muito conhecida, então escrevi um pequeno artigo cobrindo isso com mais detalhes e minha própria opinião sobre isso.

Ladislav Mrnka
fonte
Obrigado pela sua resposta muito perspicaz e também pelos avisos sobre a terminologia correta, que me ajudou a encontrar muitos recursos sobre o assunto e os prós / contras de ambas as técnicas.
Daniel Liuzzi
1
Acabei de ler seu artigo, Ladislav. Leitura muito interessante e excelente recurso para entender melhor a diferença entre essas duas abordagens. Felicidades.
Daniel Liuzzi
1
@GaussZ: Como eu sei, não houve nenhuma mudança na forma como as associações são tratadas desde o EF4 (onde as associações FK foram introduzidas).
Ladislav Mrnka
1
Esta e as outras respostas não parecem afetar a preocupação com o desempenho. No entanto, de acordo com a seção 2.2 Fatores que afetam o desempenho do View Generation de um artigo do MSDN, o uso da Independent Association parece aumentar o custo da View Generation em relação às associações de chave estrangeira.
Veverke
1
@LadislavMrnka: você pode verificar se o link para o artigo que você mencionou acima está funcionando? Eu não consigo acessar.
Veverke
34

Use ambos. E torne suas referências de entidade virtuais para permitir o carregamento lento. Como isso:

public class Order
{
  public int ID { get; set; }
  public int CustomerID { get; set; }
  public virtual Customer Customer { get; set; } // <-- Customer object
  ...
}

Isso economiza em pesquisas desnecessárias no banco de dados, permite o carregamento lento e permite que você veja / defina o ID facilmente se souber o que deseja que seja. Observe que ter ambos não altera a estrutura da tabela de forma alguma.

Seth Paulson
fonte
5
Acordado. Foi o que acabei fazendo, como sugeriu Ladislav. Isso realmente oferece o melhor dos dois mundos; o objeto inteiro quando você precisa de todas as suas propriedades, e seu ID quando você precisa apenas do PK e não se importa com o resto.
Daniel Liuzzi
9

A associação independente não funciona bem com o AddOrUpdateque geralmente é usado no Seedmétodo. Quando a referência for um item existente, ele será reinserido.

// Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, Customer = customer };
db.Set<Order>().AddOrUpdate(order);

O resultado é que o cliente existente será reinserido e o novo (reinserido) cliente será associado ao novo pedido.


A menos que usemos a associação de chave estrangeira e atribuamos o id.

 // Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, CustomerId = customer.Id };
db.Set<Order>().AddOrUpdate(order);

Temos o comportamento esperado, o cliente existente será associado a um novo pedido.

Yuliam Chandra
fonte
2
Este é um bom achado. No entanto, a solução alternativa (e acho que a maneira certa) para anexar um cliente ao pedido é carregá-lo do contexto do banco de dados como este: var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; Ou você pode usar o método Select para carregar o cliente do contexto do banco de dados. Isso funciona com associação independente.
tala9999
4

Sou a favor da abordagem do objeto para evitar pesquisas desnecessárias. Os objetos de propriedade podem ser facilmente preenchidos quando você chama seu método de fábrica para construir a entidade inteira (usando código de retorno de chamada simples para entidades aninhadas). Não há desvantagens que eu possa ver, exceto pelo uso de memória (mas você armazenaria seus objetos em cache, certo?). Portanto, tudo o que você está fazendo é substituir o heap pela pilha e obter um ganho de desempenho por não realizar pesquisas. Espero que isto faça sentido.

CarneyCode
fonte