Skip to content

9.2 数据库上下文

📝 模块更新日志 新特性*

+ 新增 `[AppDbContext]` 特性支持 `UseSnakeCaseNaming` 属性配置表名使用蛇形命名 4\.9\.1\.5 ⏱️2023\.11\.20 [\#I8HGR2](https://gitee.com/dotnetchina/Furion/issues/I8HGR2) [!863](https://gitee.com/dotnetchina/Furion/pulls/863)
+ 新增 `Db.GetNewDbContext()` 多个重载方法,实现类似 `new DbContext()` 操作 4\.8\.8\.55 ⏱️2023\.11\.09 [4157629](https://gitee.com/dotnetchina/Furion/commit/41576295473fefc47f6961154909a690d7c5ed58)

连接字符串配置注意事项如果连接字符串是配置在自定义的 .json 文件中,那么必须在 Visual Studio 中配置 .json 右键属性,设置 复制 输出目录为 如果较新则复制,生成操作为:内容

否则就会提示找不到配置或连接字符串的错误。

9.2.1 数据库上下文

简单来说,数据库上下文是负责和数据库交互的对象,提供程序对数据库存取提供了大量的方法。

Furion 框架中,默认集成了微软亲儿子:EntityFramework Core ,也就是通常数据库上下文指的是 DbContext 类或它的实现类。

9.2.2 AppDbContext

在我们实际项目开发过程中,使用 EFCore 提供的 DbContext 操作对象操作数据库有些繁琐和复杂,且默认不具备读写分离、多库等操作功能。

所以,Furion 框架提供了 AppDbContext<TDbContext, TDbContextLocator> 数据库上下文,该上下文继承自 DbContext

特别说明后续章节,皆采用 EFCore 代替 EntityFramework Core

9.2.3 AppDbContextDbContext 区别

  • AppDbContext 继承自 DbContext,具备 DbContext 所有功能。
  • AppDbContext 支持多数据库操作泛型版本,如:AppDbContext<TDbContext, TDbContextLocator>
  • AppDbContext 自动配置实体信息,无需在 OnModelCreating 中配置
  • AppDbContext 支持内置多租户支持
  • AppDbContext 支持全局模型配置拦截器
  • AppDbContext 支持数据提交更改多个事件
  • AppDbContext 提供更加强大的模型操作能力,如 Sql 操作,读写分离等
  • AppDbContext 能够得到 Furion 框架更多的功能支持

9.2.4 如何定义数据库上下文

Furion 框架中了,提供了两种 AppDbContext 定义方式:

  • AppDbContext<TDbContext> 操作默认数据库
  • AppDbContext<TDbContext, TDbContextLocator> 操作 N 个数据库

其中 AppDbContext<TDbContext> 默认继承自 AppDbContext<TDbContext, TDbContextLocator>

下面是数据库上下文创建的多个例子:

9.2.4.1 创建默认数据库上下文

using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  

namespace Furion.EntityFramework.Core  
{  
    [AppDbContext("连接字符串或appsettings.json 键")]  
    public class FurionDbContext : AppDbContext<FurionDbContext>  // 继承 AppDbContext<> 类  
    {  
        /// <summary>  
        /// 继承父类构造函数  
        /// </summary>  
        /// <param name="options"></param>  
        public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options)  
        {  
        }  
    }  
}  

9.2.4.2 创建其他数据库上下文

using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  

namespace Furion.EntityFramework.Core  
{  
    [AppDbContext("连接字符串或appsettings.json 键")]  
    public class FurOtherDbContext : AppDbContext<FurOtherDbContext, FurOtherDbContextLocator>  // 继承 AppDbContext<> 类  
    {  
        /// <summary>  
        /// 继承父类构造函数  
        /// </summary>  
        /// <param name="options"></param>  
        public FurOtherDbContext(DbContextOptions<FurOtherDbContext> options) : base(options)  
        {  
        }  
    }  
}  

特别注意所有数据库上下文都应该在 Furion.EntityFramework.Core 项目中创建。另外如果系统用到了多个数据库,那么从第二个开始必须指定数据库上下文定位器。关于 TDbContextLocator 将在下一章节 《9.3 数据库上下文定位器》阐述。

9.2.5 配置连接字符串

Furion 框架提供多种数据库连接字符串配置方式:

  • 在注册数据库服务时配置:AddDbPool<TDbContext>("连接字符串") 方式
  • 使用 [AppDbContext("连接字符串/Key")] 特性方式(只在 AppDbContext 实现类有效推荐
  • 通过重写 OnConfiguring(DbContextOptionsBuilder optionsBuilder) 配置

9.2.5.1 在注册数据库服务时配置

Furion.EntityFramework.Core\Startup.cs

using Furion.DependencyInjection;  
using Microsoft.Extensions.DependencyInjection;  

namespace Furion.EntityFramework.Core  
{  
    [AppStartup(600)]  
    public sealed class FurEntityFrameworkCoreStartup : AppStartup  
    {  
        public void ConfigureServices(IServiceCollection services)  
        {  
            // 配置数据库上下文,支持N个数据库  
            services.AddDatabaseAccessor(options =>  
            {  
                // 配置默认数据库  
                options.AddDbPool<FurionDbContext>(DbProvider.SqlServer, connectionMetadata:"连接字符串");  

                // 配置多个数据库,多个数据库必须指定数据库上下文定位器  
                options.AddDbPool<SqliteDbContext, SqliteDbContextLocaotr>(DbProvider.Sqlite, connectionMetadata:"连接字符串");  
            });  
        }  
    }  
}  

新版 MySQL 注意MySQL 在新版本包中注册有所修改,所以注册方式为:

services.AddDatabaseAccessor(options =>  
{  
    options.AddDbPool<FurionDbContext>($"{DbProvider.MySql}@8.0.22");  
});  

9.2.5.2 [AppDbContext] 方式配置

using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  

namespace Furion.EntityFramework.Core  
{  
    [AppDbContext("DbConnectionString")]   // 支持 `appsettings.json` 名或 连接字符串  
    public class FurionDbContext : AppDbContext<FurionDbContext>  
    {  
        /// <summary>  
        /// 继承父类构造函数  
        /// </summary>  
        /// <param name="options"></param>  
        public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options)  
        {  
        }  
    }  
}  

小提示Furion 推荐使用此方式配置数据库连接字符串。

[AppDbContext] 内置属性:

  • ConnectionMetadata:支持数据库连接字符串,配置文件的 ConnectionStrings 中的 Key 或配置文件的完整的配置路径,如果是内存数据库,则为数据库名称。
  • TablePrefix:当前数据库上下文表统一前缀
  • TableSuffix:当前数据库上下文表统一后缀
  • ProviderName:配置数据库提供器类型,传入 DbProvider.Xxx
  • Mode:配置数据库上下文模式,DbContextMode 枚举类型,取值:
    • Cached:缓存模型数据库上下文,默认值
    • Dynamic:动态模型数据库上下文
  • SlaveDbContextLocators:主从库配置,设置多个从库定位器,Type[] 类型
  • UseSnakeCaseNaming:表名使用蛇形命名,bool 类型,默认 falseFurion 4.9.1.5+ 版本支持,需配合 EFCore.NamingConventions 库使用

9.2.5.3 OnConfiguring 方式配置

using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  

namespace Furion.EntityFramework.Core  
{  
    public class FurionDbContext : AppDbContext<FurionDbContext>  
    {  
        /// <summary>  
        /// 继承父类构造函数  
        /// </summary>  
        /// <param name="options"></param>  
        public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options)  
        {  
        }  

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)  
        {  
            base.OnConfiguring(optionsBuilder);  

            optionsBuilder.UseSqlServer("数据库连接字符串");  
        }  
    }  
}  

特别注意这三种方式可以同时使用,但是有优先级:[AppDbContext] -> 在注册数据库服务时配置 -> OnConfiguring(低到高)

也就是 OnConfiguring 配置会覆盖 在注册数据库服务时配置 配置,在注册数据库服务时配置 配置会覆盖 [AppDbContext] 配置所配置的连接字符串。

9.2.5.4 各类数据库连接字符串配置示例

  • SqliteData Source=./Furion.db
  • MySqlData Source=localhost;Database=Furion;User ID=root;Password=000000;pooling=true;port=3306;sslmode=none;CharSet=utf8;
  • SqlServerServer=localhost;Database=Furion;User=sa;Password=000000;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=True;
  • OracleUser Id=orcl;Password=orcl;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orcl)))
  • PostgreSQLPORT=5432;DATABASE=postgres;HOST=127.0.0.1;PASSWORD=postgres;USER ID=postgres;

9.2.6 数据库上下文定义位置

特别注意在 Furion 框架中,数据库上下文需定义在 Furion.EntityFramework.Core 中,且每一个数据库上下文都必须拥有唯一的 DbContextLocator 定位器

9.2.7 数据库上下文注册

数据库上下文配置好数据库连接字符串后,需要注册该数据库上下文,并指定数据库类型,如:

Furion\framework\Furion.EntityFramework.Core\FurEntityFrameworkCoreStartup.cs

using Furion.DatabaseAccessor;  
using Microsoft.Extensions.DependencyInjection;  

namespace Furion.EntityFramework.Core  
{  
    [AppStartup(600)]  
    public sealed class FurEntityFrameworkCoreStartup : AppStartup  
    {  
        public void ConfigureServices(IServiceCollection services)  
        {  
            services.AddDatabaseAccessor(options =>  
            {  
                options.AddDbPool<FurionDbContext>(DbProvider.Sqlite);  
            });  
        }  
    }  
}  

如果有多个数据库操作,那么从第二个起,就需要绑定数据库上下文定位器,如:

options.AddDbPool<FurionDbContext>(DbProvider.Sqlite); // 第一个数据库  

options.AddDbPool<SecondDbContext, SecondDbContextDbContextLocator>(DbProvider.SqlServer);  // 第二个数据库  

options.AddDbPool<ThirdDbContext, ThirdDbContextDbContextLocator>(DbProvider.SqlServer);  // 第三个数据库  

9.2.8 自定义高级注册

Furion 框架中,为了能够实现数据库的简单使用进行了注册封装,但有些时候,我们可能需要添加更多配置,这时就需要使用原生自定义配置方式,如:

services.AddDatabaseAccessor(options =>  
{  
    // 自定义原生配置  
    options.AddDb<YourDbContext>((services, builder) =>  
    {  
        builder.UseSqlite(...);  
    }  
});  

小知识Furion 框架提供了快速解析连接字符串的静态方法,自动根据名称读取配置,自动解析 [AppContext("...")] 信息,如:

// 获取连接字符串  
var connStr = DbProvider.GetConnectionString<YourDbContext>(/*这里可写可不写*/);  

options.AddDb<YourDbContext>((services, builder) =>  
{  
    builder.UseSqlite(connStr, ...);  
}  

9.2.9 动态数据库上下文对象

Furion 框架中,数据库上下文是定义在 Furion.EntityFramework.Core 项目层,并且该层不被 Furion.ApplicationFurion.Core 等层引用。

所以就不能直接在 Furion.Application 项目层直接使用 Furion.EntityFramework.Core 定义的数据库上下文。

Furion 为了解决这个问题,提供了两种方式处理:

  • repository.Context :当前数据库上下文对象,返回是 DbContext 抽象类型
  • repository.DynamicContext:当前数据库上下文对象,返回的是 dynamic 类型

如果你只是想使用 DbContext 的功能,直接使用 repository.Context 即可,如:

repository.Context.SaveChanges();  

如果你想能够获取具体的数据库上下文类型,如 MyDbContext,那么使用 repository.DynamicContext 就可以获取到具体的 MyDbContext 类型。如:

var persons = repository.DynamicContext.Persons.Find(1);  
var users = repository.DynamicContext.Users;  

这样就可以直接操作 MyDbContext 定义的属性和方法了。

9.2.10 在后台任务中使用

由于 DbContext 默认注册为 Scoped 生存周期,所以在后台任务中使用 IServiceScopeFactory 获取所有服务,如:

public class JobService : BackgroundService  
{  
    // 日志对象  
    private readonly ILogger<JobService> _logger;  

    // 服务工厂  
    private readonly IServiceScopeFactory _scopeFactory;  
    public JobService(ILogger<JobService> logger  
        , IServiceScopeFactory scopeFactory)  
    {  
        _logger = logger;  
        _scopeFactory = scopeFactory;  
    }  

    protected override Task ExecuteAsync(CancellationToken stoppingToken)  
    {  
        _logger.LogInformation("写日志~~");  

        using (var scope = _scopeFactory.CreateScope())  
        {  
            var services = scope.ServiceProvider;  

            // 获取数据库上下文  
            var dbContext = Db.GetDbContext(services);  

            // 获取仓储  
            var repository = Db.GetRepository<Person>(services);  

            // 解析其他服务  
            var otherService = services.GetService<XXX>();  
        }  

        return Task.CompletedTask;  
    }  
}  

数据库操作注意如果作用域中对数据库有任何变更操作,需手动调用 SaveChanges 或带 Now 结尾的方法。也可以使用 Scoped.CreateUow(handler) 代替。

9.2.11 AppDbContext 全局配置属性

  • InsertOrUpdateIgnoreNullValues:新增或更新忽略空值,默认 false在构造函数中配置
  • EnabledEntityStateTracked:启用实体跟踪,默认 true在构造函数中配置
  • EnabledEntityChangedListener:启用实体数据更改监听,默认 false在构造函数中配置
  • Tenant:默认内置多租户
  • FailedAutoRollback:是否启用保存失败后事务自动回滚,默认 true,可以在任何地方配置

9.2.12 配置实体 懒加载

  • 第一步:安装 EFCore 拓展包

在数据库上下文定义所在的层安装 Microsoft.EntityFrameworkCore.Proxies 拓展包

  • 第二步:采用 AddDb<TDbContext> 方式注册

确保数据库上下文采用 AddDb<TDbContext> 注册而非 AddDbPool<TDbContext>

  • 第三步:重写 OnConfiguring 方法
using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  

namespace Furion.EntityFramework.Core  
{  
    [AppDbContext("Sqlite3ConnectionString", DbProvider.Sqlite)]  
    public class DefaultDbContext : AppDbContext<DefaultDbContext>  
    {  
        public DefaultDbContext(DbContextOptions<DefaultDbContext> options) : base(options)  
        {  
        }  

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)  
        {  
            optionsBuilder.UseLazyLoadingProxies()  
                          .UseSqlite(DbProvider.GetConnectionString<DefaultDbContext>());  

            base.OnConfiguring(optionsBuilder);  
        }  
    }  
}  

小知识更多 EFCore 懒加载可查看 【EFCore - 延迟加载】 文档。

9.2.13 Db 静态类获取上下文

Db 静态类提供了多种获取 DbContext 上下文的方法,如:

// 获取默认数据库上下文  
var dbContext = Db.GetDbContext();  

// 获取定位器数据库上下文  
var locatorDbContext = Db.GetDbContext<TDbContextLocator>();  
var locatorDbContext2 = Db.GetDbContext(typeof(TDbContextLocator));  

// 创建新的默认数据库上下文(相当于 new YourDbContext)  
using var dbContext = Db.GetNewDbContext(); // 别忘记 using,Furion 4.8.8.55+ 版本有效  

// 创建新的定位器数据库上下文(相当于 new YourDbContext)  
using var locatorDbContext = Db.GetNewDbContext<TDbContextLocator>(); // 别忘记 using,Furion 4.8.8.55+ 版本有效  
using var locatorDbContext2 = Db.GetNewDbContext(typeof(TDbContextLocator)); // 别忘记 using,Furion 4.8.8.55+ 版本有效  

特别注意默认情况下 Db.GetDbContext(...) 创建数据库上下文的作用域和 Web 相同,如果您在后台线程或多线程中使用,那么必须传入第二个 serviceProvider 参数。

也可以通过 Db.GetNewDbContext(...) 的方式获取一个新的数据库上下文,但是需要手动 using 释放。

9.2.14 反馈与建议

与我们交流给 Furion 提 Issue


了解更多想了解更多 数据库上下文 知识可查阅 EF Core - 配置 DbContext 章节。