ASP.NET Core使用EFCore

ASP.NET Core分层结构项目里使用EF Core

  1. 创建一个类库项目,引入需要的包,注意选择与自己项目版本匹配的

    1
    2
    3
    Install-Package Microsoft.EntityFrameworkCore
    Install-Package Npgsql.EntityFrameworkCore.PostgreSQL //使用PostgreSQL
    Install-Package Microsoft.EntityFrameworkCore.Tools

    注意安装的包版本需要匹配你的框架版本

    用的数据库需要什么包,可以看微软官方文档这篇:数据库提供程序

  2. 建立一个 DbContext

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class HolyGrailWarDbContext:DbContext
    {
    public HolyGrailWarDbContext(DbContextOptions<HolyGrailWarDbContext> options) :base(options)
    {
    }

    public DbSet<Master> Masters { get; set; }
    }

    public class Master
    {
    public int MasterId { get; set; }
    public string Name { get; set; }
    }

    注意构造函数的写法,以及不重写 OnConfiguring 方法

  3. 创建一个ASP.NET Core WebApi项目,引入需要的包

    1
    2
    3
    Install-Package Microsoft.EntityFrameworkCore
    Install-Package Npgsql.EntityFrameworkCore.PostgreSQL
    Install-Package Microsoft.EntityFrameworkCore.Tools
  4. 注册

    1
    2
    3
    string strConn = @"Host=localhost;Database=HolyGrailWar;Username=postgres;Password=password";
    services.AddDbContext<HolyGrailWarDbContext>(options =>
    options.UseNpgsql(strConn));
  5. 注入使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    [Route("api/[controller]")]
    [ApiController]
    public class MasterController : ControllerBase
    {
    private readonly HolyGrailWarDbContext holyGrailWarDbContext;

    public MasterController(HolyGrailWarDbContext holyGrailWarDbContext)
    {
    this.holyGrailWarDbContext = holyGrailWarDbContext;
    }

    [HttpGet("/GetMasters")]
    public IEnumerable<Master> GetMasters()
    {
    return holyGrailWarDbContext.Masters;
    }

    [HttpPut("/AddMasters")]
    public IActionResult AddMasters()
    {
    var master1 = holyGrailWarDbContext.Masters.Add(new Master { Name = $"Shirou Emiya" });
    var master2 = holyGrailWarDbContext.Masters.Add(new Master { Name = $"Osaka Rin" });
    var master3 = holyGrailWarDbContext.Masters.Add(new Master { Name = $"Illyasviel von Einzbern" });
    holyGrailWarDbContext.SaveChanges();

    string str = string.Empty;
    var masters = holyGrailWarDbContext.Masters;
    foreach (var master in masters)
    {
    str += $"ID:{master.MasterId,4}, Name:{master.Name} \n";
    }
    return Ok($"Add masters OK, Now has below masters: \n{str}");
    }
    }

迁移

上面还没有建立相应的数据库,我们需要使用 EF Core 的迁移命令进行

  1. 把 ASP.NET Core WebApi 项目设为启动项目,将 “程序包管理控制台” 的默认项目设为 DbContext 所在类库

  2. 在“程序包管理控制台”,运行命令Add-Migration,获得迁移脚本

    1
    Add-Migration InitialCreate //InitialCreate是迁移版本名称,可以自定义
  3. 在“程序包管理控制台”,运行命令Update-Database,更新数据库

    1
    Update-Database
  4. 查看数据库,可以看到相应的数据库与表都建立的,使用前面添加的控制器测试使用

在运行Add-MigrationUpdate-Database命令时候,可能有一些问题,按照错误查找解决办法即可

下面列举我遇到过的问题:

  • 问题:The EF Core tools version '3.1.21' is older than that of the runtime '3.1.22'. Update the tools for the latest features and bug fixes.

    解决:更新Microsoft.EntityFrameworkCore.Tools到相应版本

  • 问题:Update-DatabaseAdd-Migration生成的脚本报错

    解决:没有Microsoft.EntityFrameworkCore.Relational包,一般引入相应的SQL包就会包含了,如Postgresql的包Npgsql.EntityFrameworkCore.PostgreSQL就包含,实在没有就自己安装

  • 问题:Add-Migration时错误,More than one DbContext was found. Specify which one to use. Use the '-Context' parameter for PowerShell commands and the '--context' parameter for dotnet commands.

    解决:先在 ASP.NET Core WebApi 项目注册需要用的 DbContext,然后使用参数 ‘-Context’ 指定DbContext,如现在有两个 DbContext(FirstholyGrailWarDbContextSecondholyGrailWarDbContext)都要用,则依下步骤

    注册

    1
    2
    3
    4
    5
    string strConn = @"Host=localhost;Database=HolyGrailWar;Username=postgres;Password=password";
    services.AddDbContext<FirstholyGrailWarDbContext>(options =>
    options.UseNpgsql(strConn));
    services.AddDbContext<SecondholyGrailWarDbContext>(options =>
    options.UseNpgsql(strConn));

    迁移,有多个 DbContext 时两个命令都需要指定要用的 DbContext,其他相关命令也是

    1
    2
    3
    4
    5
    6
    7
    //FirstholyGrailWarDbContext
    Add-Migration InitialCreate -Context FirstholyGrailWarDbContext
    Update-Database -Context FirstholyGrailWarDbContext

    //SecondholyGrailWarDbContext
    Add-Migration InitialCreate -Context SecondholyGrailWarDbContext
    Update-Database -Context SecondholyGrailWarDbContext

慎用AddDbContextPool

AddDbContext与AddDbContextPool

DbContext 是非线程安全的,我们不能在同一DbContext实例上同时运行多个操作(Add、Update、Delete等),因此一般的做法就是在需要使用的时候就创建一个DbContext,待用完后就销毁掉,这就是AddDbContext的做法。

但是实际上我们是可以重用已创建的DbContext实例的,只是需要注意该DbContext前面的操作都需要已经完成了,这就是AddDbContextPool的做法。AddDbContextPool会保留多个已创建的DbContext且现在已经没被使用的实例到池,当请求需要DbContext时也会倾向于返回池里已有的DbContext多过创建一个新的。

使用AddDbContextPool问题

AddDbContextPool的做法看似很美好,能够避免DbContext实例创建时的性能消耗,但是实际使用中还是有些坑的:

  1. 用了AddDbContextPool那么你的DbContext就难以注入其他服务,因为使用AddDbContextPool创建的DbContext类似于 singleton 服务,导致只有同为 singleton 的服务才能注入。

  2. 有很多 ADO.NET 的提供者也实现了数据库连接池的机制,有可能于其冲突。

  3. 有其他坑会导致一些奇怪的问题,如

    EF Core 小坑:DbContextPool 会引起数据库连接池连接耗尽

    关于EFCore的上下文池DbContextPool和延迟加载LazyLoader冲突的探索

使用带“小上下文”策略的AddDbContext方法

鉴于上面AddDbContextPool的问题,推荐假如自认不能 hold 住的还是使用AddDbContext的方法,虽然性能差点,但是对于非高并发请求(1000req/1s以上)的应用,使用AddDbContext就足够了。

假如觉得不满足的话,可以使用“小DbContext”的策略,即把一个DbContext分拆成多个DbContext,如本身一个大 DbContext 里有100个 DbSet,但是拆分成20个 DbContext,这样平均一个 DbContext 就4个 DbSet,创建的消耗自然减少,使用多个 DbContext 步骤如下:

  1. 首先需要自己分割 DbContext,假设已分割完了有两个FirstDbContextSecondDbContext

  2. 注册

    1
    2
    services.AddDbContext<FirstDbContext>(options => options.UseSqlServer(strConnString));
    services.AddDbContext<SecondDbContext>(options => options.UseSqlServer(strConnString));
  3. 使用时注入,假设只注入FirstDbContext

    1
    2
    3
    4
    5
    private readonly FirstDbContext firstDbContext;
    public BlogController(FirstDbContext firstDbContext)
    {
    this.firstDbContext = firstDbContext;
    }

这种做法是 DDD 设计原则,把一个大的根据业务分拆成小的,也很符合微服务的概念。不过难点就在于如何拆分,分得不好,就会导致明明以前只需要一个 DbContex 就能干的,结果现在需要多个 DbContex,不但麻烦了,性能可能还降低的。