Types d’entités

L’inclusion d’un DbSet d’un type sur votre contexte signifie qu’elle est incluse dans le modèle d’EF Core ; nous faisons généralement référence à un type tel qu’une entité. EF Core peut lire et écrire des instances d’entité depuis/vers la base de données, et si vous utilisez une base de données relationnelle, EF Core peut créer des tables pour vos entités via des migrations.

Inclusion de types dans le modèle

Par convention, les types exposés dans les propriétés DbSet de votre contexte sont inclus dans le modèle en tant qu’entités. Les types d’entités spécifiés dans la méthode OnModelCreating sont également inclus, comme tous les types trouvés en explorant de manière récursive les propriétés de navigation d’autres types d’entités découverts.

Dans l’exemple de code ci-dessous, tous les types sont inclus :

  • Blog est inclus, car il est exposé dans une propriété DbSet sur le contexte.
  • Post est inclus, car il est découvert via la propriété de navigation Blog.Posts.
  • AuditEntry, car il est spécifié dans 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; }
}

Exclusion des types du modèle

Si vous ne souhaitez pas qu’un type soit inclus dans le modèle, vous pouvez l’exclure :

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

Exclusion des migrations

Il est parfois utile d’avoir le même type d’entité mappé dans plusieurs types DbContext. Cela est particulièrement vrai lors de l’utilisation de contextes délimités, pour lesquels il est courant d’avoir un type DbContext différent pour chaque contexte délimité.

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

Avec cette migration de configuration, la table AspNetUsers n’est pas créée, mais IdentityUser est toujours inclus dans le modèle et peut être utilisé normalement.

Si vous devez commencer à gérer la table à l’aide de migrations à nouveau, une nouvelle migration doit être créée où AspNetUsers n’est pas exclu. La migration suivante contiendra désormais les modifications apportées à la table.

Nom de table

Par convention, chaque type d’entité est configuré pour être mappé à une table de base de données avec le même nom que la propriété DbSet qui expose l’entité. Si aucun DbSet n’existe pour l’entité donnée, le nom de la classe est utilisé.

Vous pouvez configurer manuellement le nom de la table :

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

Schéma de la table

Lorsque vous utilisez une base de données relationnelle, les tables sont créées par convention dans le schéma par défaut de votre base de données. Par exemple, Microsoft SQL Server utilisera le schéma dbo (SQLite ne prend pas en charge les schémas).

Vous pouvez configurer des tables à créer dans un schéma spécifique comme suit :

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

Au lieu de spécifier le schéma pour chaque table, vous pouvez également définir le schéma par défaut au niveau du modèle avec l’API Fluent :

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

Remarque : la définition du schéma par défaut affecte également d’autres objets de base de données, tels que des séquences.

Afficher le mappage

Les types d’entités peuvent être mappés aux vues de base de données à l’aide de l’API Fluent.

Remarque

EF suppose que la vue référencée existe déjà dans la base de données, elle ne la crée pas automatiquement dans une migration.

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

Le mappage à une vue supprime le mappage de table par défaut, mais le type d’entité peut également être mappé à une table explicitement. Dans ce cas, le mappage de requêtes sera utilisé pour les requêtes, et le mappage de table sera utilisé pour les mises à jour.

Conseil

Pour tester les types d’entités sans clé mappés aux vues à l’aide du fournisseur en mémoire, mappez-les à une requête via ToInMemoryQuery. Pour plus d’informations, consultez la documentation du fournisseur en mémoire.

Mappage de fonction table

Il est possible de mapper un type d’entité à une fonction table (TVF) au lieu d’une table dans la base de données. Pour illustrer cela, nous allons définir une autre entité qui représente le blog avec plusieurs billets. Dans l’exemple, l’entité est sans clé, mais elle n’a pas besoin de l’être.

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

Ensuite, créez la fonction table suivante dans la base de données, qui retourne uniquement des blogs avec plusieurs billets, ainsi que le nombre de billets associés à chacun de ces blogs :

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
)

À présent, l’entité BlogWithMultiplePosts peut être mappée à cette fonction comme suit :

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

Remarque

Pour mapper une entité à une fonction table, la fonction doit être sans paramètre.

De façon conventionnelle, les propriétés d’entité sont mappées aux colonnes correspondantes retournées par la fonction TVF. Si les colonnes retournées par la fonction TVF ont des noms différents de la propriété d’entité, les colonnes de l’entité peuvent être configurées à l’aide de la méthode HasColumnName, comme lors du mappage à une table classique.

Lorsque le type d’entité est mappé à une fonction table, la requête :

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

Génère l’instruction SQL suivante :

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

Commentaires de table

Vous pouvez définir un commentaire de texte arbitraire qui est défini sur la table de base de données, ce qui vous permet de documenter votre schéma dans la base de données :

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

Types d’entités de type partagé

Les types d’entités qui utilisent le même type CLR sont appelés types d’entités de type partagé. Ces types d’entités doivent être configurés avec un nom unique, qui doit être fourni chaque fois que le type d’entité de type partagé est utilisé, en plus du type CLR. Cela signifie que la propriété DbSet correspondante doit être implémentée à l’aide d’un appel Set.

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