多数据源实体模型不同时DAL层应选用什么设计模式?
方案整体思路
核心原则是业务层和数据源实现完全解耦,不要让不同数据源的模型差异渗透到业务逻辑里,你可以按以下步骤改造:
1. 拆分三层模型,隔离数据源差异
不要再共用同一个Car实体,拆分三类模型,职责明确:
- 领域层实体:业务层唯一依赖的模型,结构和业务需求对齐,和具体数据源无关
- 数据源专属模型:分别和SQL表结构、Mongo集合结构对齐,只在对应数据源的DAL层内部使用
- 转换逻辑:各数据源的DAL层自己负责「专属模型 ↔ 领域模型」的转换,把字段格式差异(比如逗号分隔字符串转列表)封闭在DAL内部
示例代码:
// 领域层实体,全业务层共用,和数据源无关 public class Car { public string Brand { get; set; } public List<string> Colors { get; set; } } // SQL专属模型,和SQL表结构完全对齐,仅在SQL DAL内部使用 public class CarSqlPO { public string Brand { get; set; } public string AvailableColorsCommaSperated { get; set; } }
2. 抽象通用泛型仓储接口
不要为每个实体单独定义ICarDAL这类接口,统一用泛型仓储接口定义通用CRUD规范,所有数据源的实现都遵守这个接口:
public interface IRepository<T> where T : class { List<T> GetAll(); T GetById(object id); void Add(T entity); void Update(T entity); void Delete(object id); }
3. 分数据源独立实现仓储
SQL和Mongo的仓储各写各的实现,各自维护自己的Context,不需要强行统一:
- SQL仓储用你现有EF的DbContext,内部处理SQL模型和领域模型的转换
- Mongo仓储用MongoDB客户端,内部处理Mongo集合和领域模型的转换
SQL仓储示例:
public class SqlCarRepository : IRepository<Car> { private readonly DbContext _dbContext; public SqlCarRepository(DbContext dbContext) => _dbContext = dbContext; public List<Car> GetAll() { return _dbContext.Set<CarSqlPO>() .ToList() .Select(sqlCar => new Car { Brand = sqlCar.Brand, Colors = sqlCar.AvailableColorsCommaSperated.Split(',').ToList() }) .ToList(); } public void Add(Car entity) { var sqlCar = new CarSqlPO { Brand = entity.Brand, AvailableColorsCommaSperated = string.Join(',', entity.Colors) }; _dbContext.Set<CarSqlPO>().Add(sqlCar); _dbContext.SaveChanges(); } // 其他CRUD实现逻辑同理 }
Mongo仓储示例:
public class MongoCarRepository : IRepository<Car> { private readonly IMongoCollection<Car> _carCollection; public MongoCarRepository(IMongoClient mongoClient) { _carCollection = mongoClient.GetDatabase("你的库名").GetCollection<Car>("Car"); } public List<Car> GetAll() => _carCollection.Find(_ => true).ToList(); public void Add(Car entity) => _carCollection.InsertOne(entity); // 其他CRUD实现逻辑同理 }
4. 过渡阶段双写逻辑实现
如果需要同时维护两个库,加一个组合仓储实现,也遵守IRepository<T>接口,内部同时注入SQL和Mongo的仓储实例:
public class CompositeCarRepository : IRepository<Car> { private readonly IRepository<Car> _sqlRepo; private readonly IRepository<Car> _mongoRepo; // 可通过配置动态切换读数据源 private readonly bool _readFromMongo = false; public CompositeCarRepository(IRepository<Car> sqlRepo, IRepository<Car> mongoRepo) { _sqlRepo = sqlRepo; _mongoRepo = mongoRepo; } public List<Car> GetAll() => _readFromMongo ? _mongoRepo.GetAll() : _sqlRepo.GetAll(); public void Add(Car entity) { // 双写两边,可按需加重试、一致性校验逻辑 _sqlRepo.Add(entity); _mongoRepo.Add(entity); } // 其他CRUD实现逻辑同理 }
针对你问题的直接解答
- 要不要用不同的Context? 要,EF的DbContext和Mongo的客户端本身是完全独立的组件,各自维护连接、事务逻辑即可,强行统一Context只会增加不必要的复杂度。
- 要不要用工厂模式? 如果你需要动态切换数据源(比如按配置、按请求路由到不同库)可以用工厂模式实现;如果只是固定用双写仓储或者单数据源,直接在依赖注入层注册对应实现即可,不需要额外加工厂。
如果实体数量多,可以写泛型仓储基类,配合AutoMapper做模型转换,不用每个实体单独写重复的CRUD逻辑。后期SQL完全下线后,直接把DI注册的实现换成Mongo仓储,业务层一行代码都不用改。
内容的提问来源于stack exchange,提问作者E.Hamed
相关产品推荐
相关产品推荐

