2

This is the string i want to convert into lambda expression

"o => new { Division = o.Division, Department = o.Department }"

Actually, I want to create a select lambda expression of anonymous type.

I am able to get this expression:

o => new SystemViewModel { Division = o.Division, Department = o.Department } 

but I want this expression

o => new { Division = o.Division, Department = o.Department}

This is the code

var dte = ctx.Database.SqlQuery<SystemViewModel>("select distinct Division,Department from TestReportView").ToList();"

var result2 = dte.Select(CreateNewStatement(string.Join(",", "Division,Department"))).ToList();

Func<SystemViewModel, SystemViewModel> CreateNewStatement(string fields)
{
    // input parameter "o"
    var xParameter = Expression.Parameter(typeof(SystemViewModel), "o");

    // new statement "new Data()"
    var xNew = Expression.New(typeof(SystemViewModel));

    // create initializers
    var bindings = fields.Split(',').Select(o => o.Trim())
        .Select(o => {

    // property "Field1"
    var mi = typeof(SystemViewModel).GetProperty(o);

    // original value "o.Field1"
    var xOriginal = Expression.Property(xParameter, mi);

    // set value "Field1 = o.Field1"
    return Expression.Bind(mi, xOriginal);
        }
    );

    // initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
    var xInit = Expression.MemberInit(xNew, bindings);

    // expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
    Type anonType = new { Name = "abc", Num = 123 }.GetType();
    var lambda = Expression.Lambda<Func<SystemViewModel, SystemViewModel>>(xInit, xParameter);
    var e = lambda.Body.ToString();
    //LambdaExpression.
    // compile to Func<Data, Data>
    return lambda.Compile();
}

This returns a list of SystemViewModel but I want a list of anonymous types, with only the two dynamic field Division and Department (SystemViewModel contains many other fields).

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
  • 2
    Can you explain the problem that you're trying to solve? What you've described here is your proposed solution to your problem. – Robert Harvey Aug 25 '18 at 17:50
  • Please add the code that you use to create first expression. – Selman Genç Aug 25 '18 at 17:51
  • https://stackoverflow.com/questions/3740532/how-to-use-expression-to-build-an-anonymous-type might help solve your problem. – Chris Aug 25 '18 at 17:54
  • Possible duplicate of [Is there a tool for parsing a string to create a C# func?](https://stackoverflow.com/questions/10864311/is-there-a-tool-for-parsing-a-string-to-create-a-c-sharp-func) – Adam Simon Aug 25 '18 at 18:03
  • I want to get dynamic select expression, but of anonymous type like dynamic – Muhammad Fahad Aug 25 '18 at 18:20

1 Answers1

1

As you already figured out, you cannot simply use var xNew = Expression.New(typeof(object)); instead of var xNew = Expression.New(typeof(SystemViewModel)); as it throws the exception:

System.ArgumentException: ''Division' is not a member of type 'System.Object''

So, to me, the solution is to create the anonymous type yourself. To create a type at runtime, you'll need to create a dynamic assembly with a dynamic module. You'll also have to think about caching those types, otherwise an out-of-memory exception will be around the corner.

Declare Lazy ModuleBuilder

First, we will declare a Lazy static ModuleBuilder that will create a dynamic module as a singleton:

private static Lazy<ModuleBuilder> ModuleBuilder = new Lazy<ModuleBuilder>(() =>
    AssemblyBuilder
        .DefineDynamicAssembly(new AssemblyName("AnonymousTypesAssembly"), AssemblyBuilderAccess.Run)
        .DefineDynamicModule("AnonymousTypesModule"));

This setup will make sure that only one dynamic assembly is created at runtime.

Declare a method for creating the anonymous type

private static Type CreateAnonymousType(IEnumerable<PropertyInfo> propertyInfos)
{
    var moduleBuilder = ModuleBuilder.Value;
    var typeName = Guid.NewGuid().ToString(); // Give the new type a random name
    var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);

    foreach (var propertyInfo in propertyInfos)
        typeBuilder.DefineField(propertyInfo.Name, propertyInfo.PropertyType, FieldAttributes.Public);

    return typeBuilder.CreateType();
}

CreateNewStatement modified

For flexibility, I've made this method generic and let you pass in the fieldnames as a set (we don't want doubles).

private static Func<TOriginal, object> CreateNewStatementFor<TOriginal>(ISet<string> fields)
{
    // input parameter "o"
    var xParameter = Expression.Parameter(typeof(TOriginal), "o");

    var propertyInfos = fields
        .Select(propertyName => typeof(TOriginal).GetProperty(propertyName))
        .ToArray();

    var anonymousType = CreateAnonymousType(propertyInfos);

    // create initializers
    var bindings = propertyInfos
        .Select(mi =>
        {
            // mi == property "Field1"

            // original value "o.Field1"
            var xOriginal = Expression.Property(xParameter, mi);

            // set value "Field1 = o.Field1"
            var mo = anonymousType.GetField(mi.Name);
            return Expression.Bind(mo, xOriginal);
        })
        .ToArray();

    // new statement "new Data()"
    var xNew = Expression.New(anonymousType);

    // initialization "new Data { Field1 = o.Field1, Field2 = o.Field2 }"
    var xInit = Expression.MemberInit(xNew, bindings);

    // expression "o => new Data { Field1 = o.Field1, Field2 = o.Field2 }"
    var lambda = Expression.Lambda<Func<TOriginal, object>>(xInit, xParameter);

    //LambdaExpression.
    // compile to Func<Data, Data>
    return lambda.Compile();
}

Caching

Now, each time you call CreateNewStatementFor, it will create a new dynamic type with a new random name. Even when creating a statement for the same properties. I don't have to mention that this is bad and will lead to memory leaks. To fix this, we will add a thread-safe caching mechanism where the key is based on the original type and its selected properties ordered ascending.

private static ConcurrentDictionary<string, object> StatementFuncCache = new ConcurrentDictionary<string, object>();

public static Func<TOriginal, object> GetOrCreateNewStatementFor<TOriginal>(ISet<string> fields)
{
    var key = $"{typeof(TOriginal).Name} {string.Join(",", fields.OrderBy(x => x))}";
    var func = StatementFuncCache.GetOrAdd(key, _ => CreateNewStatementFor<TOriginal>(fields));
    return (Func<TOriginal, object>)func;
}

Usage

var result2 = dte.Select(GetOrCreateNewStatementFor<SystemViewModel>(new HashSet<string> { "Division", "Department" })).ToList();
huysentruitw
  • 27,376
  • 9
  • 90
  • 133