Condividi tramite


Chiavi esterne e principali nelle relazioni

Tutte le relazioni uno-a-uno e uno-a-molti sono definite da una chiave esterna sul lato dipendente che fa riferimento a una chiave primaria o alternativa sul lato principale. 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 si può anche passare il nome della proprietà della 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 entità e devono essere create come proprietà ombra.
  • Il nome della proprietà viene calcolato o costruito in base ad alcuni input per il processo di compilazione del modello.

Colonne di chiave esterna non annullabili

Come descritto in Relazioni opzionali e obbligatorie, la nullabilità della proprietà di chiave esterna determina se una relazione è opzionale 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 creazione del modello. Per 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 annullabile anche se la proprietà della chiave esterna è annullabile. La stessa operazione può essere ottenuta configurando in modo esplicito la proprietà di chiave esterna stessa in base alle esigenze. Per 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à ombra. Nel modello EF esiste una proprietà shadow, ma non esiste nel tipo .NET. EF tiene traccia del valore delle proprietà e dello stato a livello interno.

Le chiavi esterne nascoste vengono in genere usate quando si desidera nascondere il concetto relazionale di chiave esterna dal modello di dominio utilizzato dal codice dell'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, il che spesso rappresenta un compromesso valido per evitare l'esposizione della chiave esterna, pur consentendo al suo valore di accompagnare l'entità.

Le proprietà della chiave esterna shadow vengono spesso create per convenzione. Verrà creata anche una chiave esterna temporanea se l'argomento a HasForeignKey non corrisponde a nessuna proprietà .NET. Per 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 ombra assume 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 esplicitamente la proprietà della chiave esterna shadow, il che è utile per configurare gli aspetti della proprietà. Ad esempio, per rendere la proprietà non annullabile:

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 straniera ereditano aspetti come la lunghezza massima e il supporto Unicode dalla chiave principale nella relazione. È quindi raramente necessario dover configurare in modo esplicito gli aspetti su una proprietà di chiave esterna.

Se il nome specificato non corrisponde a nessuna proprietà del tipo di entità, la creazione di una proprietà shadow può essere disabilitata tramite ConfigureWarnings. Per 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 nomi di proprietà di chiavi esterne separati da un underscore.

Questa operazione può essere modificata nell'API di compilazione del modello usando HasConstraintName. Per 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 sul lato principale 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");
}

Annotazioni

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 all'estremità dipendente del rapporto.

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

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

Annotazioni

EF non supporta le associazioni che fanno riferimento a 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à di associazione. 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"));
}