You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

  1. 用户未选择时间查询时:
Select * from BuyerBill as buy inner join BuyerBillItems As item On buy.Id=item.BuyerBillId 
                          where item.Status=1
  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),可以通过动态构建子实体的过滤表达式来实现需求:

  1. 先实现通用的WhereIf扩展方法:
public static IQueryable<T> WhereIf<T>(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate)
{
    return condition ? query.Where(predicate) : query;
}
  1. 构建针对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);
  1. 应用到查询中:
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投影:

  1. 确保已配置BuyerBill→BuyerBillDto、BuyerBillItems→BuyerBillItemsDto的映射规则。
  2. 修改查询:
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);
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 04:24:23