ORM

ORM(Object Relational Mapping)中文翻译是对象关系映射,一般是指实现对象形式访问数据库的框架。在早期的程序开发中,访问数据库需要程序员编写 SQL 语句并交给数据库系统执行(比如 ADO.NET),而 ORM 则提供了一种让程序员可以通过对象来读写数据库内容的方式,并对这些读写操作自动生成 SQL 语句并交给数据库系统执行,使得程序员可以更加关注数据的实体逻辑,面向实体模型开发而不是面向数据库开发。EFCore 就是 .NET 平台的由微软官方出品的一个 ORM 框架。

EF Core

EFCore(EntityFramework Core)是 .NET Core 平台的一个 ORM 框架,支持绝大部分主流数据库。和其他 ORM 框架不同,EFCore 在对象映射上做得非常彻底,几乎实现了所有的数据库读写到对象操作的映射。使用 EFCore 的基本流程是:创建实体类、创建实体配置类、创建数据库上下文(DbContext)、生成数据库。

以 SQLServer 为例,创建实体类,其实就是创建数据映射的实体模型,之后对数据的操作都是对该实体类的对象进行操作。在后面生成数据库时,实体类的属性会与数据库中的数据列进行映射,例如下面创建的一个书本(Book)实体类:

using System;
using System.Collections.Generic;
using System.Text;

namespace EFCore
{
    class Book
    {
        public long Id { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public DateTime Time { get; set; }
        public double Price { get; set; }
    }
}

目前这个类看起来和正常的 C# 类是没有区别的。将这个类与数据库联系起来的是实体配置类,实体配置类需要实现 IEntityTypeConfiguration 泛型接口,并传入实体类类型,该接口定义了 Configure 方法,用于配置实体类与数据之间的关系:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Text;

namespace EFCore
{
    class BookConfig : IEntityTypeConfiguration<Book> //传入实体类
    {
        public void Configure(EntityTypeBuilder<Book> builder)
        {
            builder.ToTable("T_Books"); //设置数据表
        }
    }
}

在上面的代码中,使用 EntityTypeBuilder.ToTable 方法设置了实体类映射的数据表,即 Book 实体会映射数据库中的 T_Books 表。这种通过实体配置类的配置方式叫做 FluentAPI(还有另一种通过 Attribute 标注的配置方式叫做 Data Annotation,这里不述)。在实体配置类中,没有手动指定的配置一般都按照 EFCore 的约定,这里列出最常用的几个:

  • 属性自动映射到与属性同名的数据列
  • 名字为 Id 的属性将会被视为主键
  • short、int、long 类型映射的主键自动自增
  • 属性映射的数据列会自动采用最兼容属性类型的数据类型
  • 属性映射的数据列可空性与属性类型的可空性保持一致

比如,上面 Book 类的 Id 属性会映射到 Id 数据列,并被视为主键,并且在 SQLServer 中映射到 bigint 类型,并自动自增。而 Title 和 Author 属性会映射到 nvarchar(MAX) 类型,并可空。

接下来是创建数据库上下文,这里需要创建一个继承自 DbContext 的一个类,用来管理整个数据库的映射。先看下面的代码:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.EntityFrameworkCore;

namespace EFCore
{
    class MyDbContext : DbContext
    {
        public DbSet<Book> Books { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string connStr = @"Data source=localhost;database=Test;Integrated Security=SSPI;";
            base.OnConfiguring(optionsBuilder);
            optionsBuilder.UseSqlServer(connStr);

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }

    }
}

注意到这里重写了 DbContext 的两个方法,分别是 OnConfiguringOnModelCreating。简单来说,OnConfiguring 用来配置数据库的连接等,这里就通过 UseSqlServer 方法连接被映射的数据库了(如果是其他数据库,则需要用对应的 UseXXXX 方法),由于我这里 SQLServer  使用 Windows 身份验证,所以连接字符串不需要账户和密码,如果是 MySQL 则需要另外写好。

而 OnModelCreating 则是用来配置实体模型的,上面我们已经创建好了 Book 类和 BookConfig 类,接下来需要将这个实体传递给 ModelBuilder。这里代码使用 ApplyConfigurationsFromAssembly 方法,从当前程序集中接收所有实体,ModelBuilder 会检索当前程序集中所有继承了 IEntityTypeConfiguration 接口的类,并载入其实体配置。

除此之外,DbContext 还作为程序开发中实体对象集储存的地方,所以需要声明各个实体的 DbSet 作为其属性。例如

public DbSet<Book> Books { get; set; }

这一行代码就声明了 Book 实体对应的对象集,相当于数据库的一张表,通过 ORM 映射之后,数据库中该表的每一行数据,就和该对象集中的某一个对象对应,之后程序员只需对该对象集中的对象进行读写,就能改变数据库中对应的数据了,而背后的 SQL 语句则是由 EFCore 生成的。如果有多个实体,则需要声明多个 DbSet。这里 DbContext 就相当于一个逻辑上的数据库,而 DbSet 就相当于逻辑上的数据表,并且按照约定,在未手动指定时(上面的代码通过 ToTable 方法手动指定了),DbSet 的属性名将作为生成数据库时对应的表名。

但是在舒适地读写之前,我们还有最后一步要做,那就是生成数据库,见下一节。