Chiavi esterne e principali nelle relazioni

Tutte le relazioni uno-a-uno e uno-a-molti sono definite da una chiave esterna sulla fine dipendente che fa riferimento a una chiave primaria o alternativa alla fine dell'entità. Per praticità, questa chiave primaria o alternativa è nota come "chiave principale" per la relazione. Le relazioni molti-a-molti sono costituite da due relazioni uno-a-molti, ognuna delle quali è definita da una chiave esterna che fa riferimento a una chiave principale.

Suggerimento

Il codice seguente è disponibile in ForeignAndPrincipalKeys.cs.

Chiavi esterne

La proprietà o le proprietà che costituiscono la chiave esterna vengono spesso individuate per convenzione. Le proprietà possono anche essere configurate in modo esplicito usando attributi di mapping o con HasForeignKey nell'API di compilazione del modello. HasForeignKey può essere usato con un'espressione lambda. Ad esempio, per una chiave esterna costituita da una singola proprietà:

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

In alternativa, per una chiave esterna composita costituita da più proprietà:

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

Suggerimento

L'uso di espressioni lambda nell'API di compilazione del modello garantisce che l'uso della proprietà sia disponibile per l'analisi del codice e il refactoring e fornisca anche il tipo di proprietà all'API da usare in altri metodi concatenati.

HasForeignKey può anche essere passato il nome della proprietà di chiave esterna come stringa. Ad esempio, per una singola proprietà:

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

In alternativa, per una chiave esterna composita:

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

L'uso di una stringa è utile quando:

  • La proprietà o le proprietà sono private.
  • La proprietà o le proprietà non esistono nel tipo di entità e devono essere create come proprietà shadow.
  • Il nome della proprietà viene calcolato o costruito in base ad alcuni input per il processo di compilazione del modello.

Colonne chiave esterna non nullable

Come descritto in Relazioni facoltative e obbligatorie, il supporto dei valori Null della proprietà di chiave esterna determina se una relazione è facoltativa o obbligatoria. Tuttavia, una proprietà di chiave esterna nullable può essere usata per una relazione obbligatoria usando l'attributo [Required] o chiamando IsRequired nell'API di compilazione del modello. Ad esempio:

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

In alternativa, se la chiave esterna viene individuata per convenzione, è IsRequired possibile usare senza una chiamata a HasForeignKey:

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

Il risultato finale è che la colonna chiave esterna nel database viene resa non nullable anche se la proprietà della chiave esterna è nullable. La stessa operazione può essere ottenuta configurando in modo esplicito la proprietà di chiave esterna stessa in base alle esigenze. Ad esempio:

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

Chiavi esterne ombreggiate

Le proprietà della chiave esterna possono essere create come proprietà shadow. Nel modello ENTITY esiste una proprietà shadow, ma non esiste nel tipo .NET. Ef tiene traccia del valore della proprietà e dello stato internamente.

Le chiavi esterne shadow vengono in genere usate quando si desidera nascondere il concetto relazionale di una chiave esterna dal modello di dominio usato dal codice applicazione/logica di business. Questo codice dell'applicazione modifica quindi completamente la relazione tramite gli spostamenti.

Suggerimento

Se le entità verranno serializzate, ad esempio per l'invio in rete, i valori di chiave esterna possono essere un modo utile per mantenere intatte le informazioni sulla relazione quando le entità non si trovano in un modulo oggetto/grafo. È quindi spesso pragmatico mantenere le proprietà di chiave esterna nel tipo .NET a questo scopo. Le proprietà della chiave esterna possono essere private, che spesso rappresenta un buon compromesso per evitare di esporre la chiave esterna, consentendo al contempo il relativo valore di spostarsi con l'entità.

Le proprietà della chiave esterna shadow vengono spesso create per convenzione. Verrà creata anche una chiave esterna shadow se l'argomento per non corrisponde a HasForeignKey alcuna proprietà .NET. Ad esempio:

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

Per convenzione, una chiave esterna shadow ottiene il tipo dalla chiave principale nella relazione. Questo tipo viene reso nullable a meno che la relazione non venga rilevata come o configurata come richiesto.

È anche possibile creare la proprietà della chiave esterna shadow in modo esplicito, utile per configurare i facet della proprietà. Ad esempio, per rendere la proprietà non nullable:

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

Suggerimento

Per convenzione, le proprietà della chiave esterna ereditano facet come la lunghezza massima e il supporto Unicode dalla chiave principale nella relazione. È quindi raramente necessario configurare in modo esplicito i facet in una proprietà di chiave esterna.

La creazione di una proprietà shadow se il nome specificato non corrisponde ad alcuna proprietà del tipo di entità può essere disabilitato tramite ConfigureWarnings. Ad esempio:

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

Nomi dei vincoli di chiave esterna

Per convenzione, i vincoli di chiave esterna sono denominati FK_<dependent type name>_<principal type name>_<foreign key property name>. Per le chiavi esterne composite, <foreign key property name> diventa un elenco di caratteri di sottolineatura separati da nomi di proprietà di chiave esterna.

Questa operazione può essere modificata nell'API di compilazione del modello usando HasConstraintName. Ad esempio:

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

Suggerimento

Il nome del vincolo non viene usato dal runtime di Entity Framework. Viene usato solo quando si crea uno schema di database usando Migrazioni di EF Core.

Indici per chiavi esterne

Per convenzione, Entity Framework crea un indice di database per la proprietà o le proprietà di una chiave esterna. Per altre informazioni sui tipi di indici creati per convenzione, vedere Convenzioni di compilazione di modelli.

Suggerimento

Le relazioni vengono definite nel modello ef tra i tipi di entità inclusi nel modello. Alcune relazioni potrebbero dover fare riferimento a un tipo di entità nel modello di un contesto diverso, ad esempio quando si usa il modello BoundedContext. In questi casi, le colonne chiave esterna devono essere mappate alle proprietà normali e queste proprietà possono quindi essere modificate manualmente per gestire le modifiche apportate alla relazione.

Chiavi principali

Per convenzione, le chiavi esterne sono vincolate alla chiave primaria alla fine dell'entità della relazione. Tuttavia, è possibile usare una chiave alternativa. Questo risultato viene ottenuto usando HasPrincipalKey nell'API di compilazione del modello. Ad esempio, per una singola chiave esterna di proprietà:

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

Oppure per una chiave esterna composita con più proprietà:

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

HasPrincipalKey può anche essere passato il nome della proprietà chiave alternativa come stringa. Ad esempio, per una singola chiave di proprietà:

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

In alternativa, per una chiave composta:

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

Nota

L'ordine delle proprietà nella chiave principale e esterna deve corrispondere. Si tratta anche dell'ordine in cui la chiave viene definita nello schema del database. Non deve essere uguale all'ordine delle proprietà nel tipo di entità o nelle colonne della tabella.

Non è necessario chiamare HasAlternateKey per definire la chiave alternativa nell'entità principale. Questa operazione viene eseguita automaticamente quando HasPrincipalKey viene usata con proprietà che non sono le proprietà della chiave primaria. Tuttavia, HasAlternateKey può essere usato per configurare ulteriormente la chiave alternativa, ad esempio per impostare il nome del vincolo di database. Per altre informazioni, vedere Chiavi .

Relazioni con entità senza chiave

Ogni relazione deve avere una chiave esterna che fa riferimento a una chiave principale (primaria o alternativa). Ciò significa che un tipo di entità senza chiave non può fungere da fine principale di una relazione, perché non esiste alcuna chiave principale a cui fare riferimento le chiavi esterne.

Suggerimento

Un tipo di entità non può avere una chiave alternativa, ma nessuna chiave primaria. In questo caso, la chiave alternativa (o una delle chiavi alternative, se sono presenti più) deve essere promossa alla chiave primaria.

Tuttavia, i tipi di entità senza chiave possono comunque avere chiavi esterne definite e pertanto possono fungere da fine dipendente di una relazione. Si considerino, ad esempio, questi tipi, in cui Tag non è presente alcuna chiave:

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

Tag può essere configurato alla fine dipendente della relazione:

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

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

Nota

Ef non supporta gli spostamenti che puntano ai tipi di entità senza chiave. Vedere Problema di GitHub n. 30331.

Chiavi esterne nelle relazioni molti-a-molti

Nelle relazioni molti-a-molti, le chiavi esterne vengono definite nel tipo di entità join e mappate ai vincoli di chiave esterna nella tabella join. Tutti gli elementi descritti in precedenza possono essere applicati anche a queste chiavi esterne dell'entità join. Ad esempio, impostando i nomi dei vincoli di database:

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