System.data.Sqlite: возвращает количество затронутых строк независимо от того, используется ли Query или NonQuery - PullRequest
0 голосов
/ 14 июня 2019

Я использую System.Data.SQLite поставщик ADO.NET для SQLite и следующий код Powershell для выполнения запросов (и незапросов) к БД Sqlite3:

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)

Возможно ли это?

Спасибо!

1 Ответ

1 голос
/ 28 июня 2019

Несколько комментариев перед ответом:

  • System.data.Sqlite поддерживает выполнение нескольких операторов SQL для одной команды, при условии, что CommandText имеет каждый допустимый оператор, разделенный точкой с запятой (;). Это означает, что может быть смесь запросов и операторов DML (т.е. INSERT, UPDATE, DELETE). Тот факт, что вы не хотите различать тип оператора в $Query, говорит мне о том, что вы, скорее всего, просто пропускаете операторы вслепую, поэтому он может содержать любую комбинацию операторов. Простое получение только одного значения (из запроса или DML) кажется слишком ограничивающим.
  • Использование DataAdapter для заполнения набора данных только для подсчета неэффективно. Вместо этого может быть лучше просто получить объект DataReader и сосчитать возвращаемые строки. Это также позволяет получать отдельное количество для каждого оператора запроса, что скрывается с помощью объекта DataAdapter. (Возможно, перечисление всех таблиц в результирующем наборе данных может получить одинаковое число, но я не уверен, что это всегда будет эквивалентно.)
    • Одна хорошая вещь: если вы настаиваете на использовании DataAdapter, он все равно будет выполнять операторы DML (даже если ожидаемый результат - запрос, который возвращает строки). Набор данных не будет изменен (заполнен), но все операторы в тексте команды будут по-прежнему влиять на изменения в базе данных, поэтому следующее решение все равно будет полезным.
  • Даже если код работал, я предполагаю, что строка, которая печатает «{0} Rows возвращено», предназначена для получения простого подсчета, но $ds.Tables[0].Rows должно быть $ds.Tables[0].Rows.Count.

Примечания об этом конкретном решении:

  • Ключ должен вызывать SQL-функцию 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();
...