Condividi tramite


Relazioni uno-a-molti

Le relazioni uno-a-molti vengono usate quando una singola entità è associata a un numero qualsiasi di altre entità. Ad esempio, un Blog oggetto può avere molti associati Posts, ma ognuno Post è associato a un Blogsolo oggetto .

Questo documento è strutturato in molti esempi. Gli esempi iniziano con i casi comuni, che introducono anche concetti. Negli esempi successivi vengono illustrati i tipi di configurazione meno comuni. Un buon approccio consiste nel comprendere i primi esempi e i concetti, quindi passare agli esempi successivi in base alle esigenze specifiche. In base a questo approccio, si inizierà con semplici relazioni "obbligatorie" e "facoltative" uno-a-molti.

Suggerimento

Il codice per tutti gli esempi seguenti è disponibile in OneToMany.cs.

Obbligatorio uno-a-molti

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Una relazione uno-a-molti è costituita da:

  • Una o più proprietà chiave primaria o alternativa nell'entità principale, ovvero la fine "uno" della relazione. Ad esempio: Blog.Id.
  • Una o più proprietà di chiave esterna nell'entità dipendente, ovvero la fine "molti" della relazione. Ad esempio: Post.BlogId.
  • Facoltativamente, una navigazione di raccolta sull'entità principale che fa riferimento alle entità dipendenti. Ad esempio: Blog.Posts.
  • Facoltativamente, uno spostamento di riferimento sull'entità dipendente che fa riferimento all'entità principale. Ad esempio: Post.Blog.

Quindi, per la relazione in questo esempio:

  • La proprietà Post.BlogId della chiave esterna non è nullable. Ciò rende la relazione "obbligatoria" perché ogni dipendente () deve essere correlato a un'entità (PostBlog), perché la relativa proprietà di chiave esterna deve essere impostata su un valore.
  • Entrambe le entità hanno spostamenti che puntano all'entità o alle entità correlate dall'altra parte della relazione.

Nota

Una relazione obbligatoria garantisce che ogni entità dipendente debba essere associata a un'entità principale. Tuttavia, un'entità principale può esistere sempre senza entità dipendenti. Ovvero, una relazione obbligatoria non indica che ci sarà sempre almeno un'entità dipendente. Non esiste alcun modo nel modello di Entity Framework e non esiste un modo standard in un database relazionale, per garantire che un'entità sia associata a un determinato numero di dipendenti. Se necessario, deve essere implementato nella logica dell'applicazione (business). Per altre informazioni, vedere Spostamenti necessari.

Suggerimento

Una relazione con due spostamenti, uno dipendente dall'entità e un inverso da entità a dipendente, è noto come relazione bidirezionale.

Questa relazione viene individuata per convenzione. Ovvero:

  • Blog viene individuato come entità nella relazione e Post viene individuato come dipendente.
  • Post.BlogId viene individuato come chiave esterna del dipendente che fa riferimento alla Blog.Id chiave primaria dell'entità. La relazione viene individuata come richiesto perché Post.BlogId non è nullable.
  • Blog.Posts viene individuato come struttura di spostamento della raccolta.
  • Post.Blog viene individuato come navigazione di riferimento.

Importante

Quando si usano tipi di riferimento nullable C#, lo spostamento di riferimento deve essere nullable se la proprietà della chiave esterna è nullable. Se la proprietà di chiave esterna non è nullable, lo spostamento di riferimento può essere nullable o no. In questo caso, Post.BlogId è non nullable ed Post.Blog è anche non nullable. Il = null!; costrutto viene usato per contrassegnarlo come intenzionale per il compilatore C#, poiché EF imposta in genere l'istanza Blog e non può essere Null per una relazione completamente caricata. Per altre informazioni, vedere Uso dei tipi riferimento nullable.

Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

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

Nell'esempio precedente, la configurazione delle relazioni inizia con HasMany sul tipo di entità principale (Blog) e quindi segue questa operazione con WithOne. Come per tutte le relazioni, è esattamente equivalente a iniziare con il tipo di entità dipendente (Post) e usare HasOne seguito da WithMany. Ad esempio:

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

Nessuna di queste opzioni è migliore dell'altra; entrambi comportano esattamente la stessa configurazione.

Suggerimento

Non è mai necessario configurare una relazione due volte, una volta a partire dall'entità e quindi ricominciare dall'oggetto dipendente. Inoltre, il tentativo di configurare l'entità di sicurezza e le metà dipendenti di una relazione separatamente in genere non funziona. Scegliere di configurare ogni relazione da un'estremità o dall'altra e quindi scrivere il codice di configurazione una sola volta.

Facoltativo uno-a-molti

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Si tratta dello stesso esempio precedente, ad eccezione del fatto che la proprietà della chiave esterna e la navigazione all'entità sono ora nullable. Ciò rende la relazione "facoltativa" perché un dipendente (Post) può esistere senza essere correlato a un'entità (Blog).

Importante

Quando si usano tipi di riferimento nullable C#, lo spostamento di riferimento deve essere nullable se la proprietà della chiave esterna è nullable. In questo caso, Post.BlogId è nullable, quindi Post.Blog deve essere anche nullable. Per altre informazioni, vedere Uso dei tipi riferimento nullable.

Come in precedenza, questa relazione viene individuata per convenzione. Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

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

Obbligatorio uno-a-molti con chiave esterna ombreggiatura

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

In alcuni casi, potrebbe non essere necessaria una proprietà di chiave esterna nel modello, poiché le chiavi esterne sono un dettaglio del modo in cui la relazione viene rappresentata nel database, che non è necessaria quando si usa la relazione in modo puramente orientato agli oggetti. Tuttavia, se le entità verranno serializzate, ad esempio per inviare 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. È 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à.

Seguendo i due esempi precedenti, in questo esempio viene rimossa la proprietà di chiave esterna dal tipo di entità dipendente. Ef crea quindi una proprietà di chiave esterna shadow denominata BlogId di tipo int.

Un punto importante da notare è che vengono usati tipi di riferimento nullable in C#, quindi viene usato il supporto dei valori Null della struttura di spostamento di riferimento per determinare se la proprietà della chiave esterna è nullable e quindi se la relazione è facoltativa o obbligatoria. Se i tipi riferimento nullable non vengono usati, la proprietà della chiave esterna shadow sarà nullable per impostazione predefinita, rendendo la relazione facoltativa per impostazione predefinita. In questo caso, usare IsRequired per forzare la proprietà della chiave esterna shadow in modo che non sia nullable e rendere necessaria la relazione.

Come in precedenza, questa relazione viene individuata per convenzione. Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

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

Facoltativo uno-a-molti con chiave esterna ombreggiatura

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

Come nell'esempio precedente, la proprietà di chiave esterna è stata rimossa dal tipo di entità dipendente. Ef crea quindi una proprietà di chiave esterna shadow denominata BlogId di tipo int?. A differenza dell'esempio precedente, questa volta la proprietà di chiave esterna viene creata come nullable perché vengono usati tipi di riferimento nullable C# e lo spostamento sul tipo di entità dipendente è nullable. In questo modo la relazione è facoltativa.

Quando i tipi di riferimento nullable C# non vengono usati, anche la proprietà della chiave esterna verrà creata come nullable per impostazione predefinita. Ciò significa che le relazioni con le proprietà shadow create automaticamente sono facoltative per impostazione predefinita.

Come in precedenza, questa relazione viene individuata per convenzione. Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

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

Uno-a-molti senza spostarsi all'entità di sicurezza

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Per questo esempio, la proprietà di chiave esterna è stata nuovamente introdotta, ma la navigazione sul dipendente è stata rimossa.

Suggerimento

Una relazione con una sola navigazione, una da dipendente a principal o da entità a dipendente, ma non entrambe, è nota come relazione unidirezionale.

Come in precedenza, questa relazione viene individuata per convenzione. Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

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

Si noti che la chiamata a WithOne non ha argomenti. Questo è il modo per indicare a ENTITY Framework che non è presente alcuna navigazione da Post a Blog.

Se la configurazione inizia dall'entità senza navigazione, il tipo dell'entità sull'altra estremità della relazione deve essere specificato in modo esplicito usando la chiamata generica HasOne<>() . Ad esempio:

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

Uno-a-molti senza spostamento all'entità di sicurezza e con chiave esterna ombreggiatura

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
}

In questo esempio vengono combinati due degli esempi precedenti rimuovendo sia la proprietà di chiave esterna che lo spostamento sul dipendente.

Questa relazione viene individuata per convenzione come relazione facoltativa. Poiché non è presente alcun elemento nel codice che può essere usato per indicare che deve essere necessario, è necessaria una configurazione minima che usa IsRequired per creare una relazione obbligatoria. Ad esempio:

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

Una configurazione più completa può essere usata per configurare in modo esplicito il nome della chiave esterna e di navigazione, con una chiamata appropriata a o IsRequired(false) in base alle IsRequired() esigenze. Ad esempio:

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

Uno-a-molti senza navigazione verso dipendenti

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

I due esempi precedenti avevano spostamenti dall'entità ai dipendenti, ma nessuna navigazione dall'entità dipendente all'entità. Per i due esempi successivi, lo spostamento sul dipendente viene introdotto nuovamente, mentre lo spostamento sull'entità viene rimosso.

Come in precedenza, questa relazione viene individuata per convenzione. Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

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

Si noti di nuovo che WithMany() viene chiamato senza argomenti per indicare che non è presente alcuna navigazione in questa direzione.

Se la configurazione inizia dall'entità senza navigazione, il tipo dell'entità sull'altra estremità della relazione deve essere specificato in modo esplicito usando la chiamata generica HasMany<>() . Ad esempio:

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

Uno-a-molti senza spostamenti

In alcuni casi, può essere utile configurare una relazione senza spostamenti. Tale relazione può essere modificata solo modificando direttamente il valore della chiave esterna.

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

Questa relazione non viene individuata per convenzione, poiché non sono presenti spostamenti che indicano che i due tipi sono correlati. Può essere configurato in modo esplicito in OnModelCreating. Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne();
}

Con questa configurazione, la Post.BlogId proprietà viene ancora rilevata come chiave esterna per convenzione e la relazione è necessaria perché la proprietà della chiave esterna non è nullable. La relazione può essere resa "facoltativa" rendendo la proprietà di chiave esterna nullable.

Una configurazione esplicita più completa di questa relazione è:

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

Uno-a-molti con chiave alternativa

In tutti gli esempi finora, la proprietà di chiave esterna sul dipendente è vincolata alla proprietà della chiave primaria nell'entità. La chiave esterna può invece essere vincolata a una proprietà diversa, che diventa quindi una chiave alternativa per il tipo di entità principale. Ad esempio:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the Post.BlogId foreign key
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Questa relazione non viene individuata per convenzione, poiché ENTITY creerà sempre, per convenzione, una relazione con la chiave primaria. Può essere configurato in modo esplicito usando OnModelCreating una chiamata a HasPrincipalKey. Ad esempio:

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

HasPrincipalKey può essere combinato con altre chiamate per configurare in modo esplicito gli spostamenti, le proprietà della chiave esterna e la natura obbligatoria/facoltativa. Ad esempio:

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

Uno-a-molti con chiave esterna composita

In tutti gli esempi finora, la proprietà chiave primaria o alternativa dell'entità è costituita da una singola proprietà. Le chiavi primarie o alternative possono anche essere formate da più di una proprietà. Queste chiavi sono note come "chiavi composite". Quando l'entità di una relazione ha una chiave composta, la chiave esterna del dipendente deve essere anche una chiave composta con lo stesso numero di proprietà. Ad esempio:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Questa relazione viene individuata per convenzione. Tuttavia, la chiave composita stessa deve essere configurata in modo esplicito::

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

Importante

Un valore di chiave esterna composita viene considerato null se uno dei relativi valori di proprietà è Null. Una chiave esterna composita con una proprietà Null e un'altra non Null non verrà considerata una corrispondenza per una chiave primaria o alternativa con gli stessi valori. Entrambi verranno considerati null.

Sia HasForeignKey che HasPrincipalKey possono essere usati per specificare in modo esplicito le chiavi con più proprietà. Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasMany(e => e.Posts)
                .WithOne(e => e.Blog)
                .HasPrincipalKey(e => new { e.Id1, e.Id2 })
                .HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

Suggerimento

Nel codice precedente, le chiamate a HasKey e HasMany sono state raggruppate in un generatore annidato. I generatori annidati eliminano la necessità di chiamare Entity<>() più volte per lo stesso tipo di entità, ma sono funzionalmente equivalenti alla chiamata Entity<>() più volte.

Obbligatorio uno-a-molti senza eliminazione a catena

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

Per convenzione, le relazioni necessarie sono configurate per l'eliminazione a catena. Ciò significa che quando l'entità viene eliminata, vengono eliminate anche tutte le relative dipendenze, poiché i dipendenti non possono esistere nel database senza un'entità di sicurezza. È possibile configurare Entity Framework per generare un'eccezione anziché eliminare automaticamente righe dipendenti che non possono più esistere:

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

Self-referencing uno-a-molti

In tutti gli esempi precedenti il tipo di entità principale è diverso dal tipo di entità dipendente. Questo non deve essere il caso. Ad esempio, nei tipi seguenti, ognuno Employee è correlato ad altri Employees.

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

    public int? ManagerId { get; set; } // Optional foreign key property
    public Employee? Manager { get; set; } // Optional reference navigation to principal
    public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}

Questa relazione viene individuata per convenzione. Nei casi in cui gli spostamenti, la chiave esterna o la natura obbligatoria/facoltativa della relazione non vengono individuati per convenzione, questi elementi possono essere configurati in modo esplicito. Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>()
        .HasOne(e => e.Manager)
        .WithMany(e => e.Reports)
        .HasForeignKey(e => e.ManagerId)
        .IsRequired(false);
}