LINQ在C#中构建动态表达式

时间:2015-09-03 19:33:59

标签: c# linq

我想在从DB填充的DataTable上动态构建以下Select语句中使用的linq表达式:

IQueryable iq = myDataTable
    .AsEnumerable()
    .AsQueryable()
    .Select(
        d => new {
            Field1 = d.Field<int>(37),
            Field2 = d.Field<string>(0),
            Field3 = d.Field<DateTime>(1)
            }
    );

运行前不知道这些字段。我将得到字段列表(&#34; Field1&#34;,&#34; Field2&#34;,&#34; Field3&#34; ...),它们的类型(int,string,datetime ...... )及其在运行时的索引(37,0,1 ...)。

有没有办法实现这个目标?

[UPDATE]

我开始编写以下代码:

Type type = typeof(DataRow);

MethodInfo mi1 = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(int) });
mi1 = mi1.MakeGenericMethod(type);
List<Expression> list1 = new List<Expression>();
ParameterExpression datarow1 = Expression.Parameter(type, "datarow");
ConstantExpression constant1 = Expression.Constant(37, typeof(int));
list1.Add(datarow1);
list1.Add(constant1);
ReadOnlyCollection<Expression> collection1 = new ReadOnlyCollection<Expression>(list1);
MethodCallExpression right1 = Expression.Call(null, mi1, datarow1, constant1);
Expression left1 = Expression.Parameter(type, "Field1");
var expression1 = Expression.Assign(left1, right1);

MethodInfo mi2 = typeof(DataRowExtensions).GetMethod("Field", new Type[] { typeof(DataRow), typeof(int) });
mi2 = mi2.MakeGenericMethod(type);
List<Expression> list2 = new List<Expression>();
ParameterExpression datarow2 = Expression.Parameter(type, "datarow");
ConstantExpression constant2 = Expression.Constant(0, typeof(int));
list2.Add(datarow2);
list2.Add(constant2);
ReadOnlyCollection<Expression> collection2 = new ReadOnlyCollection<Expression>(list2);
MethodCallExpression right2 = Expression.Call(null, mi2, datarow2, constant2);
Expression left2 = Expression.Parameter(type, "Field2");
var expression2 = Expression.Assign(left2, right2);

List<Expression> bindings = new List<Expression>();
bindings.Add(expression1);
bindings.Add(expression2);
var newInit = Expression.New(typeof(object));
var init = Expression.NewArrayInit(type, bindings);

var dr = Expression.Parameter(type, "dr");
var linq = Expression.Lambda<Func<DataRow, DataRow>>(init, dr);

然而,它汇编了几个问题:

  • expression1为(Field1 = datarow.Field(0))而非Field1 = datarow.Field<int>(0)
  • 表达式2相同
  • 它会在行'System.Data.DataRow[]' cannot be used for return type 'System.Data.DataRow'
  • 上抛出运行时异常:var linq = Expression.Lambda...

你能帮忙吗?

@George 我想我不需要Type字典,因为我有一组预定义类型。

1 个答案:

答案 0 :(得分:0)

我强烈建议在这种情况下构建查询字符串并针对您的数据库运行它。

但是......为了艺术,你可以做到这一点。要实现您的目标,您需要的不仅仅是创建表达式树。首先,对于在运行时找到的每个投影案例,您将需要一个匿名类型的“池”。 Id est,你需要一个字典&lt; somekey,sometype&gt;其中somekey可以是表示列的串行化组合(列索引等)的字符串,sometype将是真正的System.Type。但是等待那些类型在编译时不存在。问题是:如何创建这些类型?唯一的解决方案是使用Reflection.Emit和AssemblyBuilder,TypeBuilder,ModuleBuilder等...以便在运行时创建新类型。不管你信不信,匿名类型只是普通类型,但C#编译器为我们带来了这种魔力。您还需要为数据表中找到的每个列以及新类型中定义的每个属性创建映射。

1)所以......你最终会为你的特殊情况请求一个“匿名类型”。如果当前没有构建该类型,只需在之前构建它,将其存储在字典中然后......使用它。

2)剩下的工作是发出实际上很简单的选择体。使用Expression.MemberInit和Expression.Bind API。在谷歌上搜索它们,你会发现很多信息。要找出使用Expression.Bind“绑定”的内容,请在第二个字典中搜索(列和属性之间的映射)。

希望这会有所帮助。我不能给你代码,因为我是从一个我没有visual studio的设备上写的。