我在构建动态表达式树时遇到一些问题。我具有基本功能,可以在其中指定表达式的属性、运算符和值。但是当我尝试为对象中的列表创建表达式时遇到了问题。
我试图使其表达式的对象看起来像这样(简化)
public class Component
{
string partNumber;
string description;
List<SupplierRecord> supplierRecords;
}
public class SupplierRecord
{
string supplierName;
string supplierPartNumber;
}
我的表情生成器看起来像这样
internal class ExpressionBuilder
{
/// <summary>
/// Creates a expression for a Type
/// </summary>
/// <typeparam name="T">Type the expression applies to</typeparam>
/// <param name="filters">List of filter parameters</param>
/// <returns>An expression for the Type</returns>
public static Func<T, bool>? GetExpression<T>(IList<Filter> filters)
{
ParameterExpression parameter = Expression.Parameter(typeof(T), "t");
Expression? expression = null;
if (filters == null || !filters.Any())
{
expression = Expression.Equal(Expression.Default(typeof(T)), Expression.Default(typeof(T)));
}
else
{
// Add upp all expressions
foreach (var filter in filters)
{
if (filter.Property == "SupplierRecords")
{
MemberExpression records = Expression.PropertyOrField(parameter, "SupplierRecords");
ParameterExpression suppParameter = Expression.Parameter(typeof(SupplierRecord), "s");
Filter newFilter = new() { Property = "SupplierPartNumber", Value = filter.Value, Operator = filter.Operator };
Expression ex1 = GetExpression<SupplierRecord>(suppParameter, newFilter);
Expression ex2 = Expression.Lambda(ex1, suppParameter);
MethodCallExpression anyCall = Expression.Call(typeof(Enumerable), "Any", [typeof(SupplierRecord)], records, ex2);
if (expression == null)
{
expression = anyCall;
}
else
{
expression = Expression.AndAlso(expression, anyCall);
}
}
else if (expression == null)
{
expression = GetExpression<T>(parameter, filter);
}
else
{
expression = Expression.AndAlso(expression, GetExpression<T>(parameter, filter) ?? Expression.Default(typeof(T)));
}
}
}
if (expression == null)
{
return null;
}
Expression<Func<T, bool>> newExp = Expression.Lambda<Func<T, bool>>(expression, parameter);
return newExp.Compile();
}
/// <summary>
/// Translates user specified filters into expressions
/// </summary>
/// <typeparam name="T">Type the expression applies to</typeparam>
/// <param name="parameter">The expression object</param>
/// <param name="filter">The filter specified by the user</param>
/// <returns>A expression that represents the filter</returns>
private static Expression? GetExpression<T>(ParameterExpression parameter, Filter filter)
{
MemberExpression member = Expression.PropertyOrField(parameter, filter.Property ?? "");
UnaryExpression constant = GetUnary(member, filter);
// Get references for string methods
MethodInfo? containsMethod = typeof(string).GetMethod("Contains", [typeof(string)]);
MethodInfo? toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
MethodInfo? startsWithMethod = typeof(string).GetMethod("StartsWith", [typeof(string)]);
MethodInfo? endsWithMethod = typeof(string).GetMethod("EndsWith", [typeof(string)]);
// Error check
if (containsMethod == null)
{
MessageBox.Show("Could not find string.Contains method", "Filter Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
if (toLowerMethod == null)
{
MessageBox.Show("Could not find string.ToLower method", "Filter Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
if (startsWithMethod == null)
{
MessageBox.Show("Could not find string.StartsWith method", "Filter Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
if (endsWithMethod == null)
{
MessageBox.Show("Could not find string.EndsWith method", "Filter Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
try
{
switch (filter.Operator)
{
case Operator.Equals:
if (member.Type == typeof(string))
return Expression.Equal(Expression.Call(member, toLowerMethod), constant);
else
return Expression.Equal(member, constant);
case Operator.NotEquals:
if (member.Type == typeof(string))
return Expression.NotEqual(Expression.Call(member, toLowerMethod), constant);
else
return Expression.NotEqual(member, constant);
case Operator.GreaterThan:
return Expression.GreaterThan(member, constant);
case Operator.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(member, constant);
case Operator.LessThan:
return Expression.LessThan(member, constant);
case Operator.LessThanOrEqual:
return Expression.LessThanOrEqual(member, constant);
case Operator.Contains:
if (member.Type == typeof(string))
return Expression.Call(Expression.Call(member, toLowerMethod), containsMethod, constant);
else
return Expression.Call(member, containsMethod, constant);
case Operator.StartsWith:
if (member.Type == typeof(string))
return Expression.Call(Expression.Call(member, toLowerMethod), startsWithMethod, constant);
else
return Expression.Call(member, startsWithMethod, constant);
case Operator.EndsWith:
if (member.Type == typeof(string))
return Expression.Call(Expression.Call(member, toLowerMethod), endsWithMethod, constant);
else
return Expression.Call(member, endsWithMethod, constant);
}
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Filter Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return null;
}
/// <summary>
/// Create constant for specified value
/// </summary>
/// <param name="member">Expression member</param>
/// <param name="filter">User specified filter</param>
/// <returns>A expression constant that represents the filter value</returns>
private static UnaryExpression GetUnary(MemberExpression member, Filter filter)
{
if (filter.Value is string val)
{
return Expression.Convert(Expression.Constant(val.ToLower().Trim()), member.Type);
}
return Expression.Convert(Expression.Constant(filter.Value), member.Type);
}
}
/// <summary>
/// Representation of a filter robject
/// </summary>
public class Filter
{
/// <summary>
/// The object property to filter on
/// </summary>
public string? Property { get; set; }
/// <summary>
/// What operation to compare with
/// </summary>
public Operator Operator { get; set; }
/// <summary>
/// The value to use for the comparison
/// </summary>
public object Value { get; set; } = "";
}
/// <summary>
/// Standard operators
/// </summary>
public enum Operator
{
Contains,
Equals,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
NotEquals,
StartsWith,
EndsWith
}
我就是这样使用的
Func<Component, bool>? expression = ExpressionBuilder.GetExpression<Component>(filters);
if (expression == null)
{
goto Fault;
}
return context.Components.Where(expression).AsQueryable();
当我在
Component
类中指定任何属性时,一切正常,但是当我指定 SupplierRecord
时,我根本没有得到任何结果,我真的不明白为什么。
正如您所看到的,当指定
SupplierRecord
时,它会转换为具有用户指定值的 SupplierPartNumber
字段的过滤器。
Expression ex1 = GetExpression<SupplierRecord>(suppParameter, newFilter);
为属性生成一个表达式,内容为
{s.SupplierPartNumber.ToLower().Contains(Convert("5", String))}
Expression ex2 = Expression.Lambda(ex1, suppParameter);
将其转换为 lambda 表达式也很好。
{s => s.SupplierPartNumber.ToLower().Contains(Convert("5", String))}
MethodCallExpression anyCall = Expression.Call(typeof(Enumerable), "Any", [typeof(SupplierRecord)], records, ex2);
获取列表中要使用的任何方法似乎也可以正常工作。
{t.SupplierRecords.Any(s => s.SupplierPartNumber.ToLower().Contains(Convert("5", String)))}
最后为组件创建 lambda 表达式
Expression<Func<T, bool>> newExp = Expression.Lambda<Func<T, bool>>(expression, parameter);
似乎返回了正确的结果
{t => t.SupplierRecords.Any(s => s.SupplierPartNumber.ToLower().Contains(Convert("5", String)))}
但是当我运行查询时,即使应该有一个匹配项,我也没有得到任何结果。 如果有人知道我可能会错过什么,那将会非常有帮助。
目前对这个属性进行硬编码的原因是因为我还没有决定如何制作用户选择子属性的 UI。
正当我要发布此内容时,答案击中了我,但我想如果其他人将来遇到此问题,我无论如何都会发布它。
问题在于
SupplierRecords
未加载到上下文中。在应用表达式之前对供应商记录运行包含解决了问题。
context.Components.Include(x => x.SupplierRecords).Load();
Func<Component, bool>? expression = ExpressionBuilder.GetExpression<Component>(filters);
if (expression == null)
{
goto Fault;
}
return context.Components.Where(expression).AsQueryable();