查缺补漏系统学习 EF Core 6 - 实体配置

这是 EF Core 系列的第二篇文章,上一篇文章讲解了 EF Core 的一些基础概念,这一篇文章主要围绕实体属性的配置。

实体配置

配置实体的目的,是为了让实体属性与数据库表字段实现正确的映射。

EF Core 有三种方式来配置实体:按约定、数据注释、Fluent API,下面依次进行阐述。.

按约定配置

按约定配置,是指 EF Core 遵循一套关于属性类型和名称的简单规则,并相应地配置数据库。

按约定的配置,可以被数据注释(特性)或 Fluent API 重写。

比如,下面这个示例中:

public class Account
{
    public Guid AccountId { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

EF Core 通过遵循命名规则,来配置 Account 实体类中的主键字段。

如果实体类拥有一个名为 Id 的属性,或一个<类名+Id>的组合,它就会被作为主键。

如果实体类中有一个复合主键,我们就不能按约定配置了。

当 EF Core 使用按约定的配置时,它会遍历所有的公共属性,并通过它们的名称和类型来映射它们。

示例中,Name 属性是一个可以为 Null 的字段,因为字符串类型的默认值是 Null

但 Age 属性不会为 Null,因为它是一个值类型。

当然,如果你想让 Age 属性,在数据库中可以为 Null,那需要在类型后加上 「?」 后缀:

public int? Age { get; set; }

数据注释

数据注释的表现形式就是特性,它不仅可以用来配置实体属性,还可以永远验证实体数据是否合法。

我们来替换一下 Account 实体类中的内容:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ConsoleApp1.Entities
{
    [Table("Account")]
    public class Account
    {
        [Column("AccountId")]
        public Guid Id { get; set; }

        [Required]
        [MaxLength(50, ErrorMessage = "长度必须小于50个字符")]
        public string Name { get; set; }

        public int Age { get; set; }
    }
}

Name 属性的特性,它就是数据注释的一种,来自 System.ComponentModel.DataAnnotations 命名空间,这个命名空间中的属性主要与约束有关。

Required 属性,说明 Name 字段不能为空;MaxLengh 属性,限制了数据库中该列的长度。

Account 类型与 AccountId 属性的特性,也属于数据注释。

不过,它们来自于 System.ComponentModel.DataAnnotations.Schema 命名空间,这个命名空间中的属性主要与数据库配置有关。

默认情况下,实体类映射到数据库中的表名,与上下文类中 DbSet 属性名有关。

比如 DbSet<Account>,它的属性名是 Accounts,所以映射到数据库中的表名就是 Accounts。但是使用了 「Table」 特性就会覆盖这个默认行为。

「Column」 特性,则可以为 EF Core 提供,该属性映射到数据库中的列信息。

如果你想在代码中使用 Account 类的 Id 属性,而不是 AccountId,而又想让它在数据库中,以 AccountId 字段名表示,那么就需要使用 「Column」 特性。

「Column」 特性中还有其它的一些参数,比如字段的顺序与数据库中的类型等,简单来说,它可以定义属性在数据库中的形式。

Fluent API

Fluent API 是一组方法,这些方法提供了大量的 EF Core 配置选项,用来在上下文类中配置实体。

我们可以在 ApplicationContext 类中,添加这么一段代码:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Account>()
        .ToTable("Account");
    modelBuilder.Entity<Account>()
        .Property(s => s.Id)
        .HasColumnName("AccountId");
    modelBuilder.Entity<Account>()
        .Property(s => s.Name)
        .IsRequired()
        .HasMaxLength(50);
    modelBuilder.Entity<Account>()
        .Property(s => s.Age)
        .IsRequired(false);
}

Fluent API 可以在 OnModelCreating 方法中使用,这段配置与前面的数据注释拥有同样的效果。

Fluent API 的使用,需要在一开始,就选择需要配置的实体,然后通过 Property 方法,指定需要添加约束的属性,其他的方法就很清晰明了了。

OnModelCreating 方法会在 ApplicationContext 类第一次实例化时被调用。也就是在这一刻,所有的三种实体配置方式都会被应用。

由于配置规则非常多,而常用的又并不多,所以更多的配置规则,大家可以通过 EF Core 官方文档去查阅。

配置方式的选择

现在,我们主要来说一说,这三种配置方式, 我们应该怎么选择?

「首先是按约定配置,」这个永远都是我们的首选。

因为,拥有与表名相同的类名、拥有与命名约定相匹配的主键属性名,以及拥有与列相同的名称和类型的属性,是我们的首选。这样不需要我们做太多的工作。

「然后是数据注释」,它不仅可以配置实体,最重要的是可以实现数据验证,如必填或最大长度验证等,我们应该使用数据注释,而不是 Fluent API 方法。

原因在于,我们可以很容易的看到,哪个验证规则与哪个属性有关,因为它就在属性的上方,而且具有语义性。另外,数据注释的验证,还可以应用在 MVC 中的视图页面。

比如在 Account 类中,如果验证失败,还可以配置错误信息。这种方法会让我们的验证代码更简单,更容易维护。

「最后是 Fluent API」 ,它用于以上两种方法以外的情况,比如索引、复合键、关系都应该使用 Fluent API。

对于那些我们无法做到的配置,或者当我们想从实体类中隐藏配置时,也必须使用这种方法。

小结

这篇文章主要讲了实体属性的三种配置方式,下篇文章我们将围绕实体的数据库迁移与种子数据的填充。