基于Unit of Work+Repository+Dapper构建ORM:外键模型设计疑问
用Dapper结合Unit of Work和Repository模式获取关联Entity的解决方案
嘿,我来帮你搞定这个问题~ 首先得明确:Dapper是轻量型ORM,不像EF那样自动处理导航属性关联,所以得咱们手动写查询和映射逻辑。针对你方案1的实体设计,我给你一步步拆解实现方式:
核心思路
Dapper本身不支持自动映射导航属性,所以我们需要:
- 写关联查询SQL,把Instance和Entity的字段都查出来
- 用Dapper的多映射功能,把查询结果拆分并映射到Instance和Entity对象里
- 在Repository层封装这个逻辑,再通过Unit of Work统一管理连接和事务
具体实现步骤
1. 确认数据库表结构
首先你的数据库里Instance表必须有一个外键字段(比如EntityId),关联Entity表的Id字段——这是关联查询的基础,你的实体类里虽然没显式定义EntityId,但数据库里肯定得有哈。
2. 实现InstanceRepository的查询方法
在你的InstanceRepository里,写一个带关联查询的方法,用Dapper的多映射重载来处理对象关联:
public class InstanceRepository : IInstanceRepository { private readonly IDbConnection _dbConnection; // 通过构造函数注入数据库连接(Unit of Work会负责提供这个连接) public InstanceRepository(IDbConnection dbConnection) { _dbConnection = dbConnection; } // 根据Id查询Instance并关联Entity public async Task<Instance> GetInstanceWithEntityAsync(int instanceId) { // 写关联查询SQL,用别名区分两个表的同名字段(比如Name) var querySql = @" SELECT i.Id AS InstanceId, i.Name AS InstanceName, e.Id AS EntityId, e.Name AS EntityName FROM Instance i INNER JOIN Entity e ON i.EntityId = e.Id WHERE i.Id = @InstanceId"; // 用字典来避免重复实例(如果是查询列表的话更有用) var instanceMap = new Dictionary<int, Instance>(); // 调用Dapper的多映射QueryAsync方法 var result = await _dbConnection.QueryAsync<Instance, Entity, Instance>( sql: querySql, // 映射逻辑:把Entity赋值给Instance的Entity属性 map: (instance, entity) => { if (!instanceMap.TryGetValue(instance.Id, out var existingInstance)) { existingInstance = instance; instanceMap.Add(existingInstance.Id, existingInstance); } existingInstance.Entity = entity; return existingInstance; }, // 指定从哪个字段开始拆分到第二个实体(Entity) splitOn: "EntityId", // 查询参数 param: new { InstanceId = instanceId } ); // 返回查询到的第一个实例(按Id查应该只有一个) return result.FirstOrDefault(); } }
3. 整合Unit of Work
Unit of Work的核心是统一管理数据库连接和事务,所以你需要在UoW里注入连接,并提供Repository的实例:
public class UnitOfWork : IUnitOfWork, IDisposable { private readonly IDbConnection _dbConnection; private IDbTransaction _dbTransaction; private IInstanceRepository _instanceRepository; public UnitOfWork(IDbConnection dbConnection) { _dbConnection = dbConnection; _dbConnection.Open(); // 打开连接 } // 延迟加载InstanceRepository实例 public IInstanceRepository InstanceRepository { get { return _instanceRepository ??= new InstanceRepository(_dbConnection); } } // 开启事务 public void BeginTransaction() { _dbTransaction = _dbConnection.BeginTransaction(); // 如果需要给Repository传递事务,可以在这里修改Repository的构造或属性 } // 提交事务 public async Task CommitAsync() { try { await _dbTransaction.CommitAsync(); } catch { await _dbTransaction.RollbackAsync(); throw; // 重新抛出异常让上层处理 } finally { _dbTransaction.Dispose(); _dbTransaction = null; } } // 释放资源 public void Dispose() { _dbTransaction?.Dispose(); _dbConnection.Close(); _dbConnection.Dispose(); } }
4. 使用示例
在业务层调用的时候,就可以通过Unit of Work来获取Repository并查询:
public async Task<Instance> GetInstanceWithEntity(int instanceId) { using var unitOfWork = new UnitOfWork(new SqlConnection("你的连接字符串")); var instance = await unitOfWork.InstanceRepository.GetInstanceWithEntityAsync(instanceId); return instance; }
额外注意点
- 如果需要查询多个Instance并关联Entity,上面的代码同样适用,Dapper会帮你处理所有结果,字典会自动去重同一个Instance的重复记录
- 如果你需要懒加载(即用到Entity的时候再查询),Dapper本身不支持,你得自己实现(比如用动态代理类,但一般不推荐,预先加载关联数据更高效)
- 确保你的实体类属性名称和SQL里的别名对应(比如Instance的Id对应SQL里的InstanceId,Name对应InstanceName),或者你可以用Dapper的
Column特性来映射
内容的提问来源于stack exchange,提问作者Red Wei
相关产品推荐
相关产品推荐

