Compartilhar via


Tipos de Entidades

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.

Incluindo 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 OnModelCreating método 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 está exposto em uma propriedade DbSet no contexto.
  • Post é incluído porque é descoberto pela 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; }
}

Excluindo 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; }
}

Exclusão das migrações

Às vezes, é útil ter o mesmo tipo de entidade mapeado em vários DbContext tipos. Isso é especialmente verdadeiro ao usar contextos limitados, para os quais é comum ter um tipo diferente DbContext 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ão incluídas no modelo e podem ser usadas normalmente.

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

Nome da tabela

Por convenção, cada tipo de entidade será configurado para mapear uma tabela com o mesmo nome da propriedade DbSet que expõe a entidade. Se nenhum DbSet existir para a entidade fornecida, o nome da classe será usado.

Você pode configurar manualmente o nome da tabela:

[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 dbo esquema (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 assumirá que a visão referenciada já existe no banco de dados e não a criará automaticamente em uma migração.

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

O mapeamento para uma visualizaçã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 exibição 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 em 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

É 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 HasColumnName o método, 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 };

Produz o seguinte SQL:

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

Comentários sobre a 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 Set chamada.

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