Blogs
📆 2025-12-19 00:45

数据访问

数据库是应用服务开发的基础与核心。模板使用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

多库操作难以保证数据的一致性,耦合性较高,业务理解复杂,应尽量避免该情况。建议通过服务间调用,或使用消息队列来实现跨库操作,以保持最终一致性。