模型定义

前面章节我们简单介绍了EFCore框架和其使用方法,这篇笔记我们详细讲解如何在EFCore中定义数据模型。

约定优于配置

EFCore设计上遵循的一个原则是约定优于配置,EFCore有很多默认行为,如果我们的代码中没有对一些规则手动指定,那么EFCore可能会执行默认规则。一些常见的规则包括:

  1. 数据库表名默认采用DbContext中的DBSet属性名。
  2. 数据库表列名默认采用类的属性名,数据类型会采用尽量兼容类的属性类型的数据库类型。
  3. 数据库表列是否可空默认取决于属性是否为可空类型。
  4. 名字为Id类型+Id的属性默认会被设置为主键,如果主键使用shortint或是long类型,默认使用自增主键,如果主键采用Guid类型,默认采用Guid生成主键。

实际上EFCore包含了大量的默认行为,但我们实际开发中还是不要过分的依赖这些约定,以免降低代码的可读性和可维护性。

Data Annotation vs FluentAPI

EFCore中定义数据模型实际上有两种方式:Data Annotation(数据注解)和FluentAPI。这两种数据模型定义方法是等效的,只是写法不同。Data Annotation写法非常简单直观,而FluentAPI用起来要复杂很多。不过FluentAPI也并非完全没有优点,FluentAPI解耦了数据模型类和EFCore需要的映射关系配置,使用更加灵活,代码架构更加清晰。实际开发中我们可以根据我们的需要进行选择,一般来说可能FluentAPI使用更为广泛。

警告:数据注解和FluentAPI在同一个模型中可以同时使用,并且有一定的优先级关系,但实际开发中千万不要这么做!

Data Annotation(数据注解)

数据注解指在数据模型类上使用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]等注解用于指示模型对应的数据表名、主键字段、数据表列名等信息。

FluentAPI

除了数据注解方式,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+树索引原理,数据量变大后插入效率会直线下降。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap