实体定义

实体是我们对现实世界业务的抽象,对于C#开发者来讲,我们使用面向对象的思维用类的形式去将它表示出来。

使用EF Core可以将实体中表达的含义,转化为数据库存储的结构。

关于实体定义的规范和约定,我们要结合语言的表现能力以及EF Core的特性,给出一些建议。

EF Core 模型定义

遵循Entity Framework Core的官方文档,对模型及关联关系进行定义。

数据库元数据

EF Core提供了三种方式来补充与数据库相关的信息,这些信息并不是业务实体所必须的,如:

  • 索引
  • 注释
  • 主键
  • 值生成

我们可以通过数据注释Fluent API的方式来设定这些信息, 如官方文档:实体属性所示。

对比

使用数据注释,是指在实体模型类当中添加特性,这意味着实体模型类同时也包含数据库相关的信息。

使用Fluent API,则可以把实体跟数据库相关的表述内容分开。如果按照这种方式,在OnModelCreating中的代码会越来越多,且可读性较差。

由于Fluent API的一些问题,EF Core也提供了IEntityTypeConfiguration接口,称为分组配置, 具体可参考官方文档:分组配置

建议

建议使用统一的方式去定义数据库元数据,如统一使用数据注释分组配置,部分功能使用Fluent API

Note

Fluent API能实现很多功能,且它的优先级更高,在定义全局过滤等需求中,还是非常适合使用的。

C#写法

我们知道C#现在有了很多定义数据结构的方法,Class,Record,Struct等等,在语言层面上,他们各有不同,很多人可能会有些困惑,我应该使用什么方式去定义更好。 根据官方给出的示例和我们的实践,在此给出一些建议:

  • 使用 class去定义实体模型类
  • 使用可为 null 引用类型 (NRT),参考官方文档说明
  • 使用required关键字(.NET7/C#11新关键词)表示必填字段,避免使用[Required]特性
  • 如无必要,不要在实体模型类中使用带参数的构造方法,这会影响EF Core以及一些类型转换器的行为。

示例:

/// <summary>
/// 项目信息
/// </summary>
public class ProjectInfo
{
    // 使用required限定必填内容,不使用[required]或构造方法限定
    public required string Name { get; set; }
    // 同时使用init,表示值必须在初始化时填入,后续无法修改,也不需要使用构造方法限定
    public required string Path { get; init; }
    public ProjectType ProjectType { get; set; } = ProjectType.Web;
}