关于Unit of Work模式下扩展方法调用限制与返回类型的技术疑问
嘿,这两个问题都是异步仓储+Unit of Work模式里很常见的小困惑,我来给你一步步捋清楚:
问题1:为什么不能直接链式调用OrderBy?
你现在的写法需要换行,核心原因是 _unitOfWork.Languages.All() 返回的是 Task<IEnumerable<T>>,而 OrderBy 是针对 IEnumerable<T> 的扩展方法,不是针对 Task<> 类型的。你得先通过await把Task“拆包”,拿到里面实际的集合对象,才能调用OrderBy。
不过你完全可以不用换行,只需要给await表达式加个括号,让它先执行拿到集合,再链式调用OrderBy:
var languages = (await _unitOfWork.Languages.All()).OrderBy(x => x.Order);
这样就实现了类似链式的写法,括号确保了执行顺序:先await获取IEnumerable
另外,如果你的架构允许,也可以考虑让仓储方法返回IQueryable<T>(但这可能会把EF Core的查询逻辑暴露到业务层,需要权衡你的封装需求),这样你就能在调用端直接链式拼接查询,最后再执行异步操作:
// 假设仓储All方法返回Task<IQueryable<T>> var languages = await _unitOfWork.Languages.All().OrderBy(x => x.Order).ToListAsync();
不过这种方式会打破仓储对数据访问的封装,要不要用得看你的项目设计。
问题2:为什么最终结果是IEnumerable而不是List?
这个本质是返回类型的向上转型:
dbSet.ToListAsync()确实返回的是Task<List<T>>,但你的All方法声明的返回类型是Task<IEnumerable<T>>。- 因为
List<T>实现了IEnumerable<T>,而Task<T>是支持协变的,所以Task<List<T>>可以隐式转换为Task<IEnumerable<T>>。 - 当你await这个Task时,得到的结果就会被自动向上转型为
IEnumerable<T>,而不是原来的List<T>。
如果想直接拿到List
- 修改仓储的All方法返回类型为
Task<List<T>>:
public virtual async Task<List<T>> All() { return await dbSet.ToListAsync(); }
这样await之后直接得到List
2. 如果不想改动仓储方法,就在调用端显式转换:
var languages = (await _unitOfWork.Languages.All()).ToList().OrderBy(x => x.Order);
不过这种方式多了一次ToList的内存操作,不如直接修改仓储返回类型高效。
最后补一句关于Unit of Work的题外话:虽然确实有观点认为它是反模式,但在需要统一事务管理、或者对数据访问层有强封装需求的场景下,它依然是很实用的设计,不用太纠结单一的观点~
内容的提问来源于stack exchange,提问作者Kosta

