如何在C#的DataTable/Dataset中应用SQL查询?

6
我有一个应用程序,用户可以输入SQL查询作为文本,在我的应用程序内部我需要运行它来对C# DataTable/Dataset进行查询。这是否可行?
编辑:根据答案以及进一步的研究,无法实现 - 没有办法将SQL查询应用于已读入您的应用程序的表格。请查看答案以获取可能的解决方法。
编辑2:在我的情况下,最终解决方案是使用ANTLR实现一个简单的解析器。它允许用户使用'AND'和'OR'关键字以及括号输入简单的查询。ANTLR生成的类会将其转换为一组指令,然后我可以使用它们来查询C# Dataset。

是的,但这可能不是一个好主意。如何防止 SQL 注入?你看过 SqlCommand 和 SqlDataAdapter 类吗? - Josh C.
1
如果你是为了教程/教育目的而创建应用程序,那么这是可以的。否则,这将被视为一种不良的编程实践。你可以使用Entity Framework,最终你会有学习一些ORM工具的经验。 - user841123
@Josh C. 快速查看SqlCommand和SqlDataAdapter类给我留下的印象是,我只能将它们应用于数据库(因为连接字符串)。我如何将它们应用于驻留在C#数据集中的C# DataTable(即已经读入内存)? - Eternal21
@DarshanJoshi 数据集/数据表存在于应用程序内部,而不是实际数据库,并且是只读的(没有与实时数据库的连接)。再次强调,我不关心SQL注入问题。我只想让用户以一种简单且高度可定制的方式输入他们的搜索条件。 - Eternal21
你想在数据集/数据表上运行基于SQL的查询吗? - user841123
啊,我误解了。你只是进行筛选吗?如果是这样的话,DataTable 有一个 .Select("过滤条件"); 方法。 - Josh C.
4个回答

5
如果您的用户输入的不仅仅是最简单的选择语句,那么您在这方面将会遇到很大的困难。我想编写一个完整的 SQL 解析器对于您的项目来说成本可能过高,但这基本上就是您所讨论的内容。
对于我们自己开发的 ORM,我有一个类可以将基本预定义的 SQL 查询转换为可用于 DataTable.Select 的查询语句,但 where 子句是由 SqlParameters 生成的。
可能的解决方案是,您可以结合以下项目,接近您想要的结果: Linqer(SQL 转 LINQ 转换器) 然后 LINQ to DataSet 我自己没有使用过 Linqer。
一些其他想法
我相信你已经思考过这个问题,但是如果你稍微放大一点视野,可能会发现有更好的方法来解决这个问题。严格来说,对于一个未知查询的缓存查询,意味着你必须填充缓存中的所有可能数据,或者能够在提交查询时调用该数据。从定义上讲,这不能比直接查询源更提高性能,除非在缓存失效之前你经常命中缓存以使其变得有价值。对于一个特定报告系统(我的假设),我倾向于怀疑情况并担心它不会在任何情况下超越数据库引擎。
@JoshC也提到了Sqlite的可能性,还有SQL Server 2012 LocalDB也可以胜任,尽管它们肯定不是.net数据集。

不需要编写自己的SQL解析器,这正是我试图查看.NET是否已经支持可以接受SQL查询的方法的原因。根据迄今为止的答案,似乎它并没有支持。 - Eternal21

4

如果您想在应用程序中针对 C# DataTable/Dataset 运行搜索字符串

您可以在 Select 方法中使用过滤表达式。

myDataTable.Select("columnName1 like '%" + value + "%'");


问题在于,Select() 函数内的搜索字符串不是 SQL 查询。我不能指望我的最终用户了解一些神秘的 .NET 语法,但我可以期望他们了解 SQL 语法。有没有办法将 SQL 查询转换为可应用于 C# 数据集的内容? - Eternal21
@Eternal21 如果你只是在筛选,你可以将选择缩减到where子句。如果你根据选择隐藏/显示列,你可以从选择中解析列名并相应地修改视图。 - Josh C.
当列名中间有空格时,这个工作是否正常? - Peck_conyon
将列名用方括号[ ]括起来以进行显式过滤。 - Turbot

0
这对我来说可行,用于将DataSet导出到SQLite的操作如下所示:

$Data = <your DataSet>

$SQLiteConnection = [System.Data.SQLite.SQLiteConnection]::new('DataSource=:MEMORY:')
$SQLiteConnection.Open()

[System.String[]]$tableNames = $Data.Tables.TableName

foreach ($table in $tableNames) {
 $columnDefinitions = @()
 foreach ($column in $Data.Tables[$table].Columns) {
#  Ought to create mapping between Types and DBTypes AnsiString, Binary, Byte, Boolean, Currency, Date, DateTime, Decimal, Double, Guid, Int16, Int32, Int64, Object, SByte, Single, String, Time, UInt16, UInt32, UInt64, VarNumeric, AnsiStringFixedLength, StringFixedLength, Xml, DateTime2, DateTimeOffset
  $columnDefinitions += ($column.ColumnName + ' ' + $column.DataType.Name)
 }

 $sqliteCommand = $SQLiteConnection.CreateCommand()
 $sqliteCommand.CommandText = 'CREATE TABLE IF NOT EXISTS ' + $table + ' (' + [System.String]::Join(', ', $columnDefinitions) + ')'
 $rowsAffected = $sqliteCommand.ExecuteNonQuery()

 $InsertCommand = 'INSERT INTO ' + $table + ' (' + [System.String]::Join(', ', $Data.Tables.Columns.ColumnName) + ') VALUES (:' + [System.String]::Join(', :', $Data.Tables.Columns.ColumnName) + ')'

 $sqliteDataAdapter = [System.Data.SQLite.SQLiteDataAdapter]::new($SQLiteConnection)
 $sqliteDataAdapter.MissingSchemaAction = [System.Data.MissingSchemaAction]::AddWithKey
 $sqliteDataAdapter.InsertCommand = [System.Data.SQLite.SQLiteCommand]::new($InsertCommand, $SQLiteConnection)

 foreach ($column in $Data.Tables[$table].Columns) {
  $null += $sqliteDataAdapter.InsertCommand.Parameters.Add($column.ColumnName, $column.DataType.Name, $column.MaxLength, $column.ColumnName)
 }

 $rowsAffected = $sqliteDataAdapter.Update($Data, $table)
}

0

实际上有一个简单的解决方案。

  1. 将您的数据在 DataTable 中导出到 SQLite 内存数据库中。
  2. 在 SQLite 内存数据库上运行 SQL。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接