首页 > 解决方案 > System.data.Sqlite:无论使用 Query 还是 NonQuery,都返回受影响的行数

问题描述

我正在使用System.Data.SQLiteSQLite 的 ADO.NET 提供程序和以下 Powershell 代码对 Sqlite3 DB 执行查询(和非查询):

Function Invoke-SQLite ($DBFile,$Query) {

    try {
        Add-Type -Path ".\System.Data.SQLite.dll"
    }
    catch {
        write-warning "Unable to load System.Data.SQLite.dll"
        return
    }
    if (!$DBFile) {
        throw "DB Not Found" R
        Sleep 5
        Exit
    }
    $conn = New-Object System.Data.SQLite.SQLiteConnection
    $conn.ConnectionString="Data Source={0}" -f $DBFile
    $conn.Open()
    $cmd = $Conn.CreateCommand()
    $cmd.CommandText = $Query
    #$cmd.CommandTimeout = 10
    $ds = New-Object system.Data.DataSet
    $da = New-Object System.Data.SQLite.SQLiteDataAdapter($cmd)
    [void]$da.fill($ds)
    $cmd.Dispose()
    $conn.Close()

    write-host ("{0} Row(s) returned " -f ($ds.Tables[0].Rows|Measure-Object|Select -ExpandProperty Count)) 

    return $ds.Tables[0]
}

问题是:虽然知道在查询操作中选择了多少行是微不足道的,但如果操作是 INSERT、DELETE 或 UPDATE(非查询),则情况并非如此

我知道我可以使用 ExecuteNonQuery 方法,但我需要一个通用包装器,它返回受影响的行数,同时不知道它执行的查询(例如,Invoke-SQLCmd 会这样做)

那可能吗?

谢谢!

标签: sqlitepowershellado.netsystem.data.sqlite

解决方案


答案前的一些评论:

  • System.data.Sqlite 支持对一个命令执行多条 SQL 语句,只要 CommandText 中的每个有效语句用分号 (;) 分隔即可。这意味着可能存在查询和 DML 语句(即 INSERT、UPDATE、DELETE)的混合。您不想区分语句类型的事实$Query告诉我,您可能只是盲目地传递语句,因此它可能包含语句的任何组合。仅获取一个值(无论是来自查询还是 DML)似乎过于局限。
  • 使用 DataAdapter 填充数据集只是为了获取计数是低效的。相反,最好只获取一个 DataReader 对象并计算返回的行数。这还允许检索每个查询语句的单独计数,使用 DataAdapter 对象会掩盖这些计数。(也许枚举结果数据集中的所有表可以获得相同的数字,但我不确定这是否总是等价的。)
    • 一件好事是,如果您坚持使用 DataAdapter,它仍然会执行 DML 语句(即使预期结果是返回行的查询)。数据集不会被更改(填充),但命令文本中的所有语句仍然会影响数据库中的更改,因此以下解决方案仍然有用。
  • 即使代码有效,我假设打印“{0} Rows returned”的行是为了得到一个简单的计数,但$ds.Tables[0].Rows需要是$ds.Tables[0].Rows.Count.

关于此特定解决方案的注意事项:

  • 关键是调用任一 sqlite SQL 函数 changes()total_changes(). 这些可以使用 SQL 检索: SELECT total_changes();. 我建议total_changes()在命令之前和之后获取,然后减去差异。这将对一个命令执行的多个语句进行更改。
  • 我不是 PowerShell 专家,所以我在 C# 中测试了所有内容。将下面的代码更多地视为伪代码,因为它可能需要调整。

编码:

$conn = New-Object System.Data.SQLite.SQLiteConnection
try {
  $conn.ConnectionString="Data Source={0}" -f $DBFile
  $conn.Open()

  $cmdCount = $Conn.CreateCommand()
  $cmd = $Conn.CreateCommand()
  try {
    $cmdCount.CommandText = "SELECT total_changes();"

    $beforeChanges = $cmdcount.ExecuteScalar()

    $cmd.CommandText = $Query

    $ds = New-Object System.Data.DataSet
    $da = New-Object System.Data.SQLite.SQLiteDataAdapter($cmd)

    $rows = 0
    try {
      [void]$da.fill($ds)
      foreach ($tbl in $ds.Tables) {
         $rows += $tbl.Rows.Count;  
      }    
    } catch {}

    $afterChanges = $cmdcount.ExecuteScalar()
    $DMLchanges = $afterChanges - $beforeChanges

    $totalRowAndChanges = $rows + $DMLchanges

    # $ds.Tables[0] may or may not be valid here.
    # If query returned no data, no tables will exist.
  } finally {
    $cmdCount.Dispose()
    $cmd.Dispose()
  }
} finally {
  $conn.Dispose()
}

或者,您可以消除 DataAdapter:

    $cmd.CommandText = $Query

    $rdr = $cmd.ExecuteReader()

    $rows = 0
    do {
      while ($rdr.Read()) { 
        $rows++
      }
    } while ($rdr.NextResult())
    $rdr.Close();

推荐阅读