yeomandev
yeomandev

Reputation: 11796

How to combine MemberExpression instances in C# for a LambdaExpression?

Given a class like this:

public class AnEntity
{
    public int prop1 { get; set; }
    public string prop2 { get; set; }
    public string prop3 { get; set; }
}

I am able to generate a lambda expression that selects one property like this:

ParameterExpression pe = Expression.Parameter(typeof(AnEntity), "x");
MemberExpression selectClause = Expression
    .MakeMemberExpression(
        pe, 
        typeof(AnEntity).GetProperty(prop2)); // selecting prop2

var selectLambda = Expression.Lambda<Func<AnEntity, object>>(selectClause, pe);

I can then use the lambda expression like this:

IQueryable<AnEntity> myEntities = dbContext.MyEntities.AsQueryable();
var results = myEntities.Select(selectLambda);

How can I add a second select clause to the selectLambda? For example, how would I select both prop2 and prop3?

Upvotes: 3

Views: 1438

Answers (3)

Brandon Barkley
Brandon Barkley

Reputation: 768

Below is a fleshed out example of what "usr" described in his solution using MemberInitExpression.

For my solution, I am going to provide you with a new class that you will want write the expression result into. In reality, this could be the POCO for the entity itself, but by specifying a different class, it is clearer which class is the class you are projecting into as compared to the class you are projecting from. As "usr" mentioned, you can also try to use Tuple or other constructs. My current favorite is to use the extra code I appended to the bottom to create a new type dynamically. This is a bit more flexible than Tuple, but it has some disadvantages in that it requires Reflection to access it for the most part.

Class to project into:

public class Holder
{
    public int Item1{ get; set; }
    public string Item2 { get; set; }
    public string Item3 { get; set; }
}

Code to create expression:

ParameterExpression paramExp = Expression.Parameter(typeof(AnEntity));
NewExpression newHolder = Expression.New(typeof(Holder));    
Type anonType = typeof(Holder);                
MemberInfo item1Member = anonType.GetMember("Item1")[0];
MemberInfo item2Member = anonType.GetMember("Item2")[0];
MemberInfo item3Member = anonType.GetMember("Item3")[0];

// Create a MemberBinding object for each member 
// that you want to initialize.
 MemberBinding item1MemberBinding =
 Expression.Bind(
     item1Member,
     Expression.PropertyOrField(paramExp, "prop1"));
 MemberBinding item2MemberBinding =
 Expression.Bind(
     item2Member,
     Expression.PropertyOrField(paramExp, "prop2"));
 MemberBinding item3MemberBinding =
 Expression.Bind(
     item3Member,
     Expression.PropertyOrField(paramExp, "prop3"));

// Create a MemberInitExpression that represents initializing 
// two members of the 'Animal' class.
MemberInitExpression memberInitExpression =
    Expression.MemberInit(
        newHolder,
        item1MemberBinding,
        item2MemberBinding,
        item3MemberBinding);

var lambda = Expression.Lambda<Func<AnEntity, Holder>>(memberInitExpression, paramExp);

Finally, how you would call the expression:

IQueryable<AnEntity> myEntities = dbContext.MyEntities.AsQueryable();
var results = myEntities.Select(selectLambda);

Here is some additional code if you wanted to define a type dynamically for the return value:

    public static Type CreateNewType(string assemblyName, string typeName, params Type[] types)
    {
        // Let's start by creating a new assembly
        AssemblyName dynamicAssemblyName = new AssemblyName(assemblyName);
        AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
        ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule(assemblyName);

        // Now let's build a new type
        TypeBuilder dynamicAnonymousType = dynamicModule.DefineType(typeName, TypeAttributes.Public);

        // Let's add some fields to the type.
        int itemNo = 1;
        foreach (Type type in types)
        {
            dynamicAnonymousType.DefineField("Item" + itemNo++, type, FieldAttributes.Public);
        }

        // Return the type to the caller
        return dynamicAnonymousType.CreateType();
    }

Upvotes: 2

usr
usr

Reputation: 171178

The way to answer questions like these is to write the pattern you want to build in strongly typed C# (select new { x.p1, x.p2 }) and use the debugger to look at the expression tree. Then you build that tree yourself.

What you will find is a MemberInitExpression instantiating a class with the two properties p1 and p2 that are being initialized from x.

You have to supply such a class somehow. Either define it yourself (class X { string p1, p2; }) or use a Tuple-like construct. Or an object[].

Understand that you can only have one return value. So it needs to encapsulate multiple values.

The easiest way is going to be using an object[]. Look at how the C# compiler does it.

Upvotes: 1

Ben Voigt
Ben Voigt

Reputation: 283684

There is no "second select return value". You get to return one value, which might be an aggregate.

The C# language provides a lot of syntactic sugar in this area, in the form of LINQ and anonymous types. To return an aggregate via a lambda built by expression trees, you'll have to create a type to hold all the different values (just like the C# compiler does behind the scenes when it sees an anonymous type) and then call its constructor passing the multiple values you want to return (this is actually somewhat easier than what C# does behind the scenes, which is call a bunch of property setters). At runtime, there are no anonymous types. They all get named, if not by the programmer then by the compiler.

In fact you can probably use a Tuple<Type1, Type2, Type3> instead of making a class especially for the purpose. But then your member properties won't be named nicely.

Upvotes: 0

Related Questions