数据访问

数据库是应用服务开发的基础与核心。框架本身使用Entity Framework Core作为数据访问的ORM框架,提供了对数据库的访问能力。

使用Entity Framework Core不仅仅是为了方便,更重要的是规范了数据开发和访问的方式。

我们以Code First的方式来定义数据模型,然后使用DbContext来访问数据库。

数据库上下文

模板默认使用DefaultDbContext作为数据访问的上下文,它继承了ContextBase.cs

你可以定义自己的DbContext,并继承ContextBase。数据库上下文集中在Definition/EntityFrameworkCore/DBProvider目录下。

数据操作

数据查询是业务逻辑中的重要部分,业务代码通常在XXXManager中实现,它继承了ManagerBase<TDbContext,TEntity>,如:

public class UserManager(
    DefaultDbContext dbContext,
    ILogger<UserManager> logger
) : ManagerBase<DefaultDbContext, User>(dbContext, 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类,会通过源代码生成器生成注入的代码。

租户模式(预览)

在有些场景下,应用需要支持多租户,比如SaaS应用。不同的租户使用不同的数据库,这样可以保证数据的隔离性。也能为不同租户提供不同的功能和服务。

框架默认提供了ITenantProvider接口来获取租户信息。

开发者需要使用租户信息,来构建DbContext,框架提供了TenantDbFactory类来根据租户信息来创建DbContext。要实现基于租户的Manager,要注入TenantDbContextFactory并继承ManagerBase即可, 示例代码如下:

public class TestManager(TenantDbContextFactory factory, ILogger<TestManager> logger)
    : ManagerBase<User>(factory, logger)
{
}

Tip

你可以根据实际需求修改TenantDbFactory中的创建数据库上下文的逻辑。

多库操作(预览)

当你的逻辑需要操作多个数据库时,可以注入DbContextFactory服务,然后操作不同的数据库。

public class TestManager(
    DbContextFactory dbFactory,
    ILogger<TestManager> logger)
    : ManagerBase<User>(factory, 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);
    }
}

DbContextFactory默认会根据你的数据库上下文名称获取对应的连接字符串,并创建对应的DbContext实例。

var contextName = typeof(TContext).Name;
 if (contextName.EndsWith("DbContext"))
 {
     contextName = contextName[..^"DbContext".Length];
 }
 var connectionStrings = configuration.GetConnectionString(contextName);

你可以修改DbContextFactory的实现,以满足你实际创建DbContext的需求。

Note

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