Tipi di entità

L'inclusione di un DbSet di un tipo nel contesto significa che è inclusa nel modello di EF Core; in genere si fa riferimento a un tipo come un'entità. EF Core può leggere e scrivere istanze di entità da/al database e, se si usa un database relazionale, EF Core può creare tabelle per le entità tramite migrazioni.

Inclusione dei tipi nel modello

Per convenzione, i tipi esposti nelle proprietà DbSet nel contesto vengono inclusi nel modello come entità. Vengono inclusi anche i tipi di entità specificati nel OnModelCreating metodo , come tutti i tipi trovati esplorando in modo ricorsivo le proprietà di navigazione di altri tipi di entità individuati.

Nell'esempio di codice seguente sono inclusi tutti i tipi:

  • Blog è incluso perché è esposto in una proprietà DbSet nel contesto.
  • Post è incluso perché viene individuato tramite la Blog.Posts proprietà di navigazione.
  • AuditEntry perché è specificato in OnModelCreating.
internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AuditEntry>();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

public class AuditEntry
{
    public int AuditEntryId { get; set; }
    public string Username { get; set; }
    public string Action { get; set; }
}

Esclusione dei tipi dal modello

Se non si vuole includere un tipo nel modello, è possibile escluderlo:

[NotMapped]
public class BlogMetadata
{
    public DateTime LoadedFromDatabase { get; set; }
}

Esclusione dalle migrazioni

A volte è utile eseguire il mapping dello stesso tipo di entità in più DbContext tipi. Ciò vale soprattutto quando si usano contesti delimitati, per cui è comune avere un tipo diverso DbContext per ogni contesto delimitato.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdentityUser>()
        .ToTable("AspNetUsers", t => t.ExcludeFromMigrations());
}

Con queste migrazioni di configurazione non creerà la AspNetUsers tabella, ma IdentityUser è ancora inclusa nel modello e può essere usata normalmente.

Se è necessario iniziare a gestire nuovamente la tabella usando le migrazioni, deve essere creata una nuova migrazione in cui AspNetUsers non viene esclusa. La migrazione successiva conterrà ora tutte le modifiche apportate alla tabella.

Nome tabella

Per convenzione, ogni tipo di entità verrà configurato per eseguire il mapping a una tabella di database con lo stesso nome della proprietà DbSet che espone l'entità. Se non esiste alcun DbSet per l'entità specificata, viene usato il nome della classe.

È possibile configurare manualmente il nome della tabella:

[Table("blogs")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Schema di tabella

Quando si usa un database relazionale, le tabelle vengono create per convenzione nello schema predefinito del database. Ad esempio, Microsoft SQL Server userà lo dbo schema (SQLite non supporta gli schemi).

È possibile configurare le tabelle da creare in uno schema specifico come indicato di seguito:

[Table("blogs", Schema = "blogging")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Anziché specificare lo schema per ogni tabella, è anche possibile definire lo schema predefinito a livello di modello con l'API Fluent:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasDefaultSchema("blogging");
}

Si noti che l'impostazione dello schema predefinito influirà anche su altri oggetti di database, ad esempio sequenze.

Visualizzare il mapping

È possibile eseguire il mapping dei tipi di entità alle viste di database usando l'API Fluent.

Nota

Entity Framework presuppone che la vista a cui si fa riferimento esista già nel database, non la creerà automaticamente in una migrazione.

modelBuilder.Entity<Blog>()
    .ToView("blogsView", schema: "blogging");

Il mapping a una vista rimuoverà il mapping predefinito della tabella, ma il tipo di entità può anche essere mappato a una tabella in modo esplicito. In questo caso, il mapping delle query verrà usato per le query e il mapping della tabella verrà usato per gli aggiornamenti.

Suggerimento

Per testare i tipi di entità senza chiave mappati alle viste usando il provider in memoria, eseguirne il mapping a una query tramite ToInMemoryQuery. Per altre informazioni, vedere la documentazione del provider in memoria.

Mapping di funzioni con valori di tabella

È possibile eseguire il mapping di un tipo di entità a una funzione con valori di tabella anziché a una tabella nel database. Per illustrare questo concetto, definiamo un'altra entità che rappresenta il blog con più post. Nell'esempio l'entità è senza chiave, ma non deve essere.

public class BlogWithMultiplePosts
{
    public string Url { get; set; }
    public int PostCount { get; set; }
}

Creare quindi la funzione con valori di tabella seguente nel database, che restituisce solo blog con più post, nonché il numero di post associati a ognuno di questi blog:

CREATE FUNCTION dbo.BlogsWithMultiplePosts()
RETURNS TABLE
AS
RETURN
(
    SELECT b.Url, COUNT(p.BlogId) AS PostCount
    FROM Blogs AS b
    JOIN Posts AS p ON b.BlogId = p.BlogId
    GROUP BY b.BlogId, b.Url
    HAVING COUNT(p.BlogId) > 1
)

A questo punto, l'entità BlogWithMultiplePosts può essere mappata a questa funzione nel modo seguente:

modelBuilder.Entity<BlogWithMultiplePosts>().HasNoKey().ToFunction("BlogsWithMultiplePosts");

Nota

Per eseguire il mapping di un'entità a una funzione con valori di tabella, la funzione deve essere senza parametri.

In modo convenzionale, le proprietà dell'entità verranno mappate alle colonne corrispondenti restituite dal file TVF. Se le colonne restituite dal file TVF hanno nomi diversi rispetto alla proprietà dell'entità, è possibile configurare le colonne dell'entità usando HasColumnName il metodo , proprio come quando si esegue il mapping a una tabella normale.

Quando viene eseguito il mapping del tipo di entità a una funzione con valori di tabella, la query:

var query = from b in context.Set<BlogWithMultiplePosts>()
            where b.PostCount > 3
            select new { b.Url, b.PostCount };

Produce il codice SQL seguente:

SELECT [b].[Url], [b].[PostCount]
FROM [dbo].[BlogsWithMultiplePosts]() AS [b]
WHERE [b].[PostCount] > 3

Commenti tabella

È possibile impostare un commento di testo arbitrario che viene impostato nella tabella di database, consentendo di documentare lo schema nel database:

[Comment("Blogs managed on the website")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Tipi di entità di tipo condiviso

I tipi di entità che usano lo stesso tipo CLR sono noti come tipi di entità di tipo condiviso. Questi tipi di entità devono essere configurati con un nome univoco, che deve essere fornito ogni volta che viene usato il tipo di entità di tipo condiviso, oltre al tipo CLR. Ciò significa che la proprietà corrispondente DbSet deve essere implementata tramite una Set chiamata.

internal class MyContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs => Set<Dictionary<string, object>>("Blog");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>(
            "Blog", bb =>
            {
                bb.Property<int>("BlogId");
                bb.Property<string>("Url");
                bb.Property<DateTime>("LastUpdated");
            });
    }
}