【问题标题】:LINQ Building Dynamic Expression in C#LINQ 在 C# 中构建动态表达式
【发布时间】:2015-11-29 18:48:55
【问题描述】:

我想在从数据库填充的 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)
            }
    );

这些字段在运行前是未知的。我会在运行时。

有什么方法可以实现吗?

[更新]

我开始写下面的代码:

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);

它可以编译,但是有几个问题:

  • 表达式1 是(Field1 = datarow.Field(0)) 而不是Field1 = datarow.Field&lt;int&gt;(0)
  • 与表达式 2 相同
  • 它抛出一个运行时异常:'System.Data.DataRow[]' cannot be used for return type 'System.Data.DataRow'在线var linq = Expression.Lambda...

你能帮忙吗?

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

【问题讨论】:

  • 查看动态 linq 库,除非您想开始构建表达式树
  • 首先,调用“AsEnumerable()”后跟“AsQueryable”似乎很糟糕。从您的示例中,您的数据在内存中。一般来说,如果你的数据在内存中,你不需要调用 AsQueryable,如果你的数据在 db 中(所以 myDataTable 实际上是一个对象集),你不需要先调用 AsEnumerable。
  • 构建动态查询(在字符串中)然后运行它
  • @Goerge Lica:我需要一个 IQueryable 结果。将 DataTable 转换为 IQueryable 的唯一方法是依次调用 .AsEnumerable( ).AsQueryable( )。 DataTable 不直接实现 AsQueryable()。

标签: c# linq


【解决方案1】:

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

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

1) 所以......您最终会针对您的特定情况请求“匿名类型”。如果当前未构建该类型,则在之前构建它,将其存储在您的字典中,然后......使用它。

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

希望这会有所帮助。我不能给你代码,因为我是在没有 Visual Studio 的设备上编写的。

【讨论】:

  • 我有每天在数据库中创建的表,其架构事先不知道。表可以包含大量数据,例如 100 列和数十万行。我需要在第三方 UI 组件(网格视图)中显示这些数据。该组件可以在服务器模式下工作(在服务器上进行所有计算,从而无需在客户端加载几 MB 的数据)。要在服务器模式下工作,它需要一个 IQueryable 结果。谢谢您的回答。我会更详细地研究它。到目前为止,它对我来说是中文的;)
  • 你只需要展示他们的内容吗? (比如报表什么的)或者你需要执行删除、更新、创建等操作?如果您只需要一个只读数据网格,只需构建一个动态的“红色代码”sql,填充一个数据表并将您的组件绑定到它。这是最实用的方法。我所描述的是通过使用表达式树和动态代码编译等高级功能来实现这一点的最先进的方式。
猜你喜欢
  • 2014-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-19
  • 1970-01-01
  • 2015-04-22
  • 1970-01-01
  • 2015-12-03
相关资源
最近更新 更多