Compartir vía


Relaciones, propiedades de navegación y claves externas

En este artículo se proporciona información general sobre cómo Entity Framework administra las relaciones entre entidades. También proporciona algunas instrucciones sobre cómo asignar y manipular relaciones.

Relaciones en EF

En las bases de datos relacionales, las relaciones (también denominadas asociaciones) entre tablas se definen mediante claves externas. Una clave externa (FK) es una columna o combinación de columnas que se utiliza para establecer e imponer un vínculo entre los datos de dos tablas. Por lo general, hay tres tipos de relaciones: uno a uno, uno a varios y varios a varios. En una relación uno a varios, la clave externa se define en la tabla que representa los varios extremos de la relación. La relación de varios a varios implica definir una tercera tabla (denominada unión o tabla de combinación), cuya clave principal se compone de las claves externas de ambas tablas relacionadas. En una relación uno a uno, la clave principal actúa además como una clave externa y no hay ninguna columna de clave externa independiente para ninguna tabla.

En la imagen siguiente se muestran dos tablas que participan en una relación de uno a varios. La tabla Curso es la tabla dependiente porque contiene la columna ID de departamento que la vincula a la tabla Departamento.

Department and Course tables

En Entity Framework, una entidad puede estar relacionada con otras entidades a través de una asociación o relación. Cada relación contiene dos extremos que describen el tipo de entidad y la multiplicidad del tipo (uno, cero o uno o varios) para las dos entidades de esa relación. La relación puede venir gobernada por una restricción referencial, la cual describe qué extremo de la relación constituye un rol principal y cuál es un rol dependiente.

Las propiedades de navegación proporcionan una manera de navegar en una asociación entre dos tipos de entidad. Cada objeto puede tener una propiedad de navegación para cada relación en la que participa. Las propiedades de navegación permiten navegar y administrar las relaciones en ambas direcciones, devolviendo un objeto de referencia (si la multiplicidad es de uno o cero o uno) o bien una colección (si la multiplicidad es de varios). También puede optar por tener navegación unidireccional, en cuyo caso define la propiedad de navegación en solo uno de los tipos que participan en la relación y no en ambos.

Se recomienda incluir en el modelo propiedades que se asignan a claves externas en la base de datos. Con las propiedades de clave externa incluidas, puede crear o cambiar una relación modificando el valor de clave externa sobre un objeto dependiente. Este tipo de asociación se denomina asociación de clave externa. El uso de claves externas es aún más esencial cuando se trabaja con entidades desconectadas. Tenga en cuenta que cuando se trabaja con relaciones 1 a 1 o 1 a 0...1, no hay ninguna columna de clave externa independiente, la propiedad de clave principal actúa como clave externa y siempre se incluye en el modelo.

Cuando las columnas de clave externa no están incluidas en el modelo, la información de la asociación se administra como un objeto independiente. Se realiza un seguimiento de las relaciones a través de referencias de objeto en lugar de propiedades de clave externa. Este tipo de asociación se denomina asociación independiente. La manera más habitual de modificar una asociación independiente consiste en modificar las propiedades de navegación que se generan para cada entidad que participa en la asociación.

Puede decidir entre utilizar uno o ambos tipos de asociaciones en su modelo. Sin embargo, si tiene una relación pura de varios a varios conectada por una tabla de combinación que solo contiene claves externas, EF usará una asociación independiente para administrar dicha relación de varios a varios.   

En la imagen siguiente se muestra un modelo conceptual que se creó con Entity Framework Designer. El modelo contiene dos entidades que participan en una relación uno a varios. Ambas entidades tienen propiedades de navegación. Curso es la entidad dependiente y tiene definida la propiedad de clave externa ID del departamento.

Department and Course tables with navigation properties

El siguiente fragmento de código muestra el mismo modelo que se creó con Code First.

public class Course
{
  public int CourseID { get; set; }
  public string Title { get; set; }
  public int Credits { get; set; }
  public int DepartmentID { get; set; }
  public virtual Department Department { get; set; }
}

public class Department
{
   public Department()
   {
     this.Courses = new HashSet<Course>();
   }  
   public int DepartmentID { get; set; }
   public string Name { get; set; }
   public decimal Budget { get; set; }
   public DateTime StartDate { get; set; }
   public int? Administrator {get ; set; }
   public virtual ICollection<Course> Courses { get; set; }
}

Configuración o asignación de relaciones

En el resto de esta página se explica cómo acceder a los datos y manipularlos mediante relaciones. Para obtener información sobre cómo configurar relaciones en el modelo, consulte las páginas siguientes.

Creación y modificación de relaciones

En una asociación de clave externa, al cambiar la relación, el estado de un objeto dependiente con un estado EntityState.Unchanged cambia a EntityState.Modified. En una relación independiente, al cambiar la relación no se actualiza el estado del objeto dependiente.

En los siguientes ejemplos se muestra cómo utilizar la propiedad de clave externa y la propiedad de navegación para asociar los objetos relacionados. Con las asociaciones de clave externa, puede utilizar ambos métodos para cambiar, crear o modificar relaciones. Con asociaciones independientes, no puede utilizar la propiedad de clave externa.

  • Mediante la asignación de un nuevo valor a una propiedad de clave externa, como en el ejemplo siguiente.

    course.DepartmentID = newCourse.DepartmentID;
    
  • El código siguiente elimina una relación estableciendo la clave externa en null. Tenga en cuenta que la propiedad de clave externa debe admitir un valor null.

    course.DepartmentID = null;
    

    Nota:

    Si la referencia está en el estado agregado (en este ejemplo, el objeto curso), la propiedad de navegación de referencia no se sincronizará con los valores clave de un nuevo objeto hasta que se llame a SaveChanges. La sincronización no se produce porque el contexto del objeto no contiene claves permanentes para objetos agregados hasta que se guardan. Si necesita que los nuevos objetos estén totalmente sincronizados tan pronto como se establece la relación, utilice uno de los métodos siguientes.*

  • Asignando un nuevo objeto a una propiedad de navegación. El siguiente código crea una relación entre un curso y un department. Si los objetos están asociados al contexto, el course se agrega a la colección department.Courses, y la propiedad de clave externa correspondiente en el objeto course queda establecida con el valor de propiedad de la clave del cliente.

    course.Department = department;
    
  • Para eliminar la relación, establezca la propiedad de navegación en null. Si está trabajando con Entity Framework basado en .NET 4.0, el extremo relacionado debe cargarse antes de establecerlo en null. Por ejemplo:

    context.Entry(course).Reference(c => c.Department).Load();
    course.Department = null;
    

    A partir de Entity Framework 5.0, que se basa en .NET 4.5, puede establecer la relación en null sin cargar el extremo relacionado. También puede establecer el valor actual en null mediante el método siguiente.

    context.Entry(course).Reference(c => c.Department).CurrentValue = null;
    
  • Eliminando o agregando un objeto en una colección de entidades. Por ejemplo, puede agregar un objeto de tipo Course a la colección department.Courses. Esta operación crea una relación entre un determinado curso y un determinado department. Si los objetos están asociados al contexto del objeto, la referencia del cliente y la propiedad de clave externa en el objeto curso quedarán establecidos con el department adecuado.

    department.Courses.Add(newCourse);
    
  • Mediante el método ChangeRelationshipState para cambiar el estado de la relación especificada entre dos objetos de entidad. Este método se usa normalmente al trabajar con aplicaciones de N niveles y una asociación independiente (no se puede usar con una asociación de clave externa). Además, para usar este método, debe colocarse en ObjectContext, como se muestra en el ejemplo siguiente.
    En el ejemplo siguiente, hay una relación de varios a varios entre instructores y cursos. Llamar al método ChangeRelationshipState y pasar el parámetro EntityState.Added permite que SchoolContext sepa que se ha agregado una relación entre los dos objetos:

    
    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, instructor, c => c.Instructor, EntityState.Added);
    

    Tenga en cuenta que si va a actualizar (no solo agregar) una relación, debe eliminar la relación anterior después de agregar la nueva:

    ((IObjectContextAdapter)context).ObjectContext.
      ObjectStateManager.
      ChangeRelationshipState(course, oldInstructor, c => c.Instructor, EntityState.Deleted);
    

Sincronización de los cambios entre las claves externas y las propiedades de navegación

Al cambiar la relación de los objetos asociados al contexto utilizando uno de los métodos descritos anteriormente, Entity Framework necesita mantener sincronizadas las claves externas, las referencias y las colecciones. Entity Framework administra automáticamente esta sincronización (también conocida como corrección de relaciones) para las entidades POCO con servidores proxy. Para obtener más información, vea Trabajar con Proxies.

Si usa entidades POCO sin servidores proxy, debe asegurarse de que se llama al método DetectChanges para sincronizar los objetos relacionados en el contexto. Tenga en cuenta que las siguientes API desencadenan automáticamente una llamada DetectChanges.

  • DbSet.Add
  • DbSet.AddRange
  • DbSet.Remove
  • DbSet.RemoveRange
  • DbSet.Find
  • DbSet.Local
  • DbContext.SaveChanges
  • DbSet.Attach
  • DbContext.GetValidationErrors
  • DbContext.Entry
  • DbChangeTracker.Entries
  • Ejecución de una consulta LINQ en un DbSet

En Entity Framework, normalmente se usan propiedades de navegación para cargar entidades relacionadas con la entidad devuelta por la asociación definida. Para obtener más información, vea Cargar objetos relacionados.

Nota:

En una asociación de clave externa, al cargar un extremo relacionado de un objeto dependiente, el objeto relacionado se cargará dependiendo del valor de clave externa del objeto dependiente actualmente en memoria:

    // Get the course where currently DepartmentID = 2.
    Course course = context.Courses.First(c => c.DepartmentID == 2);

    // Use DepartmentID foreign key property
    // to change the association.
    course.DepartmentID = 3;

    // Load the related Department where DepartmentID = 3
    context.Entry(course).Reference(c => c.Department).Load();

En una asociación independiente, se consulta el extremo relacionado de un objeto dependiente de acuerdo con el valor de clave externa actualmente en la base de datos. Sin embargo, si se modificó la relación, y la propiedad de referencia en el objeto dependiente señala a un objeto principal diferente cargado en el contexto del objeto, Entity Framework intentará crear una relación tal como se define en el cliente.

Administrar la simultaneidad

Tanto en las asociaciones independientes como en las de clave externa, las comprobaciones de simultaneidad se basan en las claves de entidad y otras propiedades de entidad que se definen en el modelo. Al usar EF Designer para crear un modelo, establezca el atributo ConcurrencyMode en fijo para especificar que la propiedad debe comprobarse para la simultaneidad. Al usar Code First para definir un modelo, use la anotación ConcurrencyCheck en las propiedades que desea comprobar para la simultaneidad. Al trabajar con Code First, también puede usar la anotación TimeStamp para especificar que la propiedad debe comprobarse para la simultaneidad. Solo puede tener una propiedad timestamp en una clase determinada. Code First asigna esta propiedad a un campo que no acepta valores null en la base de datos.

Se recomienda usar siempre la asociación de claves externas al trabajar con entidades que participan en la comprobación y resolución de simultaneidad.

Vea Administración de conflictos de simultaneidad para más información.

Trabajar con claves superpuestas

Las claves superpuestas son claves compuestas en las que algunas propiedades de la clave también forman parte de otra clave de la entidad. No es posible tener una clave superpuesta en una asociación independiente. Para cambiar una asociación de clave externa que incluya claves superpuestas, recomendamos modificar los valores de clave externa en lugar de utilizar las referencias a objetos.