如何使用表达式使用子项级别?

How to use subItem level using Expression?

我有下一个模型:

public class MaterialCatalog {
  public int MaterialCatalogID { get; set; }
  public int AccountID { get; set; }
  public string MaterialName { get; set; }
  public ICollection<MaterialCategory> Categories { get; set; }
}

public class MaterialCategory {
  public int MaterialCatalogID { get; set; }
  public int UserCategoryID { get; set; }
}

我需要为此表达式创建 IQueryable 通用外部方法:

var items = from l in ctx.WorkareaTemplates
            where l.WorkareaTemplateCategories.Any(mc => mc.UserCategoryID == req.Category;

这是我试过的。我得到一个错误:

The double operator Equal is not defined for the types 'System.Collections.Generic.ICollection`1[MaterialCategory]' and 'System.Int32'.

如何转到子属性以使用 MaterialCategory

public static IQueryable<TSource> FilterByUserCategoryId<TSource>(this IQueryable<TSource> source, object searchValue) {
  if(typeof(TSource).GetProperties().Any(x => x.Name == "Categories")) { 
    var parameter = Expression.Parameter(typeof(TSource), "e");
    var prop = Expression.Property(parameter, "Categories");
    var value = Expression.Constant(searchValue);
    var body = Expression.Equal(prop, value);
    var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter);

    var overload = typeof(Queryable).GetMethods().First(x => x.Name == "Any" && x.GetParameters().Length == 2);
    var orderByGeneric = overload.MakeGenericMethod(typeof(TSource), prop.Type);

    var result = orderByGeneric.Invoke(null, new object[] { source, lambda });

    return (IQueryable<TSource>)result;
  }
  return source;
}

所以,你要做的操作基本上是这样的:

var items = ctx.WorkareaTemplates
  .Where(x => x.Categories.Any(c => c.UserCategoryID == req.Category));

您缺少的最重要的部分是您需要执行 2 个查询操作:

  • IQueryable<MaterialCatalog>.Where(过滤 MaterialCatalog
  • IEnumerable<MaterialCategory>.Any(查看 MaterialCategory 上的成员资格)。

也就是说,检查这个示例实现(您很可能想针对极端情况和例外情况加强它):

public static IQueryable<TSource> FilterByUserCategoryId<TSource>(this IQueryable<TSource> source, object searchValue) {
    var catsType = typeof(TSource).GetProperties().Single(p => p.Name == "Categories").PropertyType; // ICollection<MaterialCategory>
    var isIEnum = (Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>); 
    var catType = catsType.GetInterfaces().First(isIEnum).GetGenericArguments()[0]; // MaterialCategory

    // GOAL:
    // source.Where(x => x.Categories.Any(c => c.UserCategoryID == searchValue));

    var cParam = Expression.Parameter(catType, "c"); // MaterialCategory c
    var idProp = Expression.Property(cParam, "UserCategoryID"); // c.UserCategoryID
    var idConst = Expression.Constant(searchValue); // searchValue
    var equal = Expression.Equal(idProp, idConst); // c.UserCategoryID == searchValue
    var anyPred = Expression.Lambda(equal, new[] { cParam }); // c => c.UserCategoryID == searchValue

    // this is questionable... relying on the current API of Enumerable.cs
    var anyMeth = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Count() == 2); // Enumerable.Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    var anyMethGeneric = anyMeth.MakeGenericMethod(catType); // Enumerable.Any<MaterialCategory>(this IEnumerable<MaterialCategory> source, Func<MaterialCategory, bool> predicate)
    var xParam = Expression.Parameter(typeof(TSource), "x"); // MaterialCatalog x
    var catsMember = Expression.Property(xParam, "Categories"); // x.Categories
    var anyCall = Expression.Call(null, anyMethGeneric, catsMember, anyPred); // x.Categories.Any(c => c.UserCategoryID == searchValue));
    var wherePred = Expression.Lambda(anyCall, new[] { xParam }); // x => x.Categories.Any(c => c.UserCategoryID == searchValue)

    // this is even more questionable... relying on the private implementation of Queryable.cs
    var whereMeth = typeof(Queryable).GetMethods().First(m => m.Name == "Where"); // QueriableWhere<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    var whereMethGeneric = whereMeth.MakeGenericMethod(typeof(TSource)); // Queriable.Where<MaterialCategory>(this IQueryable<MaterialCategory> source, Expression<Func<MaterialCategory, bool>> predicate)

    var result = whereMethGeneric.Invoke(null, new object[] { source, wherePred }); // source.Where(x => x.Categories.Any(c => c.UserCategoryID == searchValue))
    return (IQueryable<TSource>)result;
}