数据库是应用服务开发的基础与核心。模板使用Entity Framework Core作为数据访问的ORM框架,同时使用BulkExtensions来优化大批量数据操作的性能。
使用Entity Framework Core不仅是为了方便,更重要的是规范了数据开发和访问的方式。
建议使用Code First的方式来定义数据模型,然后使用DbContext来访问数据库。
模板默认使用DefaultDbContext作为数据访问的上下文,它继承了ContextBase.cs。
你可以定义自己的DbContext,并继承ContextBase。数据库上下文集中在Definition/EntityFrameworkCore/AppDbContext目录下。
为了兼容多租户场景,默认将会使用TenantDbFactory来创建数据库上下文实例,如果你的应用不需要多租户支持,可以直接注入DefaultDbContext。
数据查询是业务逻辑中的重要部分,业务代码通常在XXXManager中实现,它继承了ManagerBase<TDbContext,TEntity>,如:
public class AIAgentManager( TenantDbFactory dbContextFactory, ILogger<AIAgentManager> logger, IUserContext userContext ) : ManagerBase<DefaultDbContext, AIAgent>(dbContextFactory, userContext, logger) { }
ManagerBase<TDbContext,TEntity>,提供了一些针对当前实体的封装好的数据操作方法。当然你也可以完全使用_dbContext来实现数据操作,父类提供了以下属性:
protected IQueryable<TEntity> Queryable { get; set; } protected readonly ILogger _logger; protected readonly TDbContext _dbContext; protected readonly DbSet<TEntity> _dbSet;
如上,其中Queryable默认使用AsNoTracking()的查询方式,避免数据追踪。
Important
请不要在Controller中直接使用DbContext,而是通过继承ManagerBase来实现业务逻辑中的数据操作。这样可以保证业务逻辑的清晰和可维护性。
当你的Manager不涉及到数据库操作,或不限于某个数据库上下文或实体时,你可以继承ManagerBase(ILogger logger),它不依赖任何数据库上下文或实体。
public class TestManager(MyDbContext context, MyService service, ILogger<TestManager> logger) : ManagerBase(logger) { }
Important
继承ManagerBase类,会通过源代码生成器生成注入的代码。
模板默认会使用TenantDbFactory来创建数据库上下文实例,以支持多租户场景,它会通过ITenantContext来获取 当前租户的信息,然后创建对应的数据库上下文。
Tip
你可以根据实际需求修改TenantDbFactory中的创建数据库上下文的逻辑。
当你的逻辑需要操作多个数据库时,可以注入UniversalDbFactory服务,然后操作不同的数据库。
public class TestManager( UniversalDbFactory dbFactory, ILogger<TestManager> logger) : ManagerBase(logger) { public async Task MultiDatabase() { var mssqlDb = dbFactory.CreateDbContext<MainDbContext>(); mssqlDb.Database.SetCommandTimeout(30); var tenant = await mssqlDb.Tenants.FirstOrDefaultAsync(); var pgsqlDb = dbFactory.CreateDbContext<AnotherDbContext>(DatabaseType.PostgreSql); pgsqlDb.Database.SetCommandTimeout(30); var user = await pgsqlDb.Tenants.FirstOrDefaultAsync(u => u.TenantId == tenant.TenantId); } }
UniversalDbFactory默认会根据你的数据库上下文名称获取对应的连接字符串,并创建对应的DbContext实例,你可查看UniversalDbFactory中的代码逻辑,如:
var contextName = typeof(TContext).Name; if (contextName.EndsWith("DbContext")) { contextName = contextName[..^"DbContext".Length]; } var connectionStrings = configuration.GetConnectionString(contextName);
你可以修改UniversalDbFactory的实现,以满足你实际创建DbContext的需求。
Note
多库操作难以保证数据的一致性,耦合性较高,业务理解复杂,应尽量避免该情况。建议通过服务间调用,或使用消息队列来实现跨库操作,以保持最终一致性。
内容大纲