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

如何为Entity Framework创建支持ICollection的动态通用Where子句?

问题描述

尝试为Entity Framework创建运行时动态生成Where子句的代码,处理直接对象属性正常,但处理集合属性(如Parent类的Children)时抛出错误:

System.ArgumentException: 'Instance property 'Name' is not defined for type 'System.Collections.Generic.ICollection`1[Child]' (Parameter 'propertyName')'

实体类代码

public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Child Child { get; set; }   // 此属性处理正常
    public ICollection<Child> Children { get; set; } // 集合属性处理失败
}

public class Child
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ParentId { get; set; }
    public Parent Parent { get; set; }
}

调用及动态Where方法代码

public static void Main()
{
    using (var context = new MyDbContext())
    {
        var filters = new Dictionary<string, object>
                {
                    { "Name", "Parent1" },
                    { "Child.Name","Child2" },  // 正常工作
                    { "Children.Name", "Child1" } // 无法工作
                };
        
        var results = ApplyDynamicWhereClause<Parent>(context.Parents, filters).Include(p => p.Children).ToList();
        
        foreach (var parent in results)
        {
            Console.WriteLine($"{parent.Name}:");
            foreach (var child in parent.Children)
            {
                Console.WriteLine($"  {child.Name}");
            }
        }
    }
}

public IQueryable<T> ApplyDynamicWhereClause<T>(IQueryable<T> query, Dictionary<string, object> filters)
{
    var parameter = Expression.Parameter(typeof(T), "x");
    Expression predicate = Expression.Constant(true); 

    foreach (var filter in filters)
    {
        string[] propertyPath = filter.Key.Split('.');
        Expression property = parameter;
        Type currentType = typeof(T);

        foreach (var propertyName in propertyPath)
        {
            property = Expression.Property(property, propertyName);
            currentType = currentType.GetProperty(propertyName).PropertyType;
        }

        var constant = Expression.Constant(filter.Value);
        var equals = Expression.Equal(property, constant);
        predicate = Expression.AndAlso(predicate, equals);
    }

    var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameter);
    return query.Where(lambda);
}

Mock DbContext代码

public class MyDbContext : DbContext
{
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        //optionsBuilder.UseInMemoryDatabase("TestDb");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Parent>().HasData(
            new Parent { Id = 1, Name = "Parent1" },
            new Parent { Id = 2, Name = "Parent2" });

        modelBuilder.Entity<Child>().HasData(
            new Child { Id = 1, Name = "Child1", ParentId = 1 },
            new Child { Id = 2, Name = "Child2", ParentId = 1 },
            new Child { Id = 3, Name = "Child3", ParentId = 2 });
    }
}
解决方案

问题核心是:ICollection<Child>本身没有Name属性,你需要的是判断集合是否存在满足条件的元素,而非直接访问集合的属性。修改ApplyDynamicWhereClause方法,在遍历属性路径时检测集合类型,生成Any()表达式来实现集合条件过滤。

修改后的方法代码:

public static IQueryable<T> ApplyDynamicWhereClause<T>(IQueryable<T> query, Dictionary<string, object> filters)
{
    var parameter = Expression.Parameter(typeof(T), "x");
    Expression predicate = Expression.Constant(true);

    foreach (var filter in filters)
    {
        string[] propertyPath = filter.Key.Split('.');
        Expression property = parameter;
        Type currentType = typeof(T);

        for (int i = 0; i < propertyPath.Length; i++)
        {
            var propertyName = propertyPath[i];
            var prop = currentType.GetProperty(propertyName);
            property = Expression.Property(property, prop);
            currentType = prop.PropertyType;

            // 检测是否为非字符串的集合类型
            if (i < propertyPath.Length - 1 && typeof(System.Collections.IEnumerable).IsAssignableFrom(currentType) && currentType != typeof(string))
            {
                var elementType = currentType.GetGenericArguments()[0];
                var elementParam = Expression.Parameter(elementType, "item");
                // 构建集合的Any()调用,传入元素条件lambda
                property = Expression.Call(
                    typeof(Enumerable), 
                    "Any", 
                    new[] { elementType }, 
                    property, 
                    Expression.Lambda(Expression.Property(elementParam, propertyPath[i+1]), elementParam)
                );
                i++; // 跳过下一个属性,已在Any中处理
                currentType = typeof(bool); // Any返回bool类型
            }
        }

        var constant = Expression.Constant(filter.Value);
        var equals = Expression.Equal(property, constant);
        predicate = Expression.AndAlso(predicate, equals);
    }

    var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameter);
    return query.Where(lambda);
}

代码说明

  1. 集合检测逻辑:判断当前属性是否为IEnumerable(排除字符串),若后续还有属性路径,则生成Any()调用。
  2. Any表达式构建:通过Enumerable.Any静态方法,传入集合表达式和检测元素条件的lambda,实现"集合中存在满足条件的元素"的逻辑。
  3. 路径跳过处理:处理完集合后跳过下一个属性名,避免重复解析。

修改后,"Children.Name": "Child1"会被解析为x.Children.Any(item => item.Name == "Child1"),符合EF Core查询语法,可正常过滤包含指定子元素的父实体。

内容的提问来源于stack exchange,提问作者Minhaj

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 07:37:36