Tipos de entidade

Incluir um DbSet de um tipo em seu contexto significa que ele está incluído no modelo do EF Core; geralmente nos referimos a um tipo como uma entidade. O EF Core pode ler e gravar instâncias de entidade de/para o banco de dados e, se você estiver usando um banco de dados relacional, o EF Core poderá criar tabelas para suas entidades por meio de migrações.

Como incluir tipos no modelo

Por convenção, os tipos expostos nas propriedades DbSet em seu contexto são incluídos no modelo como entidades. Os tipos de entidade especificados no método OnModelCreating também são incluídos, assim como todos os tipos encontrados explorando recursivamente as propriedades de navegação de outros tipos de entidade descobertos.

No exemplo de código abaixo, todos os tipos estão incluídos:

  • Blog é incluído porque é exposto em uma propriedade DbSet no contexto.
  • Post é incluído porque é descoberto por meio da propriedade de navegação Blog.Posts.
  • AuditEntry porque ele é especificado em 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; }
}

Como excluir tipos do modelo

Se você não quiser que um tipo seja incluído no modelo, você poderá excluí-lo:

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

Excluindo das migrações

Às vezes, é útil ter o mesmo tipo de entidade mapeado em vários tipos DbContext. Isso é especialmente verdadeiro ao usar contextos limitados, para os quais é comum ter um tipo DbContext diferente para cada contexto limitado.

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

Com essa configuração, as migrações não criarão a AspNetUsers tabela, mas IdentityUser ainda está incluído no modelo e pode ser usado normalmente.

Se você precisar começar a gerenciar a tabela usando migrações novamente, uma nova migração em que AspNetUsers não estiver excluído deverá ser criado. A próxima migração agora conterá as alterações feitas na tabela.

Nome da tabela

Por convenção, cada entidade será configurada para ser mapeada para uma tabela com o mesmo nome que a propriedade que expõe a entidade no contexto derivado. Se não existir nenhum DbSet para a entidade fornecida, o nome da classe será usado.

Você pode configurar o nome da tabela manualmente:

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

Esquema de tabela

Ao usar um banco de dados relacional, as tabelas são criadas por convenção no esquema padrão do banco de dados. Por exemplo, o Microsoft SQL Server usará o esquema dbo (o SQLite não dá suporte a esquemas).

Você pode configurar tabelas a serem criadas em um esquema específico da seguinte maneira:

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

Em vez de especificar o esquema para cada tabela, você também pode definir o esquema padrão no nível do modelo com a API fluente:

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

Observe que a configuração do esquema padrão também afetará outros objetos de banco de dados, como sequências.

Mapeamento de exibição

Os tipos de entidade podem ser mapeados para exibições de banco de dados usando a API fluente.

Observação

O EF irá pressupor que o modo de exibição referenciado já existe no banco de dados, ele não o criará automaticamente em uma migração.

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

O mapeamento para uma exibição removerá o mapeamento de tabela padrão, mas o tipo de entidade também pode ser mapeado para uma tabela explicitamente. Nesse caso, o mapeamento de consulta será usado para consultas e o mapeamento de tabela será usado para atualizações.

Dica

Para testar tipos de entidade sem chave mapeados para exibições usando o provedor na memória, mapeie-os para uma consulta por meio de ToInMemoryQuery. Consulte os documentos do provedor na memória para obter mais informações.

Mapeamento de função com valor de tabela de banco de dados

É possível mapear um tipo de entidade para uma TVF (função com valor de tabela) em vez de uma tabela no banco de dados. Para ilustrar isso, vamos definir outra entidade que representa o blog com várias postagens. No exemplo, a entidade é sem chave, mas não precisa ser.

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

Em seguida, crie a seguinte função com valor de tabela no banco de dados, que retorna apenas blogs com várias postagens, bem como o número de postagens associadas a cada um desses 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
)

Agora, a entidade BlogWithMultiplePosts pode ser mapeada para essa função da seguinte maneira:

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

Observação

Para mapear uma entidade para uma função com valor de tabela, a função deve ser sem parâmetros.

Convencionalmente, as propriedades da entidade serão mapeadas para colunas correspondentes retornadas pelo TVF. Se as colunas retornadas pelo TVF tiverem nomes diferentes da propriedade da entidade, as colunas da entidade poderão ser configuradas usando o método HasColumnName, assim como ao mapear para uma tabela regular.

Quando o tipo de entidade é mapeado para uma função com valor de tabela, a consulta:

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

Isso produz o seguinte SQL:

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

Comentários de tabela

Você pode definir um comentário de texto arbitrário que é definido na tabela de banco de dados, permitindo que você documente seu esquema no banco de dados:

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

Tipos de entidade de tipo compartilhado

Os tipos de entidade que usam o mesmo tipo CLR são conhecidos como tipos de entidade de tipo compartilhado. Esses tipos de entidade precisam ser configurados com um nome exclusivo, que deve ser fornecido sempre que o tipo de entidade de tipo compartilhado for usado, além do tipo CLR. Isso significa que a propriedade correspondente DbSet deve ser implementada usando uma chamada 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");
            });
    }
}