附加到表达式

时间:2010-02-09 18:06:59

标签: c# linq

我遵循了这个帖子:link text

杰森举了一个例子:

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,我进一步关注了上一篇文章的用户所要求的内容,link text

用户有此

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表达式?

4 个答案:

答案 0 :(得分:31)

您正在尝试构建一个表示此的表达式树:

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"

那里到底做了什么?

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

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

并将它们组合成

c => c.City == "London" && c.ClientName == "Fred Smith"
你在同一条船上; “c.City”中的“c”是不同的c 而不是其他两个。

你真正需要做的是创建一个第三个​​参数对象,替换它们在两个lambdas的主体中,以便每次出现它们的参数,然后建立一个来自生成的替换实体的新lambda表达式树

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

答案 1 :(得分:15)

我很难理解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);
            }
        }
    }
}

答案 2 :(得分: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来获取它的值,然后它构建一个新的Expression来处理比较。

最后,调用两个函数调用“组合”函数。

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

图书馆的代码和文档在这里:Kendar Expression Builder 虽然nuget包在这里:Nuget Expression Builder

答案 3 :(得分:0)

我试图实现这种东西。花了一天时间才发现。 我的解决方案基于基于谓词数组的循环中的过滤器。 作为一个注释,它完全是Generic和基于Reflection,因为关于类和字段的唯一信息是String。 为简单起见,我直接调用Model类,但是在项目中,你应该通过一个调用Model的控制器。

所以我们走了: Model部分,其中T是类中的Generic

    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构建器,它是一个基础构建器(使用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 });
        }

论点是: 字符串实体:实体类名称。 XMLContext:它是存储库的工作单元,我用它来初始化Model类 Hashtable FieldsNameToGet:我想要返回的字段列表的索引/值  Hashtable FieldFilter:用于生成Lambda表达式的FieldName / Content的键/值

祝你好运。