Объединение двух лямбда-выражений в одно лямда-выражение - PullRequest
1 голос
/ 29 апреля 2011

Я динамически генерирую деревья выражений для отправки в LINQ Entities. Я предоставляю инфраструктуру и разрешаю разработчикам указывать выходные столбцы в качестве лямбда-выражений.

Например, скажем, у них есть столбец, который они могут указать, что способ извлечь значение из базы данных:

p => p.Aliases.Count()

и другой столбец:

p => p.Names.Where(n => n.StartsWith("hello"))

Проблема здесь в том, что мне нужно объединить оба из них в одно выражение.

Моя первая попытка была:

getter = field.SelectorExpression.Body;

И я получаю сообщение об ошибке The parameter 'p' was not bound in the specified LINQ to Entities query expression, которое появляется с тех пор, потому что LINQ не может знать, что находится в p.Aliases.Count().

Следующее, что я попробовал, было:

getter = Expression.Invoke(field.OrderBySelector, new[] {parameter});

Однако затем я получаю сообщение The LINQ expression node type 'Invoke' is not supported in LINQ to Entities., которое также появляется после этого, потому что я не ожидаю, что SQL Server будет знать, как запускать лямбда-выражения.

Прямо сейчас в основном есть это выражение:

item => new MyClass
{ 
    SomeValue = item.Name, // this was generated from some other code
    AnotherValue = item.SomeOtherColumn, // there can be lots of these
    AliasCount = p.Aliases.Count() // here of course is the problem
}

Очевидно, что я хочу заменить выражение на «p», когда создаю его, выражением для элемента (которое у меня есть и я знаю, как его использовать).

TLDR Существует ли простой способ заменить все экземпляры параметра, использованного в выражении LambdaExpression, на другое выражение?

1 Ответ

0 голосов
/ 12 мая 2011

Пример, с которого можно начать.

class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<Foo1, int>> expression1 = a => a.Foo2S.Count();

            Expression<Func<IEnumerable<Foo1>, IEnumerable<Foo2>>> expression2 =
                a => a.Select(b => new Foo2 { String1 = "asdf", Foo2Count = 3 });

            MemberAssignment foo2countAssignment = GetExpression(expression2);
            // in this case, it will be a ConstantExpression with a value of 3.
            var expression = foo2countAssignment.Expression as ConstantExpression;

            Expression<Func<IEnumerable<Foo1>, IEnumerable<Foo2>>> expression3 =
                a => a.Select(b => new Foo2 { String1 = "asdf", Foo2Count = b.Foo2S.Count() });

            foo2countAssignment = GetExpression(expression3);
            // in this case, it will be an Expression<Func<Foo1, int>>
            // exactly the same as expression1, except that it has a different parameter.
            var expressionResult = foo2countAssignment.Expression as MethodCallExpression;
            var foo2SPropertyExpression = expressionResult.Arguments[0] as MemberExpression;
            // This is the "b".Foo2SCount()
            var theBparameter = foo2SPropertyExpression.Expression as ParameterExpression;


            // Practical demonstartion.
            var mce = expression1.Body as MethodCallExpression;

            var selectStatement = expression2.Body as MethodCallExpression;
            var selectLambda = selectStatement.Arguments[1] as Expression<Func<Foo1, Foo2>>;
            var bParameter = selectLambda.Parameters[0];

            var me = mce.Arguments[0] as MemberExpression;
            var newExpression = me.Update(bParameter);
            // Then you go up the expression tree using Update to create new expression till first level.
            // Unless you find a way to replace me.
        }

        public static MemberAssignment GetExpression(Expression<Func<IEnumerable<Foo1>, IEnumerable<Foo2>>> expression2)
        {
            // a."Select"
            var selectStatement = expression2.Body as MethodCallExpression;
            // a.Select("b => new Foo2..."
            var selectLambda = selectStatement.Arguments[1] as Expression<Func<Foo1, Foo2>>;
            // a.Select(b => "new Foo2"
            var newFoo2Statement = selectLambda.Body as MemberInitExpression;
            // a.Select(b => new Foo2 {string1 = "asdf", !!Foo2Count = 3!! })
            return newFoo2Statement.Bindings[1] as MemberAssignment;
        }
    }

        public class Foo1
    {
        public IEnumerable<Foo2> Foo2S { get; set; }
    }

    public class Foo2
    {
        public string String1 { get; set; }
        public int Foo2Count { get; set; }
    }

По сути, эта программа пересекает дерево выражений и размещает каждый узел.Вы можете использовать ".GetType ()" на каждом узле, чтобы получить точный тип выражения и обработать их соответственно.(В этом примере он жестко запрограммирован и известен заранее).

В последнем примере показано, как можно заменить «a» в a.Foo2s.Count () на «b», чтобы его можно было заменить навторое более длинное выражение.

Тогда, конечно, вам нужно придумать, как можно автоматически обнаруживать и реплицировать все выражения 1 обратно в выражение 2.

Непростая задача.

...