Claves externas y principales en relaciones

Todas las relaciones uno a uno y uno a varios se definen mediante una clave externa en el extremo dependiente que hace referencia a una clave principal o alternativa en el extremo principal. Por comodidad, esta clave principal o alternativa se conoce como "clave principal" para la relación. Las relaciones de varios a varios se componen de dos relaciones de uno a varios, cada una de las cuales se define mediante una clave externa que hace referencia a una clave principal.

Sugerencia

El código siguiente se puede encontrar en el archivo ForeignAndPrincipalKeys.cs.

Claves externas

La propiedad o las propiedades que componen la clave externa a menudo se detectan por convención. Las propiedades también se pueden configurar explícitamente mediante atributos de asignación o con el elemento HasForeignKey en la API de creación de modelos. HasForeignKey se puede usar con una expresión lambda. Por ejemplo, para una clave externa formada por una sola propiedad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.ContainingBlogId);
}

O bien, para una clave externa compuesta formada por más de una propiedad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => new { e.ContainingBlogId1, e.ContainingBlogId2 });
}

Sugerencia

El uso de expresiones lambda en la API de creación de modelos garantiza que el uso de la propiedad esté disponible para el análisis y la refactorización de código, y también proporciona el tipo de propiedad a la API para su uso en métodos encadenados adicionales.

También se puede pasar a HasForeignKey el nombre de la propiedad de clave externa como una cadena. Por ejemplo, para una sola propiedad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId");
}

O bien, para una clave externa compuesta:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("ContainingBlogId1", "ContainingBlogId2");
}

El uso de una cadena es útil cuando:

  • La propiedad o las propiedades son privadas.
  • La propiedad o las propiedades no existen en el tipo de entidad y se deben crear como propiedades reemplazadas.
  • El nombre de la propiedad se calcula o construye en función de alguna entrada del proceso de creación del modelo.

Columnas de clave externa que no aceptan valores NULL

Como se describe en Relaciones opcionales y obligatorias, la nulabilidad de la propiedad de clave externa determina si una relación es opcional u obligatoria. Sin embargo, se puede usar una propiedad de clave externa que admite un valor NULL para una relación obligatoria mediante el atributo [Required] o mediante una llamada a IsRequired en la API de creación de modelos. Por ejemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

O bien, si la clave externa se detecta por convención, se puede usar IsRequired sin una llamada a HasForeignKey:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .IsRequired();
}

El resultado final de esto es que la columna de clave externa de la base de datos no acepta valores NULL incluso si la propiedad de clave externa admite un valor NULL. Puede lograr lo mismo si configura explícitamente la propia propiedad de clave externa según sea necesario. Por ejemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property(e => e.BlogId)
        .IsRequired();
}

Claves externas reemplazadas

Las propiedades de clave externa se pueden crear como propiedades reemplazadas. Una propiedad reemplazada existe en el modelo de EF, pero no existe en el tipo de .NET. EF realiza un seguimiento interno del valor de la propiedad y del estado.

Las claves externas reemplazadas se suelen usar cuando hay un deseo de ocultar el concepto relacional de una clave externa del modelo de dominio utilizado por el código de la aplicación o la lógica de negocios. A continuación, este código de aplicación manipula la relación por completo mediante navegaciones.

Sugerencia

Si las entidades se van a serializar, por ejemplo, para enviarse a través de una conexión, los valores de clave externa pueden ser una manera útil de mantener intacta la información de la relación cuando las entidades no están en forma de objeto o grafo. Por lo tanto, suele ser pragmático mantener las propiedades de clave externa en el tipo de .NET para este fin. Las propiedades de clave externa pueden ser privadas, lo que suele ser un buen compromiso para evitar exponer la clave externa, al tiempo que permite que su valor viaje con la entidad.

Las propiedades de clave externa reemplazadas suelen ser creadas por convención. También se creará una clave externa reemplazada si el argumento para HasForeignKey no coincide con ninguna propiedad de .NET. Por ejemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Por convención, una clave externa reemplazada obtiene su tipo de la clave principal de la relación. Este tipo se convierte en que admite un valor NULL a menos que la relación se detecte como o se configure según sea necesario.

La propiedad de clave externa reemplazada también se puede crear explícitamente, lo que resulta útil para configurar facetas de la propiedad. Por ejemplo, para que la propiedad no acepte valores NULL:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .Property<string>("MyBlogId")
        .IsRequired();

    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("MyBlogId");
}

Sugerencia

Por convención, las propiedades de clave externa heredan facetas como la longitud máxima y la compatibilidad con Unicode de la clave principal de la relación. Por lo tanto, rara vez es necesario configurar explícitamente facetas en una propiedad de clave externa.

La creación de una propiedad reemplazada si el nombre especificado no coincide con ninguna propiedad del tipo de entidad se puede deshabilitar mediante ConfigureWarnings. Por ejemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.ConfigureWarnings(b => b.Throw(CoreEventId.ShadowPropertyCreated));

Nombres de restricciones de clave externa

Por convención, las restricciones de clave externa se llaman FK_<dependent type name>_<principal type name>_<foreign key property name>. En el caso de las claves externas compuestas, <foreign key property name> se convierte en una lista de nombres de propiedad de clave externa separados por caracteres de subrayado.

Esto se puede cambiar en la API de creación de modelos mediante HasConstraintName. Por ejemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .HasConstraintName("My_BlogId_Constraint");
}

Sugerencia

El entorno de ejecución de EF no usa el nombre de la restricción. Solo se usa al crear un esquema de base de datos mediante migraciones de EF Core.

Índices para claves externas

Por convención, EF crea un índice de base de datos para la propiedad o las propiedades de una clave externa. Consulte Convenciones de creación de modelos para obtener más información sobre los tipos de índices creados por convención.

Sugerencia

Las relaciones se definen en el modelo de EF entre los tipos de entidad incluidos en ese modelo. Es posible que algunas relaciones necesiten hacer referencia a un tipo de entidad en el modelo de un contexto diferente, por ejemplo, al usar el patrón BoundedContext. En esta situación, las columnas de clave externa se deben asignar a propiedades normales y, a continuación, estas propiedades se pueden manipular manualmente para controlar los cambios en la relación.

Claves principales

Por convención, las claves externas están restringidas a la clave principal del extremo principal de la relación. Sin embargo, se puede usar una clave alternativa en su lugar. Esto se logra mediante HasPrincipalKey en la API de creación de modelos. Por ejemplo, para una clave externa de una sola propiedad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

O bien, para una clave externa compuesta con varias propiedades:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => new { e.AlternateId1, e.AlternateId2 });
}

También se puede pasar a HasPrincipalKey el nombre de la propiedad de clave alternativa como una cadena. Por ejemplo, para una clave de una sola propiedad:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId");
}

O bien, para una clave compuesta:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey("AlternateId1", "AlternateId2");
}

Nota

El orden de las propiedades de la clave principal y la externa deben coincidir. Este es también el orden en que la clave se define en el esquema de la base de datos. No es necesario que sea el mismo que el orden de las propiedades del tipo de entidad o las columnas de la tabla.

No es necesario llamar a HasAlternateKey para definir la clave alternativa en la entidad principal; esto se hace automáticamente cuando se usa HasPrincipalKey con propiedades que no son las propiedades de la clave principal. Sin embargo, se puede usar HasAlternateKey para configurar aún más la clave alternativa, como para establecer su nombre de restricción de base de datos. Consulte Claves para obtener más información.

Relaciones con entidades sin clave

Cada relación debe tener una clave externa que haga referencia a una clave principal (principal o alternativa). Esto significa que un tipo de entidad sin clave no puede actuar como el extremo principal de una relación, ya que no hay ninguna clave principal para que hagan referencia a ella las claves externas.

Sugerencia

Un tipo de entidad no puede tener una clave alternativa y no tener ninguna clave principal. En este caso, la clave alternativa (o una de las claves alternativas, si hay varias) se debe promover a clave principal.

Sin embargo, los tipos de entidad sin clave todavía pueden tener claves externas definidas y, por tanto, pueden actuar como el extremo dependiente de una relación. Por ejemplo, tenga en cuenta estos tipos, donde Tag no tiene ninguna clave:

public class Tag
{
    public string Text { get; set; } = null!;
    public int PostId { get; set; }
    public Post Post { get; set; } = null!;
}

public class Post
{
    public int Id { get; set; }
}

Se puede configurar Tag en el extremo dependiente de la relación:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Tag>()
        .HasNoKey();

    modelBuilder.Entity<Post>()
        .HasMany<Tag>()
        .WithOne(e => e.Post);
}

Nota

EF no admite navegaciones que apuntan a tipos de entidad sin clave. Consulte Problema de GitHub n.º 30331.

Claves externas en relaciones de varios a varios

En las relaciones de varios a varios, las claves externas se definen en el tipo de entidad de combinación y se asignan a restricciones de clave externa en la tabla de combinación. Todo lo descrito anteriormente también se puede aplicar a estas claves externas de entidad de combinación. Por ejemplo, establecer los nombres de restricción de base de datos:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(e => e.Tags)
        .WithMany(e => e.Posts)
        .UsingEntity(
            l => l.HasOne(typeof(Tag)).WithMany().HasConstraintName("TagForeignKey_Constraint"),
            r => r.HasOne(typeof(Post)).WithMany().HasConstraintName("PostForeignKey_Constraint"));
}