实体类型

在上下文中包含一种类型的 DbSet 意味着它包含在 EF Core 的模型中;我们通常将此类类型称为实体。 EF Core 可以从/向数据库中读取和写入实体实例,如果使用的是关系数据库,EF Core 可以通过迁移为实体创建表。

在模型中包含类型

按照约定,上下文的 DbSet 属性中公开的类型作为实体包含在模型中。 还包括在 OnModelCreating 方法中指定的实体类型,以及通过递归探索其他发现的实体类型的导航属性找到的任何类型。

下面的代码示例中包含了所有类型:

  • 包含 Blog,因为它在上下文的 DbSet 属性中公开。
  • 包含 Post,因为它是通过 Blog.Posts 导航属性发现的。
  • 包含 AuditEntry因为它是 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; }
}

从模型中排除类型

如果不希望在模型中包含某一类型,则可以排除它:

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

从迁移中排除

有时,将相同的实体类型映射到多个 DbContext 类型中非常有用。 在使用绑定上下文时尤其如此,对于每段绑定上下文,使用不同 DbContext 类型的情况很常见。

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

此配置迁移不会创建 AspNetUsers 该表,但 IdentityUser 仍包含在模型中,并且可正常使用。

如果需要再次使用迁移来管理表,则应创建不包括 AspNetUsers 的新迁移。 下一次迁移将包含对表所做的任何更改。

表名称

按照约定,每个实体类型都将设置为映射到与公开实体的 DbSet 属性名称相同的数据库表。 如果给定实体不存在 DbSet,则使用类名称。

可以手动配置表名:

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

表架构

使用关系数据库时,表按约定在数据库的默认架构中创建。 例如,Microsoft SQL Server 将使用 dbo 架构(SQLite 不支持架构)。

你可以配置要在特定架构中创建的表,如下所示:

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

还可以在模型级别使用 Fluent API 定义默认架构,而不是为每个表指定架构:

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

请注意,设置默认架构也会影响其他数据库对象,例如序列。

视图映射

可以使用 Fluent API 将实体类型映射到数据库视图。

注意

EF 假定数据库中已存在引用的视图,它不会在迁移中自动创建它。

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

映射到视图时,将移除默认表映射,但实体类型也可以显式映射到表。 在这种情况下,查询映射将用于查询,表映射将用于更新。

提示

要测试使用内存中提供程序映射到视图的无键实体类型,请通过 ToInMemoryQuery 将它们映射到查询。 有关详细信息,请参阅内存中提供程序文档

表值函数映射

可以将实体类型映射到表值函数 (TVF) 而不是数据库中的表。 为了说明这一点,我们定义另一个实体来表示具有多个帖子的博客。 在示例中,实体是无键的,但它不必是无键实体。

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

接下来,在数据库中创建以下表值函数,该函数仅返回包含多个帖子的博客以及与其中每个博客关联的帖子数:

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
)

现在,可以通过以下方式将实体 BlogWithMultiplePosts 映射到此函数:

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

注意

若要将实体映射到表值函数,函数必须是无参数的。

通常情况下,实体属性将映射到 TVF 返回的匹配列。 如果 TVF 返回的列名称与实体属性的名称不同,则可以使用 HasColumnName 方法配置实体的列,就像映射到常规表一样。

当实体类型映射到表值函数时,查询:

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

生成以下 SQL:

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

表注释

可以对数据库表设置任意文本注释,从而在数据库中记录架构:

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

共享类型实体类型

使用相同 CLR 类型的实体类型称为共享类型实体类型。 需要为这些实体类型配置一个唯一的名称,除了 CLR 类型之外,在使用共享类型实体类型时必须提供该名称。 这意味着,必须使用 Set 调用来实现对应的 DbSet 属性。

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