数据库是应用服务开发的基础与核心。框架本身使用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
多库操作难以保证数据的一致性,耦合性较高,业务理解复杂,应尽量避免该情况。建议通过服务间调用,或使用消息队列来实现跨库操作,以保持最终一致性。