我正在开发一个自动生成报告的库。这些报告在高级情况下是未知的,但调用它们的方式是在 Oracle 数据库中配置的。每个报告都有自己的类;在每个类中,都有一个返回 DataTable 的数据库查询。
报告生成过程效果很好,但我需要为每个创建一个报告索引文件。该配置也在数据库中找到,格式如下:
REPORT_ID / QUERY / ELEMENT_NAME
12 GROUPBY Reference 12 GROUPBY Internal_Seq 12 COUNT Item_Count 12 MIN Process_Date 12 MAX Job_Id 12 SUM Paid_Amount
由于每个报表的配置不同,我需要动态查询数据表的内容。那时我看到了来自ScottGu 的博客和帮助动态 Linq 查询的信息,让我到达了我需要去的地方。我确实修改了一些代码以满足我的需要,但这就是我所拥有的:
public void GetIndexData(DataTable p_Table)
{
string location = Utilities.GetLocation(this.GetType(), MethodBase.GetCurrentMethod());
m_log.Info(location + "Starting...");
try
{
//Create the datatable to contain the fields/aggregates from the datasource
if (m_Fields.Count > 0)
{
List<ArchiveField> groupFields = new List<ArchiveField>();
List<ArchiveField> aggreFields = new List<ArchiveField>();
foreach (ArchiveField _field in m_Fields)
{
if (_field.Query == "GROUPBY")
{
groupFields.Add(_field);
}
else
{
aggreFields.Add(_field);
}
}
//Build the GroupBy parameters using an anonymous type
List<string> fields = new List<string>();
object[] param = new object[groupFields.Count];
int counter = 0;
foreach (ArchiveField _field in groupFields)
{
fields.Add("get_Item(@" + counter.ToString() + ") as " + _field.Name);
param[counter] = _field.Name;
counter++;
}
var query = p_Table.AsEnumerable().AsQueryable().GroupBy("New(" + string.Join(",", fields.ToArray()) + ")", "it", param);
var groupType = query.ElementType;
//Build the Select parameters using Dynamic Lambda invocation
fields = new List<string>();
param = new object[aggreFields.Count];
counter = 0;
foreach (ArchiveField _field in aggreFields)
{
fields.Add("@" + counter.ToString() + "(it) as " + _field.Name);
param[counter] = GetGroupByExpression(groupType, _field.Name, typeof(long), _field.Query);
counter++;
}
foreach (ArchiveField _field in groupFields)
{
fields.Add("it.Key." + _field.Name + " as " + _field.Name);
}
query = query.Select("New(" + string.Join(",", fields.ToArray()) + ")", param);
//Convert the IQueryable to a datatable
PropertyInfo[] _props = query.ElementType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
DataTable dt = new DataTable();
foreach (PropertyInfo p in _props)
{
dt.Columns.Add(p.Name, p.PropertyType);
}
foreach (var l in query)
{
var temp = l;
dt.Rows.Add(_props.Select((PropertyInfo p) => p.GetValue(temp, null)).ToArray());
}
m_DataSource = dt;
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
m_log.Info(location + "Ending...");
}
}
private LambdaExpression GetGroupByExpression(Type p_groupType, string p_ColumnName, Type p_ColumnType, string p_Aggregate)
{
MethodInfo fieldMethod = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(string) });
fieldMethod = fieldMethod.MakeGenericMethod(p_ColumnType);
ConstantExpression colParam = Expression.Constant(p_ColumnName, typeof(string));
ParameterExpression rowParam = Expression.Parameter(typeof(DataRow), "r");
MethodCallExpression fieldMethodCall = Expression.Call(fieldMethod, rowParam, colParam);
var columnExpression = Expression.Lambda(fieldMethodCall, rowParam);
ParameterExpression groupParam = Expression.Parameter(p_groupType, "g");
MethodInfo aggrMethod = null;
MethodCallExpression aggrMethodCall = null;
if (p_Aggregate.ToUpper() == "COUNT")
{
aggrMethod = typeof(Enumerable).GetMethods().Single(m => m.Name.ToUpper() == p_Aggregate.ToUpper() & m.IsStatic & m.GetParameters().Length == 1);
aggrMethod = aggrMethod.MakeGenericMethod(typeof(DataRow));
aggrMethodCall = Expression.Call(aggrMethod, groupParam);
}
else
{
aggrMethod = typeof(Enumerable).GetMethods().Single(m => m.Name.ToUpper() == p_Aggregate.ToUpper() & m.ReturnType.Equals(p_ColumnType) & m.IsGenericMethod);
aggrMethod = aggrMethod.MakeGenericMethod(typeof(DataRow));
aggrMethodCall = Expression.Call(aggrMethod, groupParam, columnExpression);
}
return Expression.Lambda(aggrMethodCall, groupParam);
}
在代码中,m_DataSource 在我的类中定义为一个 DataTable,它应该包含最终结果。
这是来自 Microsoft 的 System.Linq.Dynamic 类的摘录:
public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (keySelector == null) throw new ArgumentNullException("keySelector");
if (elementSelector == null) throw new ArgumentNullException("elementSelector");
LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values);
LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values);
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "GroupBy",
new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)));
}
public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (keySelector == null) throw new ArgumentNullException("keySelector");
if (elementSelector == null) throw new ArgumentNullException("elementSelector");
LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values);
LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values);
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "GroupBy",
new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)));
}
我的代码在这部分失败,并出现错误“指定的转换无效”,每当它尝试查找查询对象的元素时:
foreach (var l in query)
我需要一个解决方案,因为我知道:
- 我必须在 Framework 3.5 中工作(没有机会改变这一点)。
- 所有调用都是动态的,因为报表类是通过后期绑定调用的,而且我事先永远不知道要使用哪个聚合(配置从一个报表更改为下一个报表)。
- 我必须限制第三方库的数量。如果一切都可以在我的代码中完成而不受外部影响,那就更好了。
- 我无权访问数据库以使用聚合生成辅助查询,因为数据来自报告类。
我缺少什么来获取我的数据表所需的值?最初的示例是否有缺陷,以至于找不到可以枚举我需要的数据行的演员表?