9.1 数据库上下文
连接字符串配置注意事项
如果连接字符串是配置在自定义的 .json
文件中,那么必须在 Visual Studio
中配置 .json
右键属性,设置 复制
输出目录为 如果较新则复制
,生成操作为:内容
。
否则就会提示找不到配置或连接字符串的错误。
9.1.1 数据库上下文
简单来说,数据库上下文是负责和数据库交互的对象,提供程序对数据库存取提供了大量的方法。
在 Furion
框架中,默认集成了微软亲儿子:EntityFramework Core
,也就是通常数据库上下文指的是 DbContext
类或它的实现类。
9.1.2 AppDbContext
在我们实际项目开发过程中,使用 EFCore
提供的 DbContext
操作对象操作数据库有些繁琐和复杂,且默认不具备读写分离、多库等操作功能。
所以,Furion
框架提供了 AppDbContext<TDbContext, TDbContextLocator>
数据库上下文,该上下文继承自 DbContext
。
特别说明
后续章节,皆采用 EFCore
代替 EntityFramework Core
。
9.1.3 AppDbContext
和 DbContext
区别
AppDbContext
继承自DbContext
,具备DbContext
所有功能。AppDbContext
支持多数据库操作泛型版本,如:AppDbContext<TDbContext, TDbContextLocator>
AppDbContext
自动配置实体信息,无需在OnModelCreating
中配置AppDbContext
支持内置多租户支持AppDbContext
支持全局模型配置拦截器AppDbContext
支持数据提交更改多个事件AppDbContext
提供更加强大的模型操作能力,如Sql
操作,读写分离等AppDbContext
能够得到Furion
框架更多的功能支持
9.1.4 如何定义数据库上下文
在 Furion
框架中了,提供了两种 AppDbContext
定义方式:
AppDbContext<TDbContext>
操作默认数据库AppDbContext<TDbContext, TDbContextLocator>
操作 N 个数据库
其中 AppDbContext<TDbContext>
默认继承自 AppDbContext<TDbContext, TDbContextLocator>
。
下面是数据库上下文创建的多个例子:
9.1.4.1 创建默认数据库上下文
using Furion.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
namespace Furion.EntityFramework.Core
{
[AppDbContext("连接字符串或appsetting.json 键")]
public class FurionDbContext : AppDbContext<FurionDbContext> // 继承 AppDbContext<> 类
{
/// <summary>
/// 继承父类构造函数
/// </summary>
/// <param name="options"></param>
public FurionDbContext(DbContextOptions<FurionDbContext> options) : base(options)
{
}
}
}
9.1.4.2 创建其他数据库上下文
using Furion.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
namespace Furion.EntityFramework.Core
{
[AppDbContext("连接字符串或appsetting.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.2 数据库上下文定位器》阐述。
9.1.5 配置连接字符串
Furion
框架提供多种数据库连接字符串配置方式:
- 在注册数据库服务时配置:
AddDbPool<TDbContext>("连接字符串")
方式 - 使用
[AppDbContext("连接字符串/Key")]
特性方式(只在AppDbContext 实现类有效
)推荐 - 通过重写
OnConfiguring(DbContextOptionsBuilder optionsBuilder)
配置
9.1.5.1 在注册数据库服务时配置
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.1.5.2 [AppDbContext]
方式配置
using Furion.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
namespace Furion.EntityFramework.Core
{
[AppDbContext("DbConnectionString")] // 支持 `appsetting.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[]
类型
9.1.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.1.5.4 各类数据库连接字符串配置示例
Sqlite
:Data Source=./Furion.db
MySql
:Data Source=localhost;Database=Furion;User ID=root;Password=000000;pooling=true;port=3306;sslmode=none;CharSet=utf8;
SqlServer
:Server=localhost;Database=Furion;User=sa;Password=000000;MultipleActiveResultSets=True;
Oracle
:User Id=orcl;Password=orcl;Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orcl)))
PostgreSQL
:PORT=5432;DATABASE=postgres;HOST=127.0.0.1;PASSWORD=postgres;USER ID=postgres;
9.1.6 数据库上下文定义位置
特别注意
在 Furion
框架中,数据库上下文需定义在 Furion.EntityFramework.Core
中,且每一个数据库上下文都必须拥有唯一的 DbContextLocator
定位器
9.1.7 数据库上下文注册
数据库上下文配置好数据库连接字符串后,需要注册该数据库上下文,并指定数据库类型,如:
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.1.8 自定义高级注册
在 Furion
框架中,为了能够实现数据库的简单使用进行了注册封装,但有些时候,我们可能需要添加更多配置,这时就需要使用原生自定义配置方式,如:
services.AddDatabaseAccessor(options =>
{
// 自定义原生配置
options.AddDb<YourDbContext>(builder =>
{
builder.UseSqlite(...);
}
});
小知识
Furion
框架提供了快速解析连接字符串的静态方法,自动根据名称读取配置,自动解析 [AppContext("...")]
信息,如:
// 获取连接字符串
var connStr = DbProvider.GetConnectionString<YourDbContext>(/*这里可写可不写*/);
options.AddDb<YourDbContext>(builder =>
{
builder.UseSqlite(connStr, ...);
}
9.1.9 动态数据库上下文对象
在 Furion
框架中,数据库上下文是定义在 Furion.EntityFramework.Core
项目层,并且该层不被 Furion.Application
和 Furion.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.1.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.1.11 AppDbContext
全局配置属性
InsertOrUpdateIgnoreNullValues
:新增或更新忽略空值,默认false
,在构造函数中配置EnabledEntityStateTracked
:启用实体跟踪,默认true
,在构造函数中配置EnabledEntityChangedListener
:启用实体数据更改监听,默认false
,在构造函数中配置Tenant
:默认内置多租户FailedAutoRollback
:是否启用保存失败后事务自动回滚,默认true
,可以在任何地方配置
9.1.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.1.13 反馈与建议
与我们交流
给 Furion 提 Issue。
了解更多
想了解更多 数据库上下文
知识可查阅 EF Core - 配置 DbContext 章节。