动态 Linq 查询 - 如何构建 select 子句?
Posted
技术标签:
【中文标题】动态 Linq 查询 - 如何构建 select 子句?【英文标题】:Dynamic Linq query - how do I build the select clause? 【发布时间】:2012-08-16 16:04:10 【问题描述】:目前我正在尝试使用 Dynamic Linq 进行一些测试,但是我是新手,遇到了麻烦。
目前我有一个 DataTable 对象,我已通过对数据库的 SQL 查询填充该对象。现在我想在这个 DataTable 上执行动态 linq 查询。这可能看起来不合逻辑,但我的最终目标是能够对由两个不同数据库填充的两个不同 DataTable 执行连接,所以考虑到这一点,我希望它更有意义。 在继续解决该主要问题之前,我试图了解一个更简单的情况并对其进行一些试验。
问题一是我不完全确定 IQueryable(Of T) 和 IEnumerable(Of T) 之间的选择。我的理解是,如果你在内存中做所有事情,你会选择 IEnumerable。我会认为这适合我的情况,对吧?但是,当我查看更改 IQuerbyable 中的 IEnumerable 时(在下面的代码 sn-p 中),我惊讶地发现“x.Item(Fieldname)”不起作用!?我错过了什么?它给出的错误是:“后期绑定操作无法转换为表达式树”。
不管怎样,让我们看看。我已经完成了部分工作:
Dim desc As String = "Description"
Dim status As String = "Status"
Dim query As IEnumerable(Of DataRow) = From x In tab.AsEnumerable()
query = query.Where(Function(x As Object) x.Item(status) < 100)
For Each row As DataRow In query.ToList()
'Do something
Next row
这似乎工作正常。我使用了两个字符串变量,因为最后我想动态决定取哪个字段。所以,“x!Description”不是我要走的路。但是,现在我想在查询中添加一个投影,即选择多个列。我的期望是它应该像这样工作:
query = query.Select(Of String)(Function(x As Object) x.Item(desc))
(注意:我使用 Select(Of String),因为这个特定的列有它作为类型。这当然应该最终也被动态填充)
但是,这在运行时因 InvalidCastException 失败:“无法转换类型为 'WhereSelectEnumerableIterator2[System.Data.DataRow,System.String]' to type 'System.Collections.Generic.IEnumerable
1[System.Data.DataRow]' 的对象。”我真的不明白这里发生了什么;我猜出了点问题,因为我想返回 DataRow,但目前只选择一个字段,恰好是一个字符串。谁能解释/帮助我?
提前致谢!
好的,编辑,因为我可能应该从头开始提供更多信息: 我可以有可变数量的 where 或 select 子句。我的想法是我可以通过循环遍历包含此信息的列表来动态添加这些信息。所以我想我有点需要 IEnumerable 提供的 .Where() 和 .Select() 函数。
【问题讨论】:
【参考方案1】:您在以下行中将 query
声明为 IEnumerable(Of DataRow)
类型:
Dim query As IEnumerable(Of DataRow) = From x In tab.AsEnumerable()
现在,您想用您的Select
创建一个IEnumerable(Of String)
,这很好。
但您尝试将IEnumerable(Of String)
类型的结果分配给query
,IEnumerable(Of DataRow)
类型。
query = query.Select(Of String)(Function(x As Object) x.Item(desc))
由于IEnumerable(Of String)
不能转换为IEnumerable(Of DataRow)
,反之亦然,因此您会得到InvalidCastException
。
另外,我不知道你为什么在Where
和Select
中使用Function(x As Object)
,最好使用Function(row As DataRow)
,因为你已经知道你正在使用DataRow
。
此外,您的代码可以这样重写:
Dim desc As String = "Description"
Dim status As String = "Status"
Dim columnToUse = status
Dim query = From x In tab.AsEnumerable()
Where x.Item(status) < 100
Select x(columnToUse)
For Each item as String In query.ToList()
'Do something
Next
更改 columnToUse
将使您能够动态选择所需的字段。
要选择多个字段,您需要返回一组 Dictionary、ExpandoObjects 或 Tuples 或类似的东西。
例子:
Dim columnToUse = new String() desc, status ' select columns dynamically
Dim query = tab.AsEnumerable().Where(Function(row) row.Item(status) < 100)
' Selecting dynamically
Dim result1 = query.Select(function(row) columnToUse.ToDictionary(function(c) c, function(c) row(c)))
Dim result2 = query.Select(Function(row)
Dim exp As IDictionary(Of String, Object) = new ExpandoObject()
For Each column in columnToUse
exp(column) = row(column)
Next
return exp
End Function)
【讨论】:
我没有意识到 IEnumerable 是如何工作的。以下更改至少解决了 InvalidCastException: Dim projection As IEnumerable(Of String) = query.Select(Of String)(Function(x As DataRow) x.Item(desc)) 但是好吧,总的来说我不想获得一个字符串,我只想获得一个带有提供的字段的 DataRow。我将如何在该 DataRow 中返回 2 个字段?我事先不知道表中到底是什么,所以我不认为自定义 DataType 是一个解决方案。 如果您的问题回答另一部分:是的,请在此处使用IEnumerable
,因为您已经在处理内存中的DataTable
。
另外,我知道你展示的重写,但这不是我想要的。原因是我可能会通过 for 循环创建 IEnumerable。例如,我可以在列表中选择字段并在 For 循环中创建选择。或者我可以有可变数量的 where 子句并使用 for 循环添加它们。所以类似这样的伪代码: For each field in List query = query.Select(field As field.type) Next field
@Martao 我添加了两个如何动态选择多个字段的示例。请注意,您必须返回一组字典或类似的东西才能进行这项工作。多个Where
子句应该不是问题,因为链接它们不会像Select
投影那样改变结果数据类型。
感谢您的建议。我尝试了第一个解决方案并让它工作。它当然能让我走得更远。 ExpandoObject 也是一个我以前从未听说过的非常有趣的构造。可能会弹出更多问题,但我猜这个问题大部分都已回答。再次感谢。以上是关于动态 Linq 查询 - 如何构建 select 子句?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 LINQ 针对 Azure Cosmos Document DB SQL API 有效地进行动态查询?