【问题标题】:Convert string to lambda expression to pass to LINQ Select将字符串转换为 lambda 表达式以传递给 LINQ Select
【发布时间】:2018-08-27 03:00:01
【问题描述】:

这是我要转换成 lambda 表达式的字符串

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

其实我想创建一个匿名类型的select lambda表达式。

我可以得到这个表达式:

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

但我想要这个表达方式

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

这是代码

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();
}

这会返回SystemViewModel 的列表,但我想要一个匿名类型的列表,只有两个动态字段 Division 和 Department(SystemViewModel 包含许多其他字段)。

【问题讨论】:

标签: c# linq-expressions


【解决方案1】:

正如您已经知道的那样,您不能简单地使用var xNew = Expression.New(typeof(object)); 而不是var xNew = Expression.New(typeof(SystemViewModel));,因为它会引发异常:

System.ArgumentException: ''Division' 不是'System.Object' 类型的成员'

所以,对我来说,解决方案是自己创建匿名类型。要在运行时创建类型,您需要使用动态模块创建动态程序集。您还必须考虑缓存这些类型,否则很快就会出现内存不足的异常。

声明惰性模块构建器

首先,我们将声明一个 Lazy 静态 ModuleBuilder,它将创建一个作为单例的动态模块:

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

此设置将确保在运行时只创建一个动态程序集。

声明创建匿名类型的方法

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 已修改

为了灵活性,我已经将此方法设为通用方法,并让您将字段名作为一组传递(我们不希望双打)。

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();
}

缓存

现在,每次调用CreateNewStatementFor,它都会创建一个具有新随机名称的新动态类型。即使为相同的属性创建语句。我不必提到这很糟糕,并且会导致内存泄漏。为了解决这个问题,我们将添加一个线程安全的缓存机制,其中键基于原始类型,并且其选定的属性按升序排列。

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;
}

用法

var result2 = dte.Select(GetOrCreateNewStatementFor<SystemViewModel>(new HashSet<string> { "Division", "Department" })).ToList();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多