问题
I programmed the following query which selects the grouped data key columns and sums the Amount column. It works perfect.
private static IEnumerable<GroupSum> GetListOfGroupedRows(IEnumerable<IGrouping<GroupKey, DataRow>> queryGroup)
{
IEnumerable<GroupSum> querySelect = queryGroup
.Select(g => new GroupSum
{
KeyS0 = g.Key.KeyS0,
KeyS1 = g.Key.KeyS1,
AggN0 = g.Sum(row => row.Field<double>("Amount"))
});
return querySelect;
}
The query uses the following types to group and sum.
private class GroupKey : IEquatable<GroupKey>
{
public string KeyS0 { get; set; }
public string KeyS1 { get; set; }
public bool Equals(GroupKey other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return string.Equals(this.KeyS0, other.KeyS0) &&
string.Equals(this.KeyS1, other.KeyS1);
}
public override int GetHashCode()
{
int hash0 = this.KeyS0 == null ? 0 : this.KeyS0.GetHashCode();
int hash1 = this.KeyS1 == null ? 0 : this.KeyS1.GetHashCode();
return hash0 + 31 * hash1;
}
}
private class GroupSum : GroupKey
{
public Double AggN0 { get; set; }
}
As the next step I want to program the equivalent query using the Linq Expressions.
I faced an issue that I do not know how to create MethodCallExpression for:
g.Sum(row => row.Field("Amount"))
I programmed the code below. I marked in the comments where I'm stuck.
private static void GetListOfGroupedRowsExpress()
{
//The MethodInfo for generic Field<T>(DataRow, String) can be retrieved by:
MethodInfo methInfo = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) });
ParameterExpression expRow = Expression.Parameter(typeof(DataRow), "row"); //Parametr: (row =>....)
//Property to bind
PropertyInfo propertyInfo = typeof(GroupSum).GetProperty("AggN0");
//This returns properly: row.Field<double>("Amount")
MethodCallExpression expCall = GetFieldCallExpression(expRow, methInfo, propertyInfo.PropertyType, "Amount");
//This returns properly: row => row.Field<double>("Amount")
LambdaExpression expRowValues = Expression.Lambda<Func<DataRow, double>>(expCall, expRow);
NewExpression expNewGroupKey = Expression.New(typeof(GroupSum));
ParameterExpression expG = Expression.Parameter(typeof(GroupSum), "g");
//This returns properly method info for: double Sum<T>()
MethodInfo methodInfoSum = typeof(Queryable).GetMethods().First(m =>
m.Name == "Sum"
&& m.ReturnType == typeof(double)
&& m.IsGenericMethod
);
//This returns properly method info for: double Sum<DataRow>()
MethodInfo methodInfoSumDataRow = methodInfoSum.MakeGenericMethod(new Type[] { typeof(DataRow) });
//And here I'm stuck. The code below compiles but at runtime it throws an error:
//Expression of type 'TestLinq.TestLinqDataTable+GroupSum' cannot be used for parameter of type 'System.Linq.IQueryable`1[System.Data.DataRow]' of method 'Double Sum[DataRow](System.Linq.IQueryable`1[System.Data.DataRow], System.Linq.Expressions.Expression`1[System.Func`2[System.Data.DataRow,System.Double]])'
MethodCallExpression expSumRows = Expression.Call(
null,
methodInfoSumDataRow,
expG,
expRowValues);
}
private static MethodCallExpression GetFieldCallExpression(ParameterExpression expRow, MethodInfo methodFieldGeneric,
Type type, string columnName)
{
List<Expression> list = new List<Expression>();
list.Add(expRow);
ConstantExpression expColumnName = Expression.Constant(columnName, typeof(string));
list.Add(expColumnName);
MethodInfo methodFieldTyped = methodFieldGeneric.MakeGenericMethod(type);
MethodCallExpression expCall = Expression.Call(null, methodFieldTyped, list);
return expCall;
}
Could anybody help me please how to construct the call expression for Sum() ?
回答1:
I've made some changes to your code:
private static Func<IGrouping<GroupKey, DataRow>, double> GetFunc()
{
//row => row.Field<double>("Amount")
//The MethodInfo for generic Field<T>(DataRow, String) can be retrieved by:
MethodInfo methInfo = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) });
ParameterExpression expRow = Expression.Parameter(typeof(DataRow), "row"); //Parametr: (row =>....)
//Property to bind
PropertyInfo propertyInfo = typeof(GroupSum).GetProperty(nameof(GroupSum.AggN0));
//This returns properly: row.Field<double>("Amount")
MethodCallExpression expCall = GetFieldCallExpression(expRow, methInfo, propertyInfo.PropertyType, "Amount");
//This returns properly: row => row.Field<double>("Amount")
var expRowValues = Expression.Lambda(expCall, expRow);
ParameterExpression expQuerygroup = Expression.Parameter(typeof(IGrouping<GroupKey, DataRow>), "g");
MethodCallExpression expSumRows = Expression.Call(typeof(Enumerable), nameof(Enumerable.Sum), new[] { expRow.Type }, expQuerygroup, expRowValues);
var sum = Expression.Lambda<Func<IGrouping<GroupKey, DataRow>, double>>(expSumRows, expQuerygroup);
return sum.Compile();
}
private static MethodCallExpression GetFieldCallExpression(ParameterExpression expRow, MethodInfo methodFieldGeneric, Type type, string columnName)
{
ConstantExpression expColumnName = Expression.Constant(columnName, typeof(string));
MethodInfo methodFieldTyped = methodFieldGeneric.MakeGenericMethod(type);
MethodCallExpression expCall = Expression.Call(null, methodFieldTyped, expRow, expColumnName);
return expCall;
}
There is a wonderful overload of Expression.Call
that finds and handles generic methods, and you don't need an array/List<>
to call Expression.Call
, because it has a params
overload.
Note that I've changed your code to Enumerable
... I don't think you can do what you want with Queryable
... But you can try to change it back. Note even that, while you have tried to make the code "universal" on the type of AggN0
(the PropertyInfo propertyInfo
that is used only to discover the type of AggN0
), the double
keyword appears in places where it is difficult to remove (the return type of the GetFunc()
method)
来源:https://stackoverflow.com/questions/50623451/c-sharp-linq-methodcallexpression-for-sum-datarow-used-for-groupedby-select