大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: ADO&EF 修改讲义

复习:持久化:ORM:映射 / SQL生成 / 自动建表 

Entity Framwork

entity:实体,所有需要被持久化的对象。

目前.NET平台官方的ORM工具就是Entity Framework,根据所依据的.NET框架不同,又分为:

  • EF core,支持.NET5/6、.NET core
  • EF6,支持.NET Framework

两者大同小异,我们的课程先讲一遍.NET core,最后再对照.NET core把EF6快速的拉一遍。

PS:其他流行的、由开源社区支持维护的ORM还有NHibernate、Dapper、FreeSql……

演示:通过Nuget引入EF框架


DbContext和DbSet

这是EF的的两个核心对象。

因为要OOD(Object-Oriented Dessign/Develop),即从一开始就首先考虑对象(entity)而不是数据库,我们选择使用Code First。

声明一个Student对象:

public class Student
{
    public int Id { get; set; }
    //或者:public int StudentId { get; set; }
    public string Name { get; set; }
    public Bed SleepIn { get; set; }

一个Bed对象:

public class Bed

注意:每一个Entity都应该有一个Key定义/辨别该Entity,EF默认使用属性名为 Id 或 类名+Id 的属性作为 Key。否则后面会报错。

DbContext

通常我们都是自己创建一个类来继承DbContext

public class SqlDbContext : DbContext   

指定数据库

EF通过provider的形式支持多种数据库,详见:Database Providers

所以还需要在DbContext子类中override OnConfiguring()方法,指定数据库:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    string connectionString =
        @"Data Source=(localdb)\ProjectModels;Initial Catalog=17bang;Integrated Security=True;";
    optionsBuilder.UseSqlServer(connectionString);

为了适配SQL Server(点出UseSqlServer()),还需要安装dll:

Microsoft.EntityFrameworkCore.SqlServer

如果是要连接mysql的话,详见Linq……


DbSet

然后,通过声明/添加DbSet<E>属性,告知EF哪些entity需要被持久化。

//注意这里:不能是字段
public DbSet<Student> Students { set; get; }   //EF据此确定哪些class需要映射到数据库
或者,在方法中OnModelCreating()中使用方法配置:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Teacher>();

顾名思义,OnModelCreating在Moldel创建时被EF自动调用,通过modelBuilder.Entity<T> ()还可以设置T的自定义映射细节。

默认的(by convention),EF会把以下三种class映射到数据库

  1. DbContext子类中DbSet<T>属性(注意:必须是属性,字段不行)的泛型参数指定的类型
  2. OnModelCreating()中配置的Entity<T>里的泛型参数指定的类型。如上文中的Major。即使没有DbSet<Major>属性,EF仍然会将Major进行映射。
  3. 上述两种类型里关联的类型(后文详述)


EF Migrations

可以根据DbContext中指定的entity,自动建表。

为了使用EF Migrations工具,我们还需要在起始项目中引入dll:(ASP.NET Core version 2.1后版本已自动安装)

Microsoft.EntityFrameworkCore.Tools

然后,在VS中打开 Package Manager Console(Tools > NuGet Package Manager > Package Manager Console)

测试是否已安装成功

输入:GET-HELP about_EntityFrameworkCore

显示以下内容,表明Migration Tool安装成功

PS:如果操作系统是windows7,可能还需要额外的安装相应的.NET Framework/Core

Add-Migration

根据entity“准备”好表结构映射

Migration文件夹

演示成功运行命令:Add-Migration <name>之后(<name>由开发人员指定,注意要有意义),

EF在DbContext所在项目生成一个文件夹Migrations:

以及两种类文件:

  • ModelSnapshot(快照):记录的是当前EF的映射情况

  • migration文件

    1. 类名前缀是时间戳,显示migration生成时间(不是本地时间),有助于其后续维护
    2. 命令行中的<name>就对应着生成的类名(复习:部分类),里面有Up()和Down()两个方法


      由EF将现有的entity和ModelSnapShot.cs做对比生成,供之后Update-Database使用(前进/回滚)

如果是mysql的话,(EF6)需要在DbContext上添加特性:

[DbConfigurationType(typeof(MySqlEFConfiguration))]
public class MysqlDbContext : DbContext


多次执行

Add-Migration可多次执行,比如随着项目的开发,Entity发生了变更:
        //Student中添加一行Age
        public int Age { get; set; }

我们就可以再执行一次Add-Migration。

每执行一次Add-Migration,添加migration文件并更新ModelSnapShot所以它始终只有一个

Remove-Migration

删除(不是revert)“最近”(注意只能是最近)的一次migration,表现为:<datetime_stamp><migration_name>.cs同时被删除(演示:略)

Update-Database

仅仅执行Add-Migration,数据库不会变化(演示)

执行Update-Database命令,才会利用Migrations文件夹下内容,同步数据库表结构。(3.1.30第一次会自动建库,已有数据库会报异常)

演示:检查数据库,可以看到

  • 根据entity类名生成的Student表,且列名和属性名相同
  • 表_MigrationHistory

_MigrationHistory表

该表由Migration自动生成并维护。

顾名思义,_MigrationHistory记录了和Migration的历史。

这样,当执行Update-Database命令时,EF会对比表_MigrationHistory和C#项目中的Migrations文件夹,决定需要执行的Migration

回滚/前进

我们还可能会回退到某一个版本(复习:git),这时数据库需要响应的回滚。

在Update-Database时我们可以指定“版本号”,形成“回退/前进”效果:

  • 不带参数:执行所有migration
  • 指定参数:
    • 0:彻底revert所有migration
    • Add-Migration中的<datetime_stamp><migration_name>(如果没有重复,也可以省略时间戳,简写为<migration_name>)

@想一想@:为什么需要数据库里的_EFMigrationsHistory?

Script-Migration

生成sql代码,常用于部署。可使用参数:

  • -From:从哪一次Migration开始,默认为第一次
  • -To:到哪一次结束,默认为最近一次
  • -Output:输出路径,默认在 程序运行 位置,如: /obj/Debug/netcoreapp2.1/ghbkztfz.sql

生成的SQL只包含_EFMigrationHistory数据。

演示:略

多项目场景

注意指定好:

  • 起始(startup)项目:默认是solution的启动项目,只能是“可执行”的,且必须引用了EF tools组件
  • 目标(target)项目:DbContext所在的项目,默认是PM Console中的“Default Project”

Migration会:

  1. 从起始项目开始,
  2. 找到目标项目中的DbContext(有且仅有一个)
  3. 再根据DbContext中的设定(比如那些entity需要映射)进行migration


使用API

可以使用C#代码快速的建库建表。

首先生成一个SqlDbContext对象

SqlDbContext context = new SqlDbContext();

然后删除/创建数据库:

context.Database.EnsureDeleted();
context.Database.EnsureCreated();

Enusure:存在才删除,不存在才创建。

演示:建立的数据库没有__EFMigrationsHistory表


自定义配置

之前我们使用的都是EF的默认配置。

  • 表名:DbSet属性名(如果有的话),或类名(OnModelCreating()中引入)
  • 列名:entity属性名
某些情况我们可能会想要进行一些自定义的修改,大部分的自定义配置都可以使用两种方式:
  • OnModelCreating()中调用方法
  • Annotations:在类或属性声明特性
#理解#:EF框架利用反射,检查特性,然后调整映射……

表名列名

表名:

    modelBuilder.Entity<NewStudent>().ToTable("Student");
    [Table("Student")]
    public class NewStudent  //NewStudent 对应着 Student表
列名:
    modelBuilder.Entity<NewStudent>().Property(s => s.IsFemale)
        .HasColumnName("Gender");
    [Column("Gender")]
    public bool IsFemale { get; set; }

数据类型

EF对各种基本数据类型的映射都比较完美,比如C#的int自动对应SQL的int,bool对应bit……

日期

注意EF会将DateTime自动映射成SQL中的DateTime2。

这是因为C#中DateTime的默认值是0001-01-01 00:00:00.0000000

  • 和SQL Sever中的DateTime2匹配,
  • 和最小值为1753-01-01 的DateTime匹配

枚举

枚举会自动转为int类型演示:DayOfWeek Oncall

字符串

默认是nvarchar(max),不能建立索引。所以有时候会指定

最大长度,nvarchar(50)

[MaxLength(50)]
public string Name { get; set; }

或者固定长度,nchar(50)

[StringLength(50)]
public string Name { get; set; }

OnModelCreating()中没有StringLength,所以:

modelBuilder.Entity<Student>().Property(s => s.Name)
    .HasMaxLength(50)
    .IsFixedLength()     //指定固定长度

HasColumnType()

还可以用字符串直接指定(简单粗暴)

    modelBuilder.Entity<Student>()
        .Property(s => s.Oncall).HasColumnType("NVARCHAR(20)");

NULL Constraint

对于C#中有默认值的值类型属性(比如int),EF默认是映射为NOT NULL的;要想EF默认映射为可以NULL值,需要将其声明为可空类型,比如int?。

对于引用类型(如字符串),EF默认是映射为可以NULL,要想变成NOT NULL:

    modelBuilder.Entity<Student>()
        .Property(s => s.Name).IsRequired(); 
或者
    [Required]
    public string Name { get; set; }

CHECK约束

比如Age只能在0-150之间。你可以会认为使用Range特性即可:

    [Range(0, 150)]  //不会自动生成CHECK约束
    public int Age { get; set; }
但这是行的,需要在OnModelCreating中进行配置(EF core3.0之后版本):
    modelBuilder.Entity<Student>()
        .HasCheckConstraint("CK_Age","Age Between 0 AND 150")

演示查看生成的SQL

ALTER TABLE [dbo].[TB_Student]
    ADD CONSTRAINT [CK_Age] CHECK ([Age]>=(0) AND [Age]<=(150));

Primary Key

EF默认将Entity中的Id或者<entity>Id作为主键。如果我们要指定其他主键(不推荐),可以:

            modelBuilder.Entity<Student>()
                .HasKey(s => s.Name);

或者:

        [Key]
        public string Name { get; set; }

联合主键因为要跨多列,所以只能设置在OnModelCreating()中:(后文详述)

    modelBuilder.Entity<StudentAndTeacher>()
        .HasKey(st => new { st.TeacherId, st.StudentId });

Index

除了主键,EF不会自动添加索引。

我们需要自己添加:

        modelBuilder.Entity<Student>()
            .HasIndex(s => s.Name)
            .IsUnique();   //默认是非唯一的
    modelBuilder.Entity<Student>()
        //联合键索引
        .HasIndex(s=>new {s.Name, s.Age })

或者

 [Index("Name", IsUnique = true)]
    public class Student

排除

默认所有的属性都会被映射。

如果想要排除掉一些属性,可以使用:

    modelBuilder.Entity<Student>().Ignore(s => s.Age);

甚至可以直接ignore掉整个Entity:

    modelBuilder.Ignore<Student>();

或者

        [NotMapped]
        public string Age { get; set; }


作业

ORM概述第1/2/3题


学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ADO&EF 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码