EFCore学习笔记(2)——实体类型

文章目录

  • 一、实体类型(Entity Types)
    • 1. 在model中包含类型
    • 2. 从model中排除类型
      • △2.1. 从migrations中排除
    • 3. 表名
    • 4. Table schema
    • △5. 视图映射
    • △6. 表值的函数映射
    • △7. 表评论
    • △8. 共享类型的实体类型
  • 二、小结

一、实体类型(Entity Types)

上下文(Context的派生类)中包含一种类型的DbSet意味着它存在于EF Core的模型中;

internal class MyContext : DbContext
{
	// Blog是实体
    public DbSet<Blog> Blogs { get; set; }
	...
}

通常称这样的类型为实体(Entity)(所以实体就是那个类,而不是DbSet xxx)。EF Core能从数据库中读数据至实体的实例或将实体实例写入数据库,并且如果你用的是关系数据库,EF Core能通过migrations为你的实体建表。

1. 在model中包含类型

按照约定(指的是EF Core这套技术设计/使用上的规定),在你定义的上下文中的DbSet属性暴露的类型会作为实体被包含在model中。

在OnModelCreating方法中指定的实体类型也会被包含在内,并且由递归探索到的实体类型的导航属性所找到的任何类型也会被包含在内(很拗口,结合下面例子进行理解)。

在下面代码示例中,包含了上面描述的所有类型:

  • Blog是被包含在内的,因为它在上下文中由DbSet属性暴露;
  • Post是被包含在内的,因为它通过Blog.Posts导航属性可以发现;
  • AudiEntry是,因为它在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; }
}

2. 从model中排除类型

如果你不想某个类型被包含到模型中,你可以排除它:

// 两种方式
// 1. 数据标注方式
[NotMapped]
public class BlogMetadata
{
    public DateTime LoadedFromDatabase { get; set; }
}
// 2. Fluent API方式
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
	modelBuilder.Ignore<BlogMetadata>();
}

△2.1. 从migrations中排除

注意:
该功能在EF Core 5.0之后才引入的。

虽然这个功能我暂时不用,不过还是简单学习下,留个印象。
有时,将相同的实体类型映射到多个DbContext中是很有用的。尤其在使用有界上下文时,因为每个有界上下文通常有不同的DbContext类型。

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

通过这个配置项,migrations将不会创建AspNetUsers表,但IdentityUser仍然会包含在模型中,且能被正常使用。
如果你需要再次使用migrations来管理表,那么应该在不排除AspNetUsers的情况下创建一个新的migration。现在,下一个migration将包含对表做的任何更改。

3. 表名

按照约定,每个实体类型将被设置为映射到与暴露实体的DbSet属性有着相同名称的数据表。若给定的实体不存在DbSet,则使用类名。
你可以手动配置表名:

// 两种方式
// 1. 数据标注
[Table("blogs")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
// 2. Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable("blogs");
}

4. Table schema

schema这个概念我也是不太熟悉,所以特地去补了一下。
因为在我的感觉中,数据库的关系是这样的, 数据服务器下面有数据库,数据库下面有数据表,表中有字段,仅此而已。
实际上当我打开MySQL workbench,打开一个数据服务器连接后,展现的树状图如下,

EFCore学习笔记(2)——实体类型_第1张图片

显然,它们都是在一个叫SCHEMAS的根节点下的。所以这个图标是数据库形状的子节点居然是schema?!
这边我也不想展开讲,网上说法很多,但是就目前来看,在MySQL中把schema理解成database没有大问题。

当使用关系数据库时,表按照约定会创建到你数据库中默认的table schema中。例如,SQL Server将会使用dbo schema(SQLite不支持schemas)。
你可以按照下面方式在指定的schema中配置创建的表:

// 1. 数据标注
[Table("blogs", Schema = "blogging")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
// 2. Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable("blogs", schema: "blogging");
}

除了为每个表指定schema,你也可以使用fluent API在模型上定义默认的schema:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasDefaultSchema("blogging");
}
注意:设置默认的schema还会影响其他的数据库对象,例如序列(sequence)。

△5. 视图映射

实体类型能通过Fluent API映射到到数据库视图中。

注意:
EF会假设所引用的视图已经存在于数据库中,它不会在migration中自动创建它。
modelBuilder.Entity<Blog>()
    .ToView("blogsView", schema: "blogging");

映射到视图会删除默认的表映射,但是从EF 5.0开始,实体类也可以显示映射到表。在这种情况下,查询映射将用于查询,表映射将用于更新。

提示:
要测试使用内存提供程序映射到视图的无键实体类型,通过ToInMemoryQuery将它们映射到查询。

△6. 表值的函数映射

有时也会将实体类型映射到表值函数(TVF),而不是数据库中的表。为了说明这点,我们定义另一个实体,它表示带有多个帖子的blog。在本例中,实体是无键的,但它也不是必须的。

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返回的列有与entity属性不同的名称,那么可以用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

△7. 表评论

你可以在数据表上设置任意文本注释,允许你在数据库中记录你的模式:

// 1. 数据标注 EF Core 5.0引入的
[Comment("Blogs managed on the website")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}
// 2. fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasComment("Blogs managed on the website");
}

△8. 共享类型的实体类型

注意:
EF Core 5.0引入了支持共享类型的实体类型。

使用相同CLR类型的实体类型被称为共享类型的实体类型。这些实体类型需要配置一个唯一的名称,除了CLR类型,每当使用共享类型实体类型时,都必须提供这个名称。这意味着相应的DbSet属性必须使用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");
            });
    }
}

二、小结

本意是稍微学习一下实体类型是啥,以及实体类型在模型中扮演怎样的角色。结果词条太多,写了这么多玩意儿。于是我用 标注了我认为对初学者来说暂时不那么重要的条目。

最后用自己的语言总结一下实体类型的特点:

  • 实体是个类,它是用来与数据库通信的,它本身不能起通信作用,需要以DbSet来在上下文中暴露才行(还有其它两种方式)
  • 每个实体类型将被设置为映射到与暴露实体的DbSet属性有着相同名称的数据表。若给定的实体不存在DbSet,则使用类名。(所以操作实体类时,用它的实例来读写数据,可以认为在操作一张表)

你可能感兴趣的:(.NET,数据库,数据库,.net)