System.LINQ.Dynamic:将Select(“new(…)”)select到List <T>(或任何其他的<T>的可枚举集合)
说我有一个DataTable与四列公司(string),基金(string),国家(string),价值(双):
table1.Rows.Add("Company 1","Fund 1","NY",100)); table1.Rows.Add("Company 2","Fund 1","CA",200)); table1.Rows.Add("Company 3","Fund 1","FL",300)); table1.Rows.Add("Company 4","Fund 2","CA",400)); table1.Rows.Add("Company 5","Fund 1","NY",500)); table1.Rows.Add("Company 6","Fund 2","CA",600)); table1.Rows.Add("Company 7","Fund 3","FL",700));
我想要使用System.LINQ.Dynamic构build一个dynamic查询,这个查询在公司,基金或者状态上进行分组,然后按照标准select我的组作为第一列,然后求和(value):
string groupbyvalue="Fund"; var q1= table1.AsEnumerable().AsQueryable() .GroupBy(groupbyvalue,"it") .Select("new ("+groupbyvalue+" as Group, Sum(Value) as TotalValue)");
在上面的查询中,所选的groupbyvalue(Group)将始终是一个string,总和将总是一个double,所以我想能够转换成类似List的东西,其中Result是具有属性的对象Group(string )和TotalValue(双)。
我遇到了很多麻烦,谁能解释一下?
首先,您将在您的Select子句中以Key
访问当前的分组值:
.Select("new (Key as Group, Sum(Value) as TotalValue)");
这应该使您的查询工作。 更难的问题是如何将返回的对象(将具有从DynamicClass
inheritance的dynamic生成的types)转换为静态types。
选项1:使用reflection来访问dynamic对象的Group
和TotalValue
属性。
选项2:使用编译的expression式树生成轻量级代码以访问Group
和TotalValue
属性。
选项3:修改dynamic库以支持强types的结果。 原来这很简单:
-
在
ExpressionParser.Parse()
,在私有字段中捕获types参数:private Type newResultType; public Expression Parse(Type resultType) { newResultType = resultType; int exprPos = token.pos; // ...
-
在
ExpressionParser.ParseNew()
的结尾处,我们将尝试在默认为dynamictypes之前使用newResultType
:Expression ParseNew() { // ... NextToken(); Type type = newResultType ?? DynamicExpression.CreateClass(properties); MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); return Expression.MemberInit(Expression.New(type), bindings); }
-
最后,我们需要一个强types的
Select()
:public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(TResult), selector, values); return source.Provider.CreateQuery<TResult>( Expression.Call( typeof(Queryable), "Select", new Type[] { source.ElementType, typeof(TResult) }, source.Expression, Expression.Quote(lambda))); }
来自原始
Select()
的唯一更改是我们引用TResult
地方。
现在我们只需要一个指定types来返回:
public class Result { public string Group { get; set; } public double TotalValue { get; set; } }
而你更新的查询将如下所示:
IQueryable<Result> res = table1.AsQueryable() .GroupBy(groupbyvalue, "it") .Select<Result>("new (Key as Group, Sum(Value) as TotalValue)");
我通过以下方式将返回的数据转换为List <IExampleInterface>:
1扩展selectdynamicLINQ的方法来支持genericstypes。 在这里看第一个答案
2使用Select方法(非常类似于dahlbyk答案的第一行)
Select<dynamic>("new (Key as Group, Sum(Value) as TotalValue)")
如果你需要多列,你可以使用以下内容:
GroupBy(@"new (Fund, Column1, Column2)", "it"). Select<dynamic>("new (Key.Fund, Key.Column1, Key.Column2, Sum(Value) as TotalValue)")
3将数据转换为列表。
var data = ... GroupBy("...").Select<dynamic>("..."). Cast<dynamic>().AsEnumerable().ToList();
4使用Impromptu将List转换为List <IExampleInterface>
var result = new List<IExampleInterface>(); foreach (var d in data) { dynamic r = Impromptu.ActLike<IExampleInterface>(d); result.Add(r); }
另一种可能性是扩展DLinq的查询语言,以便在查询string的new
子句中指定types名称。
我已经在另一个stackoverflow答案中描述了dynamic.cs中需要的更改。
它将允许你提出像下面这样的查询:
IQueryable<Result> res = table1.AsQueryable() .GroupBy(groupbyvalue, "it") .Select("new Result(Key as Group, Sum(Value) as TotalValue)") as IQueryable<Result>;