EF Core 5子实体动态条件过滤及Select映射异常求助
EF Core子实体动态过滤及Select映射问题解决
初始问题:子实体动态条件过滤
以下代码可正常运行,但我希望根据用户输入值自定义添加过滤条件:
var resultList = dbContext.BuyerBill .Include(x=>x.BuyerBillItems.Where(x=>x.Status == input.Status && x.BuildTime > input.BeginTime && x.BuildTime < input.EndTime)) .ToList();
我期望的实现方式:
var query = WhereIf(input.Status!=null,x=>x.Status == input.Status); query = WhereIf(input.BeginTime!=null,x=>x.BuildTime > input.BeginTime); query = WhereIf(input.EndTime!=null,x=>x.BuildTime > input.EndTime);
实体类定义
public class BuyerBill { public BuyerBill() { BuyerBillItems = new List<BuyerBillItems>(); } public int Id {get;set;} public int BuyUserId {get;set;} public int OrderId {get;set;} public List<BuyerBillItems> BuyerBillItems { get; set; } // 其他属性 } public class BuyerBillItems { public int Id {get;set;} public int BuyerBillId {get;set;} public decimal Fee {get;set;} public int Status {get;set;} public DateTime CreateTime {get;set;} public BuyerBill BuyerBill {get;set;} // 其他属性 }
期望生成的SQL
- 用户未选择时间查询时:
Select * from BuyerBill as buy inner join BuyerBillItems As item On buy.Id=item.BuyerBillId where item.Status=1
- 用户选择时间查询时:
Select * from BuyerBill as buy inner join BuyerBillItems as item on buy.Id=item.BuyerBillId where item.Status=1 and item.BuildTime > '2022-7-19' and item.BuildTime < '2022-7-19'
我主要困惑于如何对子实体进行条件过滤,单实体过滤我知道用Where()方法,但子实体的条件过滤不知如何实现。
后续问题:LinqKit结合Select映射异常
我已通过LinqKit解决了上述问题,但遇到了新问题:
var predicatess = PredicateBuilder.New<BuyerBillItems>(true); predicatess = predicatess.And(x => x.CreateTime > StringHelper.AddDateTime("2022-07-16")); predicatess = predicatess.And(x => x.Status == 2); // 我觉得这行代码冗余,但不知道如何隐式转换 var convertPredicate = (Expression<Func<BuyerBillItems, bool>>)predicatess; var query = dbContext.BuyerBill.AsExpandable().Include(x => x.BuyerBillItems.Where(x => convertPredicate.Invoke(x))) .Where(x => x.BuyerBillItems.Any(s => convertPredicate.Invoke(s))) .Where(x => x.BuyUserId == 4); // 不使用Select时一切正常 var result1 = query.ToList(); // 使用Select后BuyerBillItemsDto结果不正确 var result2 = query.Select(x => new BuyerBillDto { Id = x.Id, BuyUserId = x.BuyUserId, OrderId = x.OrderId, BuyerBillItemsDto = mapper.Map<List<BuyerBillItems>, List<BuyerBillItemsDto>>(x.BuyerBillItems) }).ToList();
我必须使用Select来过滤列以避免性能损耗,请问该如何解决这个问题?
解决方案
一、子实体动态过滤的正确实现
首先,EF Core支持过滤包含(Filtered Include),可以通过动态构建子实体的过滤表达式来实现需求:
- 先实现通用的
WhereIf扩展方法:
public static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate) { return condition ? query.Where(predicate) : query; }
- 构建针对
BuyerBillItems的动态条件:
Expression<Func<BuyerBillItems, bool>> itemPredicate = x => true; if (input.Status != null) { itemPredicate = itemPredicate.And(x => x.Status == input.Status.Value); } if (input.BeginTime != null) { itemPredicate = itemPredicate.And(x => x.CreateTime > input.BeginTime.Value); } if (input.EndTime != null) { itemPredicate = itemPredicate.And(x => x.CreateTime < input.EndTime.Value); }
注:如果使用LinqKit的
PredicateBuilder,可以更简洁地拼接条件:var itemPredicate = PredicateBuilder.New<BuyerBillItems>(true); itemPredicate = itemPredicate.WhereIf(input.Status != null, x => x.Status == input.Status.Value); itemPredicate = itemPredicate.WhereIf(input.BeginTime != null, x => x.CreateTime > input.BeginTime.Value); itemPredicate = itemPredicate.WhereIf(input.EndTime != null, x => x.CreateTime < input.EndTime.Value);
- 应用到查询中:
var query = dbContext.BuyerBill .Include(x => x.BuyerBillItems.Where(itemPredicate)) .Where(x => x.BuyerBillItems.Any(itemPredicate));
这样就能根据输入动态生成子实体的过滤条件,同时确保只返回包含符合条件子实体的主实体。
二、解决Select映射异常的问题
你遇到的问题是因为EF Core无法将AutoMapper的Map方法转换为SQL,导致内存映射时过滤后的子集合未被正确保留。可以通过以下两种方式解决:
方法1:在投影中直接过滤并映射子实体
不依赖Include的过滤结果,直接在Select中对子实体应用过滤条件并映射:
var query = dbContext.BuyerBill .Where(x => x.BuyUserId == 4) .Where(x => x.BuyerBillItems.Any(itemPredicate)) .AsExpandable() .Select(x => new BuyerBillDto { Id = x.Id, BuyUserId = x.BuyUserId, OrderId = x.OrderId, BuyerBillItemsDto = x.BuyerBillItems.Where(itemPredicate) .Select(item => new BuyerBillItemsDto { Id = item.Id, Fee = item.Fee, Status = item.Status, CreateTime = item.CreateTime // 映射其他属性 }).ToList() }); var result2 = query.ToList();
方法2:使用AutoMapper的ProjectTo扩展
利用AutoMapper的ProjectTo让EF Core直接生成正确的SQL投影:
- 确保已配置
BuyerBill→BuyerBillDto、BuyerBillItems→BuyerBillItemsDto的映射规则。 - 修改查询:
var query = dbContext.BuyerBill .Where(x => x.BuyUserId == 4) .Where(x => x.BuyerBillItems.Any(itemPredicate)) .AsExpandable() .ProjectTo<BuyerBillDto>(mapper.ConfigurationProvider, opt => opt.Items["ItemPredicate"] = itemPredicate);
- 在AutoMapper配置中应用过滤条件:
CreateMap<BuyerBill, BuyerBillDto>() .ForMember(dest => dest.BuyerBillItemsDto, opt => opt.MapFrom((src, dest, _, context) => src.BuyerBillItems.Where((Expression<Func<BuyerBillItems, bool>>)context.Items["ItemPredicate"])));
三、LinqKit表达式转换的冗余优化
LinqKit的PredicateBuilder.New<T>()返回的ExpressionStarter<T>本身支持隐式转换为Expression<Func<T, bool>>,无需手动强制转换:
var predicate = PredicateBuilder.New<BuyerBillItems>(true); predicate = predicate.And(x => x.CreateTime > StringHelper.AddDateTime("2022-07-16")); predicate = predicate.And(x => x.Status == 2); // 直接使用predicate即可 var query = dbContext.BuyerBill.AsExpandable() .Include(x => x.BuyerBillItems.Where(predicate)) .Where(x => x.BuyerBillItems.Any(predicate)) .Where(x => x.BuyUserId == 4);
内容的提问来源于stack exchange,提问作者john
相关产品推荐
相关产品推荐

