Code First: ¿Asociaciones independientes frente a asociaciones de clave externa?

102

Tengo un debate mental conmigo mismo cada vez que comienzo a trabajar en un nuevo proyecto y estoy diseñando mis POCO. He visto muchos tutoriales / ejemplos de código que parecen favorecer las asociaciones de claves externas :

Asociación de clave externa

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

A diferencia de las asociaciones independientes :

Asociación independiente

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

He trabajado con NHibernate en el pasado y usé asociaciones independientes, que no solo se sienten más OO, sino que también (con carga diferida) tienen la ventaja de darme acceso a todo el objeto Cliente, en lugar de solo su ID. Esto me permite, por ejemplo, recuperar una instancia de Order y luego hacerlo Order.Customer.FirstNamesin tener que hacer una unión explícitamente, lo cual es extremadamente conveniente.

Entonces, para recapitular, mis preguntas son:

  1. ¿Existe alguna desventaja significativa en el uso de asociaciones independientes? y...
  2. Si no hay ninguna, ¿cuál sería la razón para utilizar asociaciones de claves externas?
Daniel Liuzzi
fuente

Respuestas:

106

Si desea aprovechar al máximo ORM, definitivamente utilizará la referencia de entidad:

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

Una vez que genere un modelo de entidad a partir de una base de datos con FK, siempre generará referencias de entidad. Si no desea utilizarlos, debe modificar manualmente el archivo EDMX y agregar propiedades que representen los FK. Al menos este fue el caso en Entity Framework v1, donde solo se permitieron asociaciones independientes.

Entity Framework v4 ofrece un nuevo tipo de asociación denominada Asociación de clave externa. La diferencia más obvia entre la asociación de clave independiente y externa está en la clase Order:

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

Como puede ver, tiene tanto la propiedad FK como la referencia de entidad. Hay más diferencias entre dos tipos de asociaciones:

Asociación independiente

  • Se representa como un objeto separado en ObjectStateManager. ¡Tiene el suyo EntityState!
  • Al crear una asociación, siempre necesita entidades de ambos extremos de la asociación
  • Esta asociación se asigna de la misma forma que la entidad.

Asociación de clave externa

  • No se representa como un objeto separado en ObjectStateManager. Por eso debes seguir algunas reglas especiales.
  • Al crear una asociación, no necesita ambos extremos de la asociación. Es suficiente tener una entidad secundaria y un PK de la entidad principal, pero el valor de PK debe ser único. Por lo tanto, cuando use la asociación de claves externas, también debe asignar ID únicos temporales a las entidades recién generadas que se usan en las relaciones.
  • Esta asociación no está mapeada, sino que define restricciones referenciales.

Si desea utilizar la asociación de clave externa, debe marcar Incluir columnas de clave externa en el modelo en el Asistente de modelo de datos de entidad.

Editar:

Encontré que la diferencia entre estos dos tipos de asociaciones no es muy conocida, así que escribí un artículo corto que cubría esto con más detalles y mi propia opinión al respecto.

Ladislav Mrnka
fuente
Gracias por su respuesta tan perspicaz, y también por el aviso sobre la terminología correcta, que me ayudó a encontrar muchos recursos sobre el tema y los pros y contras de ambas técnicas.
Daniel Liuzzi
1
Acabo de encontrar tu artículo, Ladislav. Lectura muy interesante y un gran recurso para comprender mejor la diferencia entre estos dos enfoques. Salud.
Daniel Liuzzi
1
@GaussZ: Como sé, no hubo ningún cambio en la forma en que se manejan las asociaciones desde EF4 (donde se introdujeron las asociaciones FK).
Ladislav Mrnka
1
Esta y las otras respuestas no parecen afectar la preocupación por el rendimiento. Sin embargo, de acuerdo con la sección 2.2 Factores que afectan el rendimiento de View Generation de un artículo de MSDN, el uso de Independent Association parece aumentar el costo de View Generation sobre las asociaciones de clave externa.
Veverke
1
@LadislavMrnka: ¿puedes comprobar que el enlace al artículo que mencionas anteriormente funciona? No puedo acceder a él.
Veverke
34

Utilice ambos. Y haga que sus referencias de entidad sean virtuales para permitir la carga diferida. Me gusta esto:

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

Esto ahorra búsquedas de bases de datos innecesarias, permite la carga diferida y le permite ver / configurar fácilmente la ID si sabe lo que quiere que sea. Tenga en cuenta que tener ambos no cambia la estructura de su tabla de ninguna manera.

Seth Paulson
fuente
5
Convenido. Esto es lo que terminé haciendo, como sugirió Ladislav. Realmente te ofrece lo mejor de ambos mundos; el objeto completo cuando necesita todas sus propiedades, y su ID cuando solo necesita el PK y no le importa el resto.
Daniel Liuzzi
9

La asociación independiente no funciona bien con la AddOrUpdateque se usa generalmente en el Seedmétodo. Cuando la referencia sea un elemento existente, se volverá a insertar.

// 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);

El resultado es que el cliente existente se volverá a insertar y el nuevo cliente (se volverá a insertar) se asociará con un nuevo pedido.


A menos que usemos la asociación de clave externa y asignemos la identificación.

 // 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);

Tenemos el comportamiento esperado, el cliente existente se asociará con un nuevo pedido.

Yuliam Chandra
fuente
2
Este es un buen hallazgo. Sin embargo, la solución alternativa (y creo que la forma correcta) para adjuntar un cliente al pedido es cargarlo desde el contexto de la base de datos de esta manera: var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; O puede usar el método Seleccionar para cargar el cliente desde el contexto de la base de datos. Esto funciona con asociación independiente.
tala9999
4

Prefiero el enfoque de objetos para evitar búsquedas innecesarias. Los objetos de propiedad se pueden completar con la misma facilidad cuando llama a su método de fábrica para construir la entidad completa (usando un código de devolución de llamada simple para entidades anidadas). No hay desventajas que pueda ver, excepto por el uso de memoria (pero almacenaría en caché sus objetos, ¿verdad?). Por lo tanto, todo lo que está haciendo es sustituir la pila por el montón y obtener una ganancia de rendimiento al no realizar búsquedas. Espero que esto tenga sentido.

CarneyCode
fuente