前面章节我们简单介绍了EFCore框架和其使用方法,这篇笔记我们详细讲解如何在EFCore中定义数据模型。
EFCore设计上遵循的一个原则是约定优于配置,EFCore有很多默认行为,如果我们的代码中没有对一些规则手动指定,那么EFCore可能会执行默认规则。一些常见的规则包括:
DbContext
中的DBSet
属性名。Id
、类型+Id
的属性默认会被设置为主键,如果主键使用short
、int
或是long
类型,默认使用自增主键,如果主键采用Guid
类型,默认采用Guid
生成主键。实际上EFCore包含了大量的默认行为,但我们实际开发中还是不要过分的依赖这些约定,以免降低代码的可读性和可维护性。
EFCore中定义数据模型实际上有两种方式:Data Annotation(数据注解)和FluentAPI。这两种数据模型定义方法是等效的,只是写法不同。Data Annotation写法非常简单直观,而FluentAPI用起来要复杂很多。不过FluentAPI也并非完全没有优点,FluentAPI解耦了数据模型类和EFCore需要的映射关系配置,使用更加灵活,代码架构更加清晰。实际开发中我们可以根据我们的需要进行选择,一般来说可能FluentAPI使用更为广泛。
警告:数据注解和FluentAPI在同一个模型中可以同时使用,并且有一定的优先级关系,但实际开发中千万不要这么做!
数据注解指在数据模型类上使用Attribute注解来标注模型和数据表的映射关系,下面是一个例子。
Student.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Gacfox.Demo.DemoNetCore;
[Table("t_student")]
public class Student
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("student_id")]
public long StudentId { get; set; }
[Required]
[Column("student_name")]
public string StudentName { get; set; }
[Required]
[Column("age")]
public int Age { get; set; }
[Required]
[Column("create_time")]
public DateTime CreateTime { get; set; }
}
代码中我们定义了数据模型Student
类,其中用到了[Table]
、[Key]
、[Column]
等注解用于指示模型对应的数据表名、主键字段、数据表列名等信息。
除了数据注解方式,EFCore中还可以使用FluentAPI方式指定数据模型和数据库表的映射关系,下面是一个例子。
Student.cs
namespace Gacfox.Demo.DemoNetCore;
public class Student
{
public long StudentId { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
public DateTime CreateTime { get; set; }
}
首先我们定义了Student
类,和之前不同我们没有在数据模型类上使用任何注解。
StudentEntityConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Gacfox.Demo.DemoNetCore;
public class StudentEntityConfig : IEntityTypeConfiguration<Student>
{
public void Configure(EntityTypeBuilder<Student> builder)
{
builder.ToTable("t_student");
builder.HasKey(e=>e.StudentId);
builder.Property(e => e.StudentId)
.ValueGeneratedOnAdd()
.HasColumnName("student_id");
builder.Property(e => e.StudentName)
.HasColumnName("student_name")
.HasMaxLength(50)
.IsRequired();
builder.Property(e => e.Age)
.HasColumnName("age")
.IsRequired();
builder.Property(e => e.CreateTime)
.HasColumnName("create_time")
.IsRequired();
}
}
这里我们定义了StudentEntityConfig
类,这是一个配置类,其中使用了FluentAPI对数据模型和数据库表之间的映射关系进行了配置。配置内容和之前注解方式完全等效。
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace Gacfox.Demo.DemoNetCore;
public class MyDbContext : DbContext
{
public DbSet<Student> Students { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
const string connStr = "Server=localhost;Port=3306;Database=netstore;User=root;Password=root;";
optionsBuilder.UseMySql(connStr, ServerVersion.AutoDetect(connStr));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
}
}
这里注意创建DbContext
时,我们要指示EFCore加载FluentAPI数据模型配置,我们这里调用了modelBuilder.ApplyConfigurationsFromAssembly()
方法,指定EFCore从当前程序集中搜索并加载所有数据模型配置。
下面我们介绍一些常用的模型配置。
数据注解:
[Column("student_name")]
public string StudentName { get; set; }
FluentAPI:
builder.Property(e => e.StudentName).HasColumnName("student_name");
数据注解:
[Column(TypeName ="varchar(200)")]
public string StudentName { get; set; }
FluentAPI:
builder.Property(e => e.StudentName).HasColumnType("varchar(201)");
实际开发中,我们一般使用自增主键或SnowFlakeID作为主键,EFCore中我们可以直接设置自增主键,而SnowFlakeID方式则需要我们手动设置,这里我们主要介绍前者。
数据注解:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long StudentId { get; set; }
FluentAPI:
builder.HasKey(e=>e.StudentId);
builder.Property(e => e.StudentId).ValueGeneratedOnAdd();
注:为什么MySQL(InnoDB)不使用UUID或GUID作为主键?因为UUID/GUID是无序的,参考MySQL的B+树索引原理,数据量变大后插入效率会直线下降。