[英]Convert string to lambda expression to pass to LINQ Select
This is the string i want to convert into lambda expression 这是我想要转换为lambda表达式的字符串
"o => new { Division = o.Division, Department = o.Department }"
Actually, I want to create a select lambda expression of anonymous type. 实际上,我想创建一个匿名类型的select lambda表达式。
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). 这将返回一个
SystemViewModel
列表,但我想要一个匿名类型列表,只有两个动态字段Division和Department( SystemViewModel
包含许多其他字段)。
As you already figured out, you cannot simply use var xNew = Expression.New(typeof(object));
正如您已经想到的那样,您不能简单地使用
var xNew = Expression.New(typeof(object));
instead of var xNew = Expression.New(typeof(SystemViewModel));
而不是
var xNew = Expression.New(typeof(SystemViewModel));
as it throws the exception: 因为它抛出异常:
System.ArgumentException: ''Division' is not a member of type 'System.Object''
System.ArgumentException:''Division'不是'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 声明Lazy ModuleBuilder
First, we will declare a Lazy
static ModuleBuilder
that will create a dynamic module as a singleton: 首先,我们将声明一个
Lazy
静态ModuleBuilder
,它将动态模块创建为单例:
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 CreateNewStatement已修改
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. 现在,每次调用
CreateNewStatementFor
,它都会创建一个带有新随机名称的新动态类型。 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();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.