EF Core 2.2泛型仓储传投影参数报错及实现方式差异咨询
问题解答:EF Core投影报错及投影过滤与内存过滤的区别
1. 解决投影中ToList()引发的报错问题
这个错误的核心原因是:在EF Core的LINQ表达式树里,你对导航属性x.BusinessDetails调用Where后,返回的是EF Core专属的IAsyncEnumerable<BusinessDetails>类型,而ToList()方法要求的参数是标准的IEnumerable<BusinessDetails>,EF Core的查询翻译器无法完成这个类型转换,所以抛出了异常。
你只需要去掉投影里的ToList()调用即可,让EF Core自动处理集合的加载和转换:
Expression<Func<Businesses, Businesses>> projection = x => new Businesses { // 移除ToList(),让EF Core在数据库层面完成过滤后自动映射集合 BusinessDetails = x.BusinessDetails.Where(bd => bd.Status == (int)EnumGringo.LU_Status.active && bd.LanguageTypeId == (int)EnumGringo.Lu_LanguageTypes.he), Name = "Name projection" };
如果你的BusinessDetails属性是ICollection<BusinessDetails>或类似的集合类型,EF Core在执行查询后会自动将过滤后的结果转换成对应的集合类型,不需要手动调用ToList()。
另外补充一点:如果你是想投影到DTO而不是实体类型,写法类似,同样不要在投影里对集合调用ToList(),EF Core会处理好后续的映射。
2. 投影参数过滤 vs 获取实体后内存过滤的区别
这两种方式的核心差异在于过滤逻辑的执行位置,进而带来一系列性能、功能上的不同:
执行位置与性能
- 投影过滤:过滤逻辑会被EF Core翻译成SQL,直接在数据库层面执行。数据库只会返回符合条件的
BusinessDetails数据,减少了从数据库传输到应用程序的数据量,在数据量大的时候性能优势非常明显。 - 内存过滤:先把所有关联的
BusinessDetails数据加载到应用程序内存中,再用LINQ to Objects进行过滤。如果关联数据很多,会占用更多内存和网络带宽,性能较差。
- 投影过滤:过滤逻辑会被EF Core翻译成SQL,直接在数据库层面执行。数据库只会返回符合条件的
数据一致性
- 投影过滤是一个原子性的数据库查询操作,返回的数据是查询瞬间数据库的状态,不存在加载后数据被修改的问题。
- 内存过滤是分两步:先加载数据,再过滤。如果在这两步之间有其他操作修改了数据库中的
BusinessDetails数据,内存中的数据就和数据库不一致了(当然在同一个DbContext实例下,因为上下文的跟踪机制,这种情况较少,但跨上下文或多线程场景下可能出现)。
EF Core上下文跟踪
- 如果你投影返回的是实体类型(比如示例中的
new Businesses),EF Core默认不会跟踪这个投影出来的实体(除非你显式配置跟踪),修改它不会自动同步到数据库。 - 获取实体后过滤的是被DbContext跟踪的实体,对
BusinessDetails的修改会被上下文记录,后续调用SaveChanges时会同步到数据库。
- 如果你投影返回的是实体类型(比如示例中的
过滤逻辑的灵活性
- 投影过滤只能使用EF Core能够翻译成SQL的表达式,比如不能在过滤逻辑中调用自定义的.NET方法(除非该方法能被EF Core识别并翻译)。
- 内存过滤可以使用任意.NET方法和逻辑,比如调用自定义的判断函数、复杂的内存计算等,灵活性更高。
内容的提问来源于stack exchange,提问作者Offir
相关产品推荐
相关产品推荐

