如何创建动态 entity framework 过滤器表达式,如 Expression<Func<T, bool>>
How to create dynamic entity framework filter expression like Expression<Func<T, bool>>
我想创建一个用于过滤的动态 lambda 表达式。
我搜索了一下,但找不到对 child collections 有用的东西。那么如何创建这样的表达式呢?
Expression<Func<ORDERS, bool>> filter1 = c => c.ORDER_DETAILS.Any(x => x.PRODUCTS.HEADING.Contains("foo"));
PS: 我问过类似的问题,但没有得到正确答案。所以如果我没看错的话我决定从这个方向走
有关我的问题的更多信息:()
I'm trying to filter results for user request. For instance you have
orders and order details and products is child collection.
When user wants to filter by product I'm getting an error because of
No property or field 'PRODUCTS' exists in type 'ICollection1'`
I'm writing my query like this.
var orders = _uow.Repository()
.Query()
.Where("PRODUCTS.HEADING.ToLower().Contains(\"foo\")")
.Include("ORDER_DETAILS")
.Include("ORDER_DETAILS.PRODUCTS")
.ToList(); So it's not possible to filter child collection like this? Or any way to filter?
是的,你可以。我使用的一种方法使用与您的 return 类型相同的对象作为搜索过滤器。因此,如果要搜索 "Bill" 的客户名称,则将 Order.Customer.Name
设置为 Bill。将该对象传递给方法,然后应用所有适用的搜索。
为此,首先定义一个可搜索字段列表:
Field<Order>[] Fields;
通过声明新字段来填充这些:
var newField = new Field<Order>(o => o.Customer.Name, true, "Customer Name");
"true"参数表示它将作为结果的排序字段。
Field
对象包含足够的信息以供稍后生成表达式。它看起来像这样:
public class Field<T>
{
public Field(Expression<Func<T, object>> field, bool sortField = false, string displayName = null)
{
//get & validate member
MemberExp = field.Body is UnaryExpression ? ((UnaryExpression)field.Body).Operand as MemberExpression
: (MemberExpression)field.Body;
Field = MemberExp?.Member;
if (Field == null) throw new ArgumentException("Field expression is not a member.");
//set field type
switch (Field.MemberType)
{
case MemberTypes.Property:
PropertyInfo p = (PropertyInfo)Field;
FieldType = p.PropertyType;
break;
case MemberTypes.Field:
FieldInfo f = (FieldInfo)Field;
FieldType = f.FieldType;
break;
default:
throw new Exception("Unsupported member type detected.");
}
//store input values
FieldExpression = field;
SortField = sortField;
DisplayName = displayName ?? Field.Name;
}
public bool SortField { get; set; }
public string DisplayName { get; private set; }
public MemberExpression MemberExp { get; private set; }
public Expression<Func<T, object>> FieldExpression { get; private set; }
public Func<T, object> GetValue => FieldExpression.Compile();
public Type FieldType { get; set; }
/// <summary>
/// Gets the full field name, i.e o => o.Customer.CustomerName returns "Customer.CustomerName"
/// </summary>
public string UnqualifiedFieldName
{
get
{
var stringExp = MemberExp.ToString();
var paramEnd = stringExp.IndexOf('.') + 1;
return stringExp.Substring(paramEnd);
}
}
}
定义所有可搜索字段后,您将调用一个方法来根据您从用户那里收集的搜索过滤器 (T
) 获取搜索结果:
//get the results in ascending order, 10 items per page, first page
var results = GetSearchResults(searchFilters, "ASC", 10, 1);
该方法将要求您有一个可查询的数据集合。我假设您有一些方法,例如 context.GetCollection()
来检索您的数据。 GetSearchResults
方法将如下所示:
//Returns a filtered dataset based on provided search filters
//searchFilters is an object T which contains the search filters entered.
private List<T> GetSearchResults(T searchFilters, string sortDir = "ASC", int pageSize, int currentPage)
{
IQueryable<T> searchResults = context.GetCollection(); //get your data context here
var filterExpressions = new List<Expression<Func<T, bool>>>();
//Add filters
foreach (var field in Fields)
{
//try to get the search value, ignoring null exceptions because it's much harder
//to check for null objects at multiple levels. Instead the exception tells us there's
//no search value
string searchValue = null;
try
{
searchValue = field.GetValue(searchFilters)?.ToString();
}
catch (NullReferenceException) { }
if (string.IsNullOrWhiteSpace(searchValue)) continue;
//shared expression setup
ParameterExpression param = field.FieldExpression.Parameters.First();
Expression left = field.FieldExpression.Body;
ConstantExpression right = Expression.Constant(searchValue);
Expression body = null;
//create expression for strings so we can use "contains" instead of "equals"
if (field.FieldType == typeof(string))
{
//build the expression body
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
body = Expression.Call(left, method, right);
}
else
{ //handle expression for all other types
body = Expression.Equal(left, right);
}
//finish expression
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, param);
filterExpressions.Add(lambda);
}
//apply the expressions
searchResults = filterExpressions.Aggregate(searchResults, (current, expression) => current.Where(expression));
//get sort field
Field<T> sortField = Fields.FirstOrDefault(f => f.SortField);
searchResults = searchResults.OrderBy($"{sortField.UnqualifiedFieldName} {sortDir}");
// Get the search results
int count = searchResults.Count();
int maxPage = count / pageSize;
if (maxPage * pageSize < count) maxPage++;
if (currentPage > maxPage) currentPage = maxPage;
int skip = Math.Max(0, (filters.page - 1) * pageSize);
int display = Math.Max(0, Math.Min(count - skip, pageSize));
return searchResults.Skip(skip).Take(display).ToList();
}
此方法使用您的 Field[]
数组为您的条件构建表达式并将它们应用于数据集。
希望对您有所帮助!如果您有任何问题,请告诉我。
我想创建一个用于过滤的动态 lambda 表达式。
我搜索了一下,但找不到对 child collections 有用的东西。那么如何创建这样的表达式呢?
Expression<Func<ORDERS, bool>> filter1 = c => c.ORDER_DETAILS.Any(x => x.PRODUCTS.HEADING.Contains("foo"));
PS: 我问过类似的问题,但没有得到正确答案。所以如果我没看错的话我决定从这个方向走
有关我的问题的更多信息:(
I'm trying to filter results for user request. For instance you have orders and order details and products is child collection.
When user wants to filter by product I'm getting an error because of No property or field 'PRODUCTS' exists in type 'ICollection1'`
I'm writing my query like this.
var orders = _uow.Repository() .Query() .Where("PRODUCTS.HEADING.ToLower().Contains(\"foo\")") .Include("ORDER_DETAILS") .Include("ORDER_DETAILS.PRODUCTS") .ToList(); So it's not possible to filter child collection like this? Or any way to filter?
是的,你可以。我使用的一种方法使用与您的 return 类型相同的对象作为搜索过滤器。因此,如果要搜索 "Bill" 的客户名称,则将 Order.Customer.Name
设置为 Bill。将该对象传递给方法,然后应用所有适用的搜索。
为此,首先定义一个可搜索字段列表:
Field<Order>[] Fields;
通过声明新字段来填充这些:
var newField = new Field<Order>(o => o.Customer.Name, true, "Customer Name");
"true"参数表示它将作为结果的排序字段。
Field
对象包含足够的信息以供稍后生成表达式。它看起来像这样:
public class Field<T>
{
public Field(Expression<Func<T, object>> field, bool sortField = false, string displayName = null)
{
//get & validate member
MemberExp = field.Body is UnaryExpression ? ((UnaryExpression)field.Body).Operand as MemberExpression
: (MemberExpression)field.Body;
Field = MemberExp?.Member;
if (Field == null) throw new ArgumentException("Field expression is not a member.");
//set field type
switch (Field.MemberType)
{
case MemberTypes.Property:
PropertyInfo p = (PropertyInfo)Field;
FieldType = p.PropertyType;
break;
case MemberTypes.Field:
FieldInfo f = (FieldInfo)Field;
FieldType = f.FieldType;
break;
default:
throw new Exception("Unsupported member type detected.");
}
//store input values
FieldExpression = field;
SortField = sortField;
DisplayName = displayName ?? Field.Name;
}
public bool SortField { get; set; }
public string DisplayName { get; private set; }
public MemberExpression MemberExp { get; private set; }
public Expression<Func<T, object>> FieldExpression { get; private set; }
public Func<T, object> GetValue => FieldExpression.Compile();
public Type FieldType { get; set; }
/// <summary>
/// Gets the full field name, i.e o => o.Customer.CustomerName returns "Customer.CustomerName"
/// </summary>
public string UnqualifiedFieldName
{
get
{
var stringExp = MemberExp.ToString();
var paramEnd = stringExp.IndexOf('.') + 1;
return stringExp.Substring(paramEnd);
}
}
}
定义所有可搜索字段后,您将调用一个方法来根据您从用户那里收集的搜索过滤器 (T
) 获取搜索结果:
//get the results in ascending order, 10 items per page, first page
var results = GetSearchResults(searchFilters, "ASC", 10, 1);
该方法将要求您有一个可查询的数据集合。我假设您有一些方法,例如 context.GetCollection()
来检索您的数据。 GetSearchResults
方法将如下所示:
//Returns a filtered dataset based on provided search filters
//searchFilters is an object T which contains the search filters entered.
private List<T> GetSearchResults(T searchFilters, string sortDir = "ASC", int pageSize, int currentPage)
{
IQueryable<T> searchResults = context.GetCollection(); //get your data context here
var filterExpressions = new List<Expression<Func<T, bool>>>();
//Add filters
foreach (var field in Fields)
{
//try to get the search value, ignoring null exceptions because it's much harder
//to check for null objects at multiple levels. Instead the exception tells us there's
//no search value
string searchValue = null;
try
{
searchValue = field.GetValue(searchFilters)?.ToString();
}
catch (NullReferenceException) { }
if (string.IsNullOrWhiteSpace(searchValue)) continue;
//shared expression setup
ParameterExpression param = field.FieldExpression.Parameters.First();
Expression left = field.FieldExpression.Body;
ConstantExpression right = Expression.Constant(searchValue);
Expression body = null;
//create expression for strings so we can use "contains" instead of "equals"
if (field.FieldType == typeof(string))
{
//build the expression body
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
body = Expression.Call(left, method, right);
}
else
{ //handle expression for all other types
body = Expression.Equal(left, right);
}
//finish expression
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(body, param);
filterExpressions.Add(lambda);
}
//apply the expressions
searchResults = filterExpressions.Aggregate(searchResults, (current, expression) => current.Where(expression));
//get sort field
Field<T> sortField = Fields.FirstOrDefault(f => f.SortField);
searchResults = searchResults.OrderBy($"{sortField.UnqualifiedFieldName} {sortDir}");
// Get the search results
int count = searchResults.Count();
int maxPage = count / pageSize;
if (maxPage * pageSize < count) maxPage++;
if (currentPage > maxPage) currentPage = maxPage;
int skip = Math.Max(0, (filters.page - 1) * pageSize);
int display = Math.Max(0, Math.Min(count - skip, pageSize));
return searchResults.Skip(skip).Take(display).ToList();
}
此方法使用您的 Field[]
数组为您的条件构建表达式并将它们应用于数据集。
希望对您有所帮助!如果您有任何问题,请告诉我。