在选择ViewModel时如何为IQueryable添加新的Where条件?
嘿,刚接触C#和.NET遇到这种问题很正常,我来一步步帮你理清楚~
疑问1:为什么延迟执行还会报错?
你说得对,EF确实是延迟执行——只有调用ToListAsync、FirstAsync这类触发执行的方法时,才会把LINQ查询转换成SQL发送到数据库。但问题出在你调用Select投影到OfferViewModel的时机:
当你在GetOffersQueryForSeller里做了Select(o => new OfferViewModel { ... })后,返回的IQueryable<OfferViewModel>已经是针对ViewModel的查询了,而不是原始的Offer实体。此时EF的查询提供者会把后续的操作(比如你在GetDowngradableSellerOffers里加的Where(o => o.Sku.Id == monthlySkuId))尝试转换成SQL,但OfferViewModel里并没有Sku这个属性(看你定义的ViewModel字段:Id、Name、ImageUrl、QuantityName),EF不知道怎么把这个针对ViewModel的属性查询映射到数据库表的字段,所以就抛出了那个错误。
简单说:延迟执行没错,但你提前把查询从数据库实体“转成”了ViewModel,后续的条件无法被EF翻译成SQL了。
疑问2:如何正确复用公共查询?
核心思路是:不要过早投影到ViewModel,先把所有针对数据库实体的条件都加完,最后再做投影。这里给你两种可行的方案:
方案1:公共方法返回实体查询,最后统一投影
先修改你的私有方法,让它返回针对Offer实体的查询,保留所有基础过滤条件,不做投影:
private IQueryable<Offer> GetOffersQueryForSeller(int sellerId) { return Db.Offers .Include(o => o.Sku) // 若需要关联查询Sku数据,可添加Include(EF Core部分场景下无需显式Include) .Where(o => o.Sku.SellerId == sellerId && o.IsActive && !o.IsDiscontinued); }
然后把投影逻辑也提取成私有方法,避免重复代码,最后在两个公共方法里先加条件再投影:
public async Task<List<OfferViewModel>> GetSellerOffers(int sellerId) { var query = GetOffersQueryForSeller(sellerId); return await query.Select(MapToOfferViewModel()).ToListAsync(); } public async Task<List<OfferViewModel>> GetDowngradableSellerOffers(int sellerId) { var query = GetOffersQueryForSeller(sellerId); return await query .Where(o => o.Sku.Id == monthlySkuId) // 这里针对Offer实体的Sku,EF能正常转换为SQL .Select(MapToOfferViewModel()).ToListAsync(); } // 提取投影逻辑,统一维护 private Expression<Func<Offer, OfferViewModel>> MapToOfferViewModel() { return o => new OfferViewModel { Id = o.Id, Name = o.Sku.Name, ImageUrl = o.Sku.ImageUrl ?? o.Sku.Upcq.Upc.ImageUrl, QuantityName = o.Sku.QuantityName }; }
方案2:让公共方法接受额外条件参数
如果后续还有更多需要加不同条件的方法,也可以让GetOffersQueryForSeller接受一个可选的条件参数,把所有逻辑封装在私有方法里:
private IQueryable<OfferViewModel> GetOffersQueryForSeller(int sellerId, Expression<Func<Offer, bool>> extraCondition = null) { var query = Db.Offers .Include(o => o.Sku) .Where(o => o.Sku.SellerId == sellerId && o.IsActive && !o.IsDiscontinued); // 若有额外条件则追加 if (extraCondition != null) { query = query.Where(extraCondition); } // 最后统一投影到ViewModel return query.Select(o => new OfferViewModel { Id = o.Id, Name = o.Sku.Name, ImageUrl = o.Sku.ImageUrl ?? o.Sku.Upcq.Upc.ImageUrl, QuantityName = o.Sku.QuantityName }); }
调用时就会非常简洁:
public async Task<List<OfferViewModel>> GetSellerOffers(int sellerId) { var query = GetOffersQueryForSeller(sellerId); return await query.ToListAsync(); } public async Task<List<OfferViewModel>> GetDowngradableSellerOffers(int sellerId) { var query = GetOffersQueryForSeller(sellerId, o => o.Sku.Id == monthlySkuId); return await query.ToListAsync(); }
这两种方案都能完美复用公共逻辑,同时避免你遇到的运行时错误~
内容的提问来源于stack exchange,提问作者Umair Abid

