附加到表达式

问题描述 投票:0回答:5

我关注了这个帖子:链接文本

Jason 举了个例子:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}

及其用法:

Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}

我有一个订单表,我按照上面的示例更改列名称,我得到了与帖子创建者类似的错误

没有为类型“System.Func

2[Models.Order,System.Boolean]' and 'System.Func
2[Models.Order,System.Boolean]”定义二元运算符 AndAlso。

有人对我缺少的东西有什么想法吗?

更新:

Eric,我进一步遵循了上一篇文章的用户的要求,这里链接文本

用户有这个

Expression<Func<Client, bool>> clientWhere = c => true;
Expression<Func<Order, bool>> orderWhere = o => true;
Expression<Func<Product, bool>> productWhere = p => true;

if (filterByClient)
{
    clientWhere = c => c.ClientID == searchForClientID;
}

现在,如果他在

filterByClient
中有各种条件,假设他有
clientid
和/或其他列名,那么如何构建
clientWhere
表达式?

c# linq
5个回答
33
投票

您正在尝试构建表示此内容的表达式树:

c => true && c.ClientFName == searchForClientFName

您实际上正在构建一个表示此的表达式树:

c => c=> true && c => c.ClientFName == searchForClientFName

这根本没有意义。

现在,您可能天真地认为这会起作用:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
// NOTICE: Combining BODIES:
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters); 
} 

这会在你的情况下产生一些代表的东西

c => true && c.ClientFName == searchForClientFName

看起来不错。但事实上这是脆弱的。假设你有

... d => d.City == "London" ...
... c => c.ClientName == "Fred Smith" ...

并且您使用了这种方法来组合它们。你会得到一个代表

的对象
c => d.City == "London" && c.ClientName == "Fred Smith"

d 到底在里面做什么?

此外,参数是通过对象标识匹配的,而不是通过参数名称匹配的。 如果你这样做

... c => c.City == "London" ...
... c => c.ClientName == "Fred Smith" ...

并将它们组合成

c => c.City == "London" && c.ClientName == "Fred Smith"

你们在同一条船上; “c.City”中的“c”与其他两个不同。

您实际上需要做的是创建一个

third参数对象,将其替换到两个 lambda 表达式的主体中,每次出现参数时,然后从生成的替换主体中构建一个新的 lambda 表达式树

您可以通过编写一个遍历表达式树主体的访问者来构建替换引擎,并按其进行重写。


25
投票
我很难理解 hvd 的

answer,所以我创建了一些代码以不同的方式解释它。 hvd 应该因建议 ExpressionVisitor 而受到赞扬。 我只是无法理解我正在使用的 Linq to X 类型输入函数上下文中的示例。

我希望这可以帮助其他人从这个角度来回答这个问题。

此外,我创建了组合代码作为扩展方法,以使其更易于使用。


using System; using System.Collections.Generic; using System.Linq.Expressions; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy"); Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" })); Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" })); Console.ReadLine(); } public class FullName { public string FirstName { get; set; } public string LastName { get; set; } } public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2) { return func1.CombineWithAndAlso(func2).Compile(); } } public static class CombineExpressions { public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2) { return Expression.Lambda<Func<TInput, bool>>( Expression.AndAlso( func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)), func1.Parameters); } public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2) { return Expression.Lambda<Func<TInput, bool>>( Expression.AndAlso( func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)), func1.Parameters); } private class ExpressionParameterReplacer : ExpressionVisitor { public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters) { ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>(); for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++) ParameterReplacements.Add(fromParameters[i], toParameters[i]); } private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; } protected override Expression VisitParameter(ParameterExpression node) { ParameterExpression replacement; if (ParameterReplacements.TryGetValue(node, out replacement)) node = replacement; return base.VisitParameter(node); } } } }
    

0
投票
如果您需要它,我创建了一个小型流畅库来动态创建 lambda 函数,而无需直接处理 System.Linq.Expressions。而且它可以轻松应对这种情况。举个例子:

static void Main(string[] args) { var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName); var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName); Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy"); var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"}; Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck)); toCheck = new FullName {FirstName = "Cat", LastName = "Boy"}; Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck)); Console.ReadLine(); }

GetComparer 方法寻找作为表达式传递的属性并找到 ho 来获取其值,然后构建一个新的表达式来处理比较。

最后,调用“组合”函数对两个函数进行求值。

如果您需要更多验证,您可以使用数组并在“组合 lambda”内迭代它

该库的代码和文档位于:

Kendar Expression Builder 虽然 nuget 包在这里:Nuget Expression Builder


0
投票
我尝试实现这种东西。我花了一天时间才找到答案。 我的解决方案基于基于谓词数组的循环过滤器。 请注意,它完全是通用且基于反射的,因为有关类和字段的唯一信息是字符串。 为了简单起见,我直接调用 Model 类,但在项目中,您应该由调用 Model 的控制器来调用。

我们开始吧: 模型部分,其中 T 是类中的泛型

public class DALXmlRepository<T> where T : class { public T GetItem(Array predicate) { IQueryable<T> QueryList = null; QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0)); for (int i = 1; i < predicate.GetLength(0); i++) { QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i)); } if (QueryList.FirstOrDefault() == null) throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found."); return QueryList.FirstOrDefault(); } }

现在的 LambdaExpression Builder,它是一个基础的(具有 String 类型或其他类型),您可以通过更多功能来改进它:

private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue) { LambdaExpression lambda = null; Expression Criteria = null; Random r = new Random(); ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString()); if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string)) { Expression left = Expression.PropertyOrField(predParam, FieldName); Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null); //Type du champ recherché Type propType = GenericArgument.GetProperty(FieldName).PropertyType; Expression right = Expression.Constant(FieldValue, propType); Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null); Criteria = Expression.Equal(LefttoUpper, RighttoUpper); } else { Expression left = Expression.PropertyOrField(predParam, FieldName); Type propType = GenericArgument.GetProperty(FieldName).PropertyType; Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType); Criteria = Expression.Equal(left, right); } lambda = Expression.Lambda(Criteria, predParam); return lambda; }

现在是通话功能:

public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter) { //Get the type Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel"); Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type ); //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML); ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) }); IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null }); //Building the string type Expression<func<T,bool>> to init the array Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool)); Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType); Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count); MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() }); if (method == null) throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name); int j = 0; IDictionaryEnumerator criterias = FieldFilter.GetEnumerator(); criterias.Reset(); while (criterias.MoveNext()) { if (!String.IsNullOrEmpty(criterias.Key.ToString())) { lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j); } else { throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString()); } j++; } Object item = method.Invoke(DalInstance, new object[] { lambda }); }

论据是: String Entity :实体类名称。 XMLContext :它是存储库的工作单元,我用参数来初始化模型类 Hashtable FieldsNameToGet :我想要取回的字段列表的索引/值 Hashtable FieldFilter :用于制作 Lambda 表达式的具有 FieldName/Content 的键/值

祝你好运。


0
投票
你需要检测他们是否使用相同的所以尝试这个:)

public static Expression<Func<T, bool>> AndAlso<T>( this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) { // need to detect whether they use the same // parameter instance; if not, they need fixing ParameterExpression param = expr1.Parameters[0]; if (ReferenceEquals(param, expr2.Parameters[0])) { // simple version return Expression.Lambda<Func<T, bool>>( Expression.AndAlso(expr1.Body, expr2.Body), param); } // otherwise, keep expr1 "as is" and invoke expr2 return Expression.Lambda<Func<T, bool>>( Expression.AndAlso( expr1.Body, Expression.Invoke(expr2, param)), param); }
    
© www.soinside.com 2019 - 2024. All rights reserved.