问题
Say I have a DataTable with four columns, Company (string), Fund (string), State (string), Value(double):
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));
I want to use System.LINQ.Dynamic to build a dynamic query which groups on either Company, Fund, or State, and then selects my group by criteria as the first column, and sum(value):
string groupbyvalue="Fund";
var q1= table1.AsEnumerable().AsQueryable()
.GroupBy(groupbyvalue,"it")
.Select("new ("+groupbyvalue+" as Group, Sum(Value) as TotalValue)");
In the above query, the selected groupbyvalue (Group) will always be a string, and the sum will always be a double, so I want to be able to cast into something like a List, where Result is an object with properties Group (string) and TotalValue (double).
I'm having a lot of trouble with this, can anyone shed some light?
回答1:
First, you'll access the current grouped value as Key
in your Select clause:
.Select("new (Key as Group, Sum(Value) as TotalValue)");
That should make your query work. The harder question is how to turn the returned objects, which will have a dynamically generated type that inherits from DynamicClass
, into a static type.
Option 1: Use reflection to access the dynamic object's Group
and TotalValue
properties.
Option 2: Use compiled expression trees for lightweight code generation to access the Group
and TotalValue
properties.
Option 3: Modify the Dynamic library to support a strongly-typed result. This turns out to be rather simple:
In
ExpressionParser.Parse()
, capture the type argument in a private field:private Type newResultType; public Expression Parse(Type resultType) { newResultType = resultType; int exprPos = token.pos; // ...
Near the end of
ExpressionParser.ParseNew()
, we'll try to usenewResultType
before defaulting to a dynamic type: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); }
Finally, we need a strongly typed version of
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))); }
The only changes from the original
Select()
are places we referenceTResult
.
Now we just need a named type to return:
public class Result
{
public string Group { get; set; }
public double TotalValue { get; set; }
}
And your updated query will look like this:
IQueryable<Result> res = table1.AsQueryable()
.GroupBy(groupbyvalue, "it")
.Select<Result>("new (Key as Group, Sum(Value) as TotalValue)");
回答2:
I casted returned data to List<IExampleInterface> by the following way:
1 Extend Select method of dynamic linq to support generic types. See first answer here
2 Use the Select method (very similar to first line of dahlbyk's answer)
Select<dynamic>("new (Key as Group, Sum(Value) as TotalValue)")
If you need multiple columns, you can use following:
GroupBy(@"new (Fund, Column1, Column2)", "it").
Select<dynamic>("new (Key.Fund, Key.Column1, Key.Column2, Sum(Value) as TotalValue)")
3 Convert data to list.
var data = ...
GroupBy("...").Select<dynamic>("...").
Cast<dynamic>().AsEnumerable().ToList();
4 Use Impromptu to convert List to List<IExampleInterface>
var result = new List<IExampleInterface>();
foreach (var d in data)
{
dynamic r = Impromptu.ActLike<IExampleInterface>(d);
result.Add(r);
}
回答3:
Another possibility is to extend query language of DLinq with the possibility to specify the type name in the new
clause in query string.
I have described the changes need in Dynamic.cs in another stackoverflow answer.
It will allow you to pose queries like the following:
IQueryable<Result> res
= table1.AsQueryable()
.GroupBy(groupbyvalue, "it")
.Select("new Result(Key as Group, Sum(Value) as TotalValue)")
as IQueryable<Result>;
回答4:
Another, easier, way to do that is to use Json serializing/deserializing using the Newtonsoft.Json like this:
Have a class with the wanted properties:
public class MyClass
{
public string Group;
public double Total;
}
After your query, you Json-serialize then deserialize to the data type you want:
string jsonData = JsonConvert.SerializeObject(q1);
List<MyClass> typedResult = JsonConvert.DeserializeObject<List<MyClass>>(jsonData);
来源:https://stackoverflow.com/questions/1465700/system-linq-dynamic-select-new-into-a-listt-or-any-other-enumerabl