我正在为 UserProfiles
构建一个过滤系统基于已知属性但未知(直到运行时)的过滤条件组合。
在我之前的问题中How do I create a generic Expression that has an expression as a parameter ,我找到了一种方法,可以通过导航属性从 User 实体访问任何值属性(即 (User)u=> u.NavigationProperty.AnotherNavigationProperty.SomeValue
)
我有一个方法可以返回谓词 Expression<Func<User,bool>>
对于给定的属性、操作( > < == 等)和值。
现在也到了根据集合属性过滤它们的时候了。 举例来说,用户有 CheckedOutBooks 集合(这完全是虚构的,但会做) 我需要为 User 对象上的 CheckedOutBooks 集合的 Name 属性创建一个过滤器定义。
我有什么:
用户集合
用户类有一系列书籍
现在我想创建一个方法
Expression<Func<User,bool>> GetPredicate(Expression<User,TProperty>, Operations operation, TProperty value)
我可以这样称呼 GetPredicate(u=>u.Books.Select(b=>b.Name), Operations.Contains, "C# in a nutshell")
并得到类似于的表达式
u=>u.Books.Any(b=>b.Name == "C# in a nutshell")
我想也许将第一个参数分成两部分来实现这一点会更容易。
也许u=>u.Books
和b=>b.Name
会做得更好吗?
编辑: 到目前为止我得到了什么:
class FilterDefinitionForCollectionPropertyValues<T>:FilterDefinition, IUserFilter
{
public Expression<Func<UserProfile, IEnumerable<T>>> CollectionSelector { get; set; }
public Expression<Func<T, string>> CollectionPropertySelector { get; set; }
public Expression<Func<Profile.UserProfile, bool>> GetFilterPredicateFor(FilterOperations operation, string value)
{
var propertyParameter = CollectionPropertySelector.Parameters[0];
var collectionParameter = CollectionSelector.Parameters[0];
// building predicate to supply to Enumerable.Any() method
var left = CollectionPropertySelector.Body;
var right = Expression.Constant(value);
var innerLambda = Expression.Equal(left, right);
Expression<Func<T, bool>> innerFunction = Expression.Lambda<Func<T, bool>>(innerLambda, propertyParameter);
var method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(T));
var outerLambda = Expression.Call(method, Expression.Property(collectionParameter, typeof(UserProfile).GetProperty("StaticSegments")), innerFunction);
throw new NotImplementedException();
}
}
现在这个工作得很好并且完全满足了需要,现在我唯一需要弄清楚的是如何替换 typeof(UserProfile).GetProperty("StaticSegments"))
以某种方式使用 CollectionPropertySelector
当前示例中的值是 (UserProfile)u=>u.StaticSegments
最佳答案
你快完成了。现在您只需执行一个小技巧 - 将 CollectionPropertySelector lambda 表达式包装在 CollectionSelector lambda 表达式中。
Expression<Func<TParent,bool>> Wrap<TParent,TElement>(Expression<Func<TParent, IEnumerable<TElement>>> collection, Expression<Func<TElement, bool>> isOne, Expression<Func<IEnumerable<TElement>, Func<TElement, bool>, bool>> isAny)
{
var parent = Expression.Parameter(typeof(TParent), "parent");
return
(Expression<Func<TParent, bool>>)Expression.Lambda
(
Expression.Invoke
(
isAny,
Expression.Invoke
(
collection,
parent
),
isOne
),
parent
);
}
您可能需要对此进行一些更改才能用于您的特定场景,但想法应该很清晰。我的测试基本上是这样的:
var user = new User { Books = new List<string> { "Book 1", "Book 2" }};
var query = Wrap<User, string>(u => u.Books, b => b.Contains("Bookx"), (collection, condition) => collection.Any(condition));
因此,您指定集合选择器、谓词和谓词运算符,然后就完成了。
为了清楚起见,我将其编写为通用方法,但它是动态的,本质上不是强类型的,因此如果需要,将其更改为非通用方法应该很容易。
关于c# - 如何在 C# 中通过表达式树构建集合过滤器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20400474/