ラベル sqlite3 の投稿を表示しています。 すべての投稿を表示
ラベル sqlite3 の投稿を表示しています。 すべての投稿を表示

PowerShellでsqlite3のパラメータクエリを発行

PowerShellでsqlite3を利用する記事を書いたが、SQLインジェクション対策を一切していなかった。PowerShellで作成するものの用途的に特にいらないかなと思っていたが、このご時世に対策できていないのもかっこ悪いのでSQLパラメータを利用するパターンのものを作成した。



#######################################################
# パラメータ変数格納用のクラス(SQLiteParameterだとインテリセンスやコードが長くなるため独自のクラスを作成)
#######################################################
Class clsSqlParameter {
    [string]$ParameterName;
    [object]$Value;

    # コンストラクタ
    clsSqlParameter(){

    }

    # コンストラクタ
    clsSqlParameter([string]$_ParameterName,[object]$_Value){
        $this.ParameterName = $_ParameterName;
        $this.Value = $_Value;
    }
}
   

    ############################################################
    # SQLを実行しデータを取得する。戻り値はDataTable
    # パラメータ変数を利用するので、System.Collections.Generic.List[clsSqlParameter]クラスのインスタンスを指定
    # <例>
    #  $lstParameters  = New-Object "System.Collections.Generic.List[clsSqlParameter]";
    #  $para = New-Object "clsSqlParameter" -ArgumentList "@para1", "060  ";
    #  #$para.ParameterName = "@para1";
    #  #$para.Value = "060  ";
    #  $lstParameters.Add($para);
    #   $dtResult = $dba.GetDataPara("SELECT * FROM KEN_ALL WHERE 郵便番号_5桁 = @para1 ",$lstParameters)
    ############################################################
    [System.Data.DataTable]GetDataPara([string]$strSQL,$lstParas){
        # エラーが発生した際に後続の処理を停止
        $ErrorActionPreference = "Stop"

        $sqlcmd = $null;
        $sqliteConnection = $null;
        $dataTable = $null;

        try {

            # SQLiteへの接続およびSQLステートメント発行用のSystem.Data.SQLite.SQLiteCommandの生成
            $sqliteConnection = New-Object System.Data.SQLite.SQLiteConnection;
           
            # .NETのライブラリのSQLiteは共有ファイルのデータベースへアクセスする場合、「\\SV-FOLDER\DataBase\Sample.sqlite3」のようなパスを指定したときに正常に接続できない
            # この場合は、「\\\\SV-FOLDER\DataBase\Sample.sqlite3」とする必要があることがわかったため修正
            $strConnectionString = "";
            if ($this.fstrDBFilePath.ToString().Substring(0,4) -ne "\\\\" -and $this.fstrDBFilePath.ToString().Substring(0,2) -eq "\\") {
                $strConnectionString = "Data Source = \\" + $this.fstrDBFilePath;
            } else {
                $strConnectionString = "Data Source = " + $this.fstrDBFilePath;
            }

            $sqliteConnection.ConnectionString = $strConnectionString ;
            $sqlcmd = New-Object System.Data.SQLite.SQLiteCommand;
            $sqlcmd.Connection = $sqliteConnection;
            $sqliteConnection.Open();

            # SELECT実行
            $sqlcmd.CommandText = $strSQL;
            foreach ($para in $lstParas) {
                $sqlitePara = New-Object "System.Data.SQLite.SQLiteParameter" -ArgumentList $para.ParameterName, $para.Value;
                $sqlcmd.Parameters.Add($sqlitePara);
            }
            $rs =  $sqlcmd.ExecuteReader();
           
            # Datatableにデータを取り込む
            $dataTable = New-Object System.Data.DataTable;
            $dataTable.Load($rs);
            $dataTable | Format-Table ;

        } catch {
            Write-Host "【エラー発生】GetDataPara";
            Write-Host ("エラーメッセージ:" + $_.Exception.Message);
            Write-Host ("クエリ:" + $strSQL);
        } finally {
            # SQLiteの切断
            $sqlcmd.Dispose()
            $sqliteConnection.Close()
        }
        return $dataTable;
    }

 

PowerShellで単画面、複数画面を作成したいときのそれぞれの記述方法について

ケース1.1画面のみを作成するケース

1画面の作成だけで良い場合については、下記のようなソースコードを作成する。 個人的なイメージとしては全てをVB.NETでいうモジュールに記載しているイメージになる。 やろうと思えば画面ごとにPowerShellのファイルを分けて外部ファイルの読み込みを指定することで作成することもできるが結局は実行段階で1ファイルにまとめられてしまうことになるので、変数名を全て一意にしないとうまくいかない。 


 Menu.ps1で「$frm」という変数を定義し処理を記載し、frm01_0100.ps1で「$frm」を定義すると後から読み込み指定したファイルの内容が優先される。結局一つのファイルにすべて記載しているの変わらない。


 バッチ処理やCSVファイルの加工など複数画面にしなくてよいケースであれば下記のソースで書くほうがわかりやすくてよいと思われる。

複数の画面ファイルを作成するためには、普通のWindowsフォームを作成する時と同じように、Formを継承した独自クラスを作成したい場合は2.複数画面を作成するケースを参考にクラスを作成するとよい。



#アセンブリの読み込み
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

# 各パスを取得
# デバッグ用(Main.ps1で定義済)
# $CurrentDir  = Split-Path $MyInvocation.MyCommand.path
# $ScriptName = $MyInvocation.MyCommand.path
# $Basename    = $MyInvocation.MyCommand.Name

$CurrentDir  = Split-Path $MyInvocation.MyCommand.path
$ScriptName = $MyInvocation.MyCommand.path
$Basename    = $MyInvocation.MyCommand.Name

# 外部ファイルの読み込み。Powershellで外部ファイルをⅢ書するときには呼び出す前に指定しておく必要がある

#   共通関数
$CommonFile = $CurrentDir + "\Common\Common.ps1" 
.$CommonFile

#   データベースアクセス
$DbaFile = $CurrentDir + "\Common\DataBaseAccess.ps1" 
.$DbaFile

#   データベースアクセスサンプルソース
$DbaFormFile = $CurrentDir + "\020_frmDataBaseAccess.ps1" 
.$DbaFormFile

####  ↓ 画面固有の定義 ↓

#フォームの設定
$frmMenu = New-Object System.Windows.Forms.Form
$frmMenu.Size = New-Object System.Drawing.Size(800,600)
$frmMenu.Font=New-Object System.Drawing.Font("MS ゴシック", 10.5)
$frmMenu.Text = "メニュー画面"

#ボタンの設定
$btnShowSubForm = New-Object System.Windows.Forms.Button
$btnShowSubForm.Location = New-Object System.Drawing.Point(60,50)
$btnShowSubForm.Size = New-Object System.Drawing.Size(175,30)
$btnShowSubForm.Text = "データベーステスト"
$btnShowSubForm.Add_Click({
    OpenfrmDataBaseAccess($txtDbAccessPath.Text)
})
$frmMenu.Controls.Add($btnShowSubForm)


#ボタンの設定
$txtDbAccessPath = New-Object System.Windows.Forms.TextBox
$txtDbAccessPath.Location= New-Object System.Drawing.Point(10,50)
$txtDbAccessPath.Size = New-Object System.Drawing.Size(175,30)
$txtDbAccessPath.Text = ""
$frmMenu.Controls.Add($txtDbAccessPath)

#フォームの表示
$frmMenu.Add_Shown({$frmMenu.Activate()})
function OpenMainForm {
    [void] $frmMenu.ShowDialog()
}

# デバッグ用
OpenMainForm

2.複数画面を作成するケース

一覧画面と詳細画面を作成するケースなど複数の画面を連携させる場合にはどうしてもファイルを分けて作成するほうがよいケースが多い。その場合には通常のVisualStudioでプロジェクトを作成するように「Form」クラスを継承した 独自フォームを作成するのがよい。 書き方に慣れる必要はあるが、慣れてしまえばVisualStudioで開発するようにプログラムを作成することができる。

独自クラスも作成できるので、始めに色々と作っておくことで使いまわすことも容易にできる

例)
<プロジェクトの構成>

  • Main.ps1・・・後述するが、System.Windows.Formsのアセンブリロードエラー対策として必要
    • 0000_frmMenu.ps1・・・メニュー想定のフォーム
    • 0100_frmDataBaseAccess.ps1・・・データベースアクセステスト用フォーム
    • DataBaseAccess.ps1・・・データベース(sqlite3)へのアクセス用クラス。

Main.ps1

    Add-Type -AssemblyName System.Drawing;
    Add-Type -AssemblyName System.Windows.Forms;

    $CurrentDir  = Split-Path $MyInvocation.MyCommand.path;
    $ScriptName = $MyInvocation.MyCommand.path;
    $Basename    = $MyInvocation.MyCommand.Name;

    #   データベースアクセス
    $MenuFile = $CurrentDir + "\0000_frmMenu.ps1"; 
    .$MenuFile;

    [Application]::EnableVisualStyles();
    $form = [frmMenu]::new();
    $form.StartPosition="CenterScreen";   # 列挙体は文字列で指定する必要がある
    $form.ShowDialog();

0000_frmMenu.ps1

事前にsqlite3.dllのダウンロードとテスト用のデータベースを作成しておく

    using namespace System.Windows.Forms;
    using namespace System.Drawing;

    #アセンブリの読み込み
    Add-Type -AssemblyName System.Drawing;
    Add-Type -AssemblyName System.Windows.Forms;

    #[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    #[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")


    $CurrentDir  = Split-Path $MyInvocation.MyCommand.path;
    $ScriptName = $MyInvocation.MyCommand.path;
    $Basename    = $MyInvocation.MyCommand.Name;

    # 外部ファイルの読み込み。Powershellで外部ファイルをⅢ書するときには呼び出す前に指定しておく必要がある

    #   データベースアクセス
    $DbaFile = $CurrentDir + "\0100_frmDataBaseAccess.ps1"; 
    .$DbaFile;
    
    Class frmMenu : System.Windows.Forms.Form {

        $txtFilePath = [TextBox]@{
            Location = [Point]::new(10,10);
            AutoSize=$False;
            Size=[Size]::new(700,25);
            Text = "C:\Users\UserName\Desktop\DataBase\NewProject.sqlite3";
        }
    
        $btnDBA = [Button]@{
            Location = [Point]::new(720,10);
            Size=[Size]::new(250,25);
            Text = “データベースアクセス”;
        }
    
        frmMenu(){
            $this.Size = [Size]::new(900, 700);
            $this.btnDBA.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “btnDBA_Click”));
            $this.Controls.add($this.btnDBA);
            $this.Controls.add($this.txtFilePath);
            $this.txtFilePath.Text = "C:\Users\UserName\Desktop\DataBase\NewProject.sqlite3";
            $this.Font=New-Object System.Drawing.Font("MS ゴシック", 10.5)
        }
    
        btnDBA_Click([object] $sender, [EventArgs] $e){
            # インスタンス化(外部ファイルの参照設定を「.$DbaFile;」で処理済み)
            
            [string]$strdbPath=$this.txtFilePath.Text;
            $frmDba = New-Object frmDataBaseAccess($strdbPath)

            $frmDba.Owner=$this;
            $frmDba.StartPosition = "CenterParent"
            # メソッドの実行
            $frmDba.ShowDialog();

        }
    }

0100_frmDataBaseAccess.ps1

    #アセンブリの読み込み
    using namespace System.Windows.Forms;
    using namespace System.Drawing;
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    #   データベースアクセス
    $DbaFile = $CurrentDir + "\DataBaseAccess.ps1"; 
    .$DbaFile;

    Class frmDataBaseAccess :System.Windows.Forms.Form{
        [string]$fstrDBFilePath ;

        $btnRefresh = [Button]@{
            Location = [Point]::new(10,10);
            Size=[Size]::new(150,25);
            Text = “データ取得”;
        }

        $btnInsData = [Button]@{
            Location = [Point]::new(180,10);
            Size=[Size]::new(150,25);
            Text = “データ追加”;
        }

        $dgv = [DataGridView]@{
            Location = [Point]::new(10,70);
            Size=[Size]::new(800,600);
            AutoGenerateColumns=$True;
            ReadOnly=$True;

        }
        
        frmDataBaseAccess(){
            $this.Size = [Size]::new(900, 700);

            $this.bs.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “Click”));
            $this.Controls.add($this.bs);
            $this.Controls.add($this.dgv);
        }
        
        frmDataBaseAccess([string]$strDBFilePath){
            $this.fstrDBFilePath = $strDBFilePath;

            $this.Size = [Size]::new(900, 700);
            $this.btnRefresh.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “btnRefresh_Click”));
            $this.btnInsData.add_Click([Delegate]::CreateDelegate([EventHandler], $this, “btnInsData_Click”));
            $this.Controls.add($this.btnRefresh);
            $this.Controls.add($this.btnInsData);
            $this.Controls.add($this.dgv);
        }

        btnRefresh_Click([object] $sender, [EventArgs] $e){
        
            $dba = New-Object clsDataBaseAccess($this.fstrDBFilePath);

            $dt = $dba.GetData("SELECT * FROM t1");
            [MessageBox]::Show(“frmDataBaseAccess Clicked!”);
            
            $this.dgv.DataSource=$dt;

        }

        btnInsData_Click([object] $sender, [EventArgs] $e){
        
            $dba = New-Object clsDataBaseAccess($this.fstrDBFilePath);

            $dt = $dba.GetData("INSERT INTO t1(name,ostype) VALUES('Windows11','Win11')");

            [MessageBox]::Show(“frmDataBaseAccess btnInsData Clicked!”);
            

        }
    }

DataBaseAccess.ps1

データベースを操作するためのクラス

    # System.Data.sqlite.dllのロード
    $CurrentDir  = Split-Path $MyInvocation.MyCommand.path

    $sqliteDllPath = $CurrentDir + "\System.Data.sqlite.dll" 

    $asm = [System.Reflection.Assembly]::LoadFile($sqliteDllPath)
    #$asm = [System.Reflection.Assembly]::LoadFile("C:\Users\UseName\Desktop\PowerShell\Exp2Solution\System.Data.SQLite.dll")

    Class clsDataBaseAccess {

        $strDBFilePath="";

        # コンストラクタ
        clsDataBaseAccess(){

        }

        clsDataBaseAccess([string]$strDBPath){
            $this.strDBFilePath=$strDBPath;
        }

        ############################################################
        # SQLを実行しデータを取得する。戻り値はDataTable
        ############################################################
        [System.Data.DataTable]GetData([string]$strSQL){
            # SQLiteへの接続およびSQLステートメント発行用のSystem.Data.SQLite.SQLiteCommandの生成
            $sqliteConnection = New-Object System.Data.SQLite.SQLiteConnection;
            $sqliteConnection.ConnectionString = "Data Source = " + $this.strDBFilePath;
            $sqlcmd = New-Object System.Data.SQLite.SQLiteCommand;
            $sqlcmd.Connection = $sqliteConnection;
            $sqliteConnection.Open()

            # SELECT実行および表示
            $sqlcmd.CommandText = $strSQL
            $rs =  $sqlcmd.ExecuteReader()
            
            # Datatableにデータを取り込む
            $dataTable = New-Object System.Data.Datatable
            $dataTable.Load($rs)
            $dataTable | Format-Table

            # SQLiteの切断
            $sqlcmd.Dispose()
            $sqliteConnection.Close()

            return $dataTable
        }

        ############################################################
        # SQLを実行しデータを登録・更新・削除する
        # SQL インジェクション対策を実施していないので注意すること
        ############################################################
        ExecuteSQL_InsUpdDel([string]$strSQL){
            # SQLiteへの接続およびSQLステートメント発行用のSystem.Data.SQLite.SQLiteCommandの生成
            $sqliteConnection = New-Object System.Data.SQLite.SQLiteConnection;
            $sqliteConnection.ConnectionString = "Data Source = " + $this.strDBFilePath;
            $sqlcmd = New-Object System.Data.SQLite.SQLiteCommand;
            $sqlcmd.Connection = $sqliteConnection;
            $sqliteConnection.Open()

            # INSERT,UPDATE,DELETE 実行および表示(SQLインジェクション対策なし)
            $sqlcmd.CommandText = $strSQL
            $ret =  $sqlcmd.ExecuteNonQuery()

            # SQLiteの切断
            $sqlcmd.Dispose()
            $sqliteConnection.Close()

        }
    
        
    }

アセンブリのエラーについて

PowerShell ISEでデバッグしているときには個別のファイルでエラーが出ないが、右クリックでPowerShellで実行などを指定すると下記のようなメッセージが出るのはアセンブリのロードができていないことが原因。 そのため対策としては事前にMain.ps1でアセンブリのロードを記述し、Main.ps1でアセンブリのロードが完了してから、0000_frmMenu.ps1、0100_frmDataBaseAccess.ps1を読み込むようにしておく


--------------------------------------------------
発生場所 C:\Users\UserName\Desktop\PowerShell\Exp2Solution\0000_frmMenu.ps1:26 文字:17
+ Class frmMenu : System.Windows.Forms.Form {
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~
型 [System.Windows.Forms.Form] が見つかりません。
発生場所 C:\Users\UserName\Desktop\PowerShell\Exp2Solution\0000_frmMenu.ps1:27 文字:12
+     $bs = [Button]@{
+            ~~~~~~
型 [Button] が見つかりません。
発生場所 C:\Users\UserName\Desktop\PowerShell\Exp2Solution\0000_frmMenu.ps1:40 文字:10
+         [MessageBox]::Show(“Clicked!”);
+          ~~~~~~~~~~
型 [MessageBox] が見つかりません。
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : TypeNotFound
-------------------------
ソースコードに書いてあっても失敗するのは、読み込みが遅いからと思われる。
事前にmain.ps1などの別ファイルで「 Add-Type -AssemblyName System.Drawing;
Add-Type -AssemblyName System.Windows.Forms;」を記述し
Main.ps1 ← 主要なアセンブリをロードしたうえでfrmMenu.ps1を外部ファイルから参照して実行をする
VSCodeでデバッグするときにはターミナルで一度上記のアセンブリのAdd-Typeを実行しておく


VBAからsqlite3.exeを呼び出してdllやODBCを使用せずにsqlite3を利用する

VBAからsqlite3.exeを呼び出してdllやODBCを使用せずにsqlite3を利用する

バッチファイルを実行し、結果ファイルをHTMLのTableタグで出力し、出力した結果を開いてRecodesetへ取り込む。

-------------------------------------------------

Private Const SQLITE_EXE As String = "sqlite3.exe"
Private Const RESULT_FILENAME As String = "Result.xml"
Private Const EXEC_SQL As String = "ExecSQL.sql"
Private Const EXEC_BATCH As String = "ExecBatch.bat"
Private Const DB_PATH As String = "C:\Users\UserName\Desktop\PgUserName\DataBase\開発PJ.prj"
Private Const FOLDER_PATH As String = "C:\Users\UserName\Desktop\PgUserName\"
Private Const CHARSET_UTF8 As String = "UTF-8"
Private Const CHARSET_SJIS As String = "Shift-JIS"
'参照設定:Microsoft ActiveX Data Object Library 2.8
Private Function GetData(ByVal strSql As String, ByRef rdSet As ADODB.Recordset)
 
    'SQLを実行する
    Call ExecuteSqlite3(strSql)
    '出力結果のファイルを取得し、RecordSetに取り込む
    Dim strResultFilePath As String
    strResultFilePath = ThisWorkbook.Path & "\temp\" & RESULT_FILENAME
    Dim buf As String, Target As String, lngRow As Long, lngItem As Long
    Dim blnIsHeader As Boolean
    Dim strRows As Variant, strItems As Variant, j As Long
    Dim strSplitName As String
    
    blnIsHeader = True
    
    Dim rdResult As ADODB.Recordset
    Set rdResult = New ADODB.Recordset
    
    With CreateObject("ADODB.Stream")
        .Charset = "UTF-8"
        .Open
        .LoadFromFile strResultFilePath
        buf = .ReadText
        .Close
        '<TR>~</TR>で1行
        
        
        buf = Replace(buf, "<TR>", "")
        strRows = Split(buf, "</TR>" & vbLf)
        For lngRow = 0 To UBound(strRows)
            If blnIsHeader = True Then
                strSplitName = "</TH>"
            Else
                strSplitName = "</TD>"
                '行を追加
                rdResult.AddNew
            End If
            
            strItems = Split(strRows(lngRow), strSplitName & vbLf)
            
            Dim strtemp As String
            strtemp = ""
            For lngItem = 0 To UBound(strItems) - 1  '空白行が最後に含まれる
                strtemp = Mid(strItems(lngItem), 5)
                
                If blnIsHeader = True Then
                    rdResult.Fields.Append strtemp, adLongVarWChar, 255, adFldIsNullable
                Else
                    rdResult.Fields(lngItem) = strtemp
                End If
                
            Next
            
            If blnIsHeader = True Then
                blnIsHeader = False
                rdResult.Open
            Else
                rdResult.Update
            End If
        Next
        
    End With
    Set rdSet = rdResult
End Function

Private Sub ExecuteSqlite3(ByVal strSql As String)
    Dim strCommand As String
    Dim strSqlCommand As String
    
    Dim strTempFolderPath As String
    strTempFolderPath = ThisWorkbook.Path & "\temp"
    If Dir(strTempFolderPath, vbDirectory) = "" Then
        MkDir strTempFolderPath
    End If
    
    Dim strSqlite3ExePath As String
    strSqlite3ExePath = ThisWorkbook.Path & "\" & SQLITE_EXE
    
    Dim strResultFilePath As String
    strResultFilePath = "./" & RESULT_FILENAME
    
    Dim strBatchFilePath As String
    strBatchFilePath = strTempFolderPath & "\" & EXEC_BATCH
    
    Dim strExecSqlPath As String
    strExecSqlPath = strTempFolderPath & "\" & EXEC_SQL
    'sqlite3を実行するときのオプションコマンド
    strSqlCommand = ".headers on " & vbCrLf
    strSqlCommand = strSqlCommand & ".mode html " & vbCrLf
    strSqlCommand = strSqlCommand & ".output '" & Replace(strResultFilePath, "\", "/") & "'" & vbCrLf
    
    strSqlCommand = strSqlCommand & strSql & vbCrLf
    
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strSqlCommand, strExecSqlPath, CHARSET_UTF8, True)
    'バッチ用コマンド
    strCommand = ""
    strCommand = strCommand & strSqlite3ExePath & " " & DB_PATH & " < " & strExecSqlPath
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strCommand, strBatchFilePath, CHARSET_SJIS)
    Dim wsh As Object
    Dim result As Integer
    
    Set wsh = CreateObject("WScript.Shell")
    
    'バッチファイルを同期実行
    result = wsh.Run(Command:=strBatchFilePath, WaitOnReturn:=True)
    If (result = 0) Then
        Debug.Print ("バッチファイルは正常終了しました。")
    Else
        Debug.Print ("バッチファイルは異常終了しました。")
    End If
    
    '後片付け
    Set wsh = Nothing
    
End Sub
Public Sub TextOutput(ByVal strText As String, ByVal strFilePath As String, ByVal strCharSet As String, Optional ByVal blnNoBOM As Boolean = False)
    Dim i As Integer
     
    'ADODB.Streamオブジェクトを生成
    Dim ado As Object
    Set ado = CreateObject("ADODB.Stream")
     
    'ADODB.Streamで扱う文字コードを設定する
    ado.Charset = strCharSet
     
    'ADODB.Streamを開く
    ado.Open
     
    '開いたADODB.Streamに内容を保管する
    'adWriteLineは改行する時に入れる
     ado.WriteText strText
        ' ---------- ここから ----------
    If blnNoBOM = True And strCharSet = CHARSET_UTF8 Then
    
        With ado
            ' タイプをバイナリにして、先頭の3バイトをスキップ
            .Position = 0
            .Type = 1 ' ADODB.StreamTypeEnum.adTypeBinary ' タイプ変更するにはPosition = 0である必要がある
            .Position = 3
            ' 一時格納用
            Dim p_byteData() As Byte
            p_byteData = .Read
            .Close ' 一旦閉じて
            .Open ' 再度開いて
            .Write p_byteData ' ストリームに書き込む
        End With
    
    End If
     
    'ADODB.Streamに保管されている内容をファイルに保存する
    ado.SaveToFile strFilePath, 2
     
    'ADODB.Streamを閉じる
    ado.Close
   
End Sub

VBAからSqlite3.exeを利用する方法を検討(ODBC・dllを利用せずにVBAからSqlite3を利用する)

VBAでSqlite3を利用する方法としては、何個かネットに上がっているがODBCのインストールができず、dllの利用は汎用性が高いが難易度が高いと思われたので、sqlite3.exeを利用するケースを検討してみた。

下記の流れで何とかなるのではないかと思い、いろいろと検証しているが、現時点で処理ができた分をメモのために記載する。
 ・実行するSqlを作成(UTF-8 BOM無しである必要がある)
   例)SQL文の内容(最初の3行はsqlite3.exeのオプション)
     .headers on 
     .mode html 
     .output ./result.xml 
      SELECT * FROM T_Card;

 ・Sqlを呼び出すためのバッチを作成(Shift-JISである必要がある)
     C:\Users\UserName\Desktop\PgUsrName\sqlite3.exe C:\Users\UserName\Desktop\PgUsrName\DataBase\開発PJ.prj < C:\Users\UserName\Desktop\PgUsrName\ExecSQL.sql

 ・出力形式はhtml形式を指定し、出力された結果をVBAでデシリアライズする
 ・処理完了後には一時ファイルを削除する(予定)
 ・出力されたファイルをRecodesetに読み込む(予定)

--------------------------------- ソース ----------------------
 Private Const SQLITE_EXE As String = "C:\Users\UserName\Desktop\PgUsrName\sqlite3.exe"
Private Const RESULT_FILEPATH As String = "Result.xml"
Private Const EXEC_SQL As String = "ExecSQL.sql"
Private Const EXEC_BATCH As String = "ExecBatch.bat"
Private Const DB_PATH As String = "C:\Users\UserName\Desktop\PgUsrName\DataBase\開発PJ.sqlite3"
Private Const FOLDER_PATH As String = "C:\Users\UserName\Desktop\PgUsrName\"
Private Const CHARSET_UTF8 As String = "UTF-8"
Private Const CHARSET_SJIS As String = "Shift-JIS"

Private Sub Sqlite()
    Dim strCommand As String
    Dim strSqlCommand As String
    Dim strSql As String
    
    '実行するsql
    strSql = strSql & " SELECT * FROM T_Card;"
    'sqlite3を実行するときのオプションコマンド
    strSqlCommand = ".headers on " & vbCrLf
    strSqlCommand = strSqlCommand & ".mode html " & vbCrLf
    strSqlCommand = strSqlCommand & ".output ./result.xml " & vbCrLf
    
    strSqlCommand = strSqlCommand & strSql & vbCrLf
    
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strSqlCommand, FOLDER_PATH & EXEC_SQL, CHARSET_UTF8, True)
    'バッチ用コマンド
    strCommand = SQLITE_EXE & " " & DB_PATH & " < " & FOLDER_PATH & EXEC_SQL
    'sqlite3に渡すコマンドを出力する
    Call TextOutput(strCommand, FOLDER_PATH & EXEC_BATCH, CHARSET_SJIS)
    Dim wsh As Object
    Dim result As Integer
    Set wsh = CreateObject("WScript.Shell")
    
    'バッチファイルを同期実行
    result = wsh.Run(Command:=FOLDER_PATH & EXEC_BATCH, WaitOnReturn:=True)
    
    If (result = 0) Then
        MsgBox ("バッチファイルは正常終了しました。")
    Else
        MsgBox ("バッチファイルは異常終了しました。")
    End If
    
    '後片付け
    Set wsh = Nothing
    
End Sub
Public Sub TextOutput(ByVal strText As String, ByVal strFilePath As String, ByVal strCharSet As String, Optional ByVal blnNoBOM As Boolean = False)
    Dim i As Integer
     
    'ADODB.Streamオブジェクトを生成
    Dim ado As Object
    Set ado = CreateObject("ADODB.Stream")
     
    'ADODB.Streamで扱う文字コードを設定する
    ado.Charset = strCharSet
     
    'ADODB.Streamを開く
    ado.Open
     
    '開いたADODB.Streamに内容を保管する
    'adWriteLineは改行する時に入れる
     ado.WriteText strText
'    For i = 1 To 5
'        ado.WriteText Cells(i, 1).Value, adWriteLine
'    Next
     
        ' ---------- ここから ----------
    If blnNoBOM = True And strCharSet = CHARSET_UTF8 Then
    
        With ado
            ' タイプをバイナリにして、先頭の3バイトをスキップ
            .Position = 0
            .Type = 1 ' ADODB.StreamTypeEnum.adTypeBinary ' タイプ変更するにはPosition = 0である必要がある
            .Position = 3
            ' 一時格納用
            Dim p_byteData() As Byte
            p_byteData = .Read
            .Close ' 一旦閉じて
            .Open ' 再度開いて
            .Write p_byteData ' ストリームに書き込む
        End With
    
    End If
     
    'ADODB.Streamに保管されている内容をファイルに保存する
    ado.SaveToFile strFilePath, 2
     
    'ADODB.Streamを閉じる
    ado.Close
     
     
End Sub

SQLクライアントツールについて(DMonkeyスクリプト)

SQL クライアントツールについて

 色々なSQLクライアントツールを使ったが、SQL ServerならSQL ManagementStudio、OracleならSQL Developerなどそれぞれの製品特有のクライアントツールが運用上使いやすいのは使うのは当然として、そのほかに総合的に見て使いやすいと思ったツールは「A5:SQL Mk-2」が一番使いやすいと思う。
 DBeaver もフリーのクライアントツールとしては使いやすいと思うが、スクリプトを標準装備しているという点で「A5:SQL Mk-2」が一番使いやすかった。
 「A5:SQL Mk-2」はポータブルモードにすればインストールせずに利用できるので管理者権限がない環境でも利用できるし、Sqlite3も作ることができるのでデータ分析をするときにすごく重宝する。
 複数のCSVファイルを取り込んで、SQLでは実装が難しいものもDMonkeyスクリプトが組めるので最悪はスクリプトで実装することができる。
 DMonkeyはWScriptも利用することができるのでファイルのコピーや移動もすることができる。クラスも作ることができるので汎用性がすごい。


サンプルスクリプト


class Sample {
var fstrTokCD;
var fstrTokName;
var fArrlogs = new Strings();

function GetData(){
var strSQL;
fArrlogs.add(" GetDataを実行")

strSQL = "";
strSQL = strSQL + "SELECT * FROM T_得意先マスタ ";
strSQL = strSQL + "WHERE 得意先CD = '" + fstrTokCD + "'" ;


var conn = application.dbTree.getSelectedDatabaseConnection();
var rs = conn.executeQuery(strSQL); // 結果セット
rs.first();
while (!rs.eof()) {
fstrTokName = rs.getFieldValue("得意先名")
rs.next();
}
rs.close();
fArrlogs.add(" GetDataが完了")
}
}

var samp = new Sample();
samp.GetData();
samp.fArrlogs.saveToFile("C:\\Users\\UserName\\Desktop\\folder\\dg.log")

Sqlite3でのBLOB型でファイルを登録・出力

 Sqlite3のBLOB型でファイルの登録・出力を実装する。

VBAでの処理を行いたいと考えているがBinaryにしてから登録したりいろいろとしていたが正直うまくいかなかった。

ただ、Sqlite3.exeの関数でファイルのバイナリ化からの登録、出力ができるとのことだったので、試してみた。


sqlite3.exeをコマンドプロンプトで起動し

※TestTableは「intValue」(int型)、「bobValue」(blob型)のテーブル

sqlite3.exe "sample.sqlite3";

insert into TestTable values(111,readfile('in.jpg'));

select writefile('out.jpg',bobValue) from TestTable;

とすることで画像の入出力ができた。


後はVBAでShellの実行でsqlite3.exeを起動してあげれば対応することができる

備忘録 Having句で集計関数を利用する

 HAVING句をうまく利用する
 
 意外と思いつかなかったけど、HAVING句でCASE句を利用することができる
 ※例は適当です。グループの中で条件を満たしていないものがあるかどうかのチェックができます。
 
  ・会社ごとに振込先口座を持っている
  ・グループ会社の中で異なる振込先があるグループがあるか
 
   (例)
    
   
    会社CD |   会社名   | グループ会社CD | 請求会社CD | 親会社CD | 銀行CD | 支店CD | 口座番号 | 口座名義人
    00001  |テスト1    | 001            | 00001      |  00001   |  0001  |  002   | 1234567  | カ)テスト1
    00002  |テスト1-1   | 001            | 00002      |  00001   |  0001  |  002   | 0004567  | カ)テスト2
    00003  |テスト1-2   | 001            | 00001      |  00001   |  0001  |  002   | 1234567  | カ)テスト1
    10001  |テスト2    | 002            | 10001      |  10001   |  0001  |  002   | 1234567  | カ)テスト2
    10002  |テスト2-1   | 002            | 10001      |  10001   |  0001  |  002   | 1234567  | カ)テスト2
    
    
    -- 「請求先」が「親会社」になっていないデータが混じっているグループ会社を抽出
  -- グループCDで集約を行い、グループCDでまとめた件数=レコードの中で「請求会社CD」
  SELECT グループ会社CD
  FROM 契約会社テーブル
  GROUP BY グループ会社CD
  HAVING COUNT(*) <> SUM(CASE 
                        WHEN 請求会社CD = 親会社CD THEN 1 
                        ELSE 0 
                    END) AS 抽出対象
  上記のデータ例で行くと「グループ会社CD:001」が抽出される
  
 集計関数の中にもCASE式が書けるって初めて知ったときに結構衝撃だったけど、月ごとの集計結果を列で持ちたいときとかにも使えるので素晴らしい。
 
  SELECT 
         商品品種名
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '01' THEN 売上金額 ELSE 0 END) AS 1月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '02' THEN 売上金額 ELSE 0 END) AS 2月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '03' THEN 売上金額 ELSE 0 END) AS 3月売上
        ,SUM(CASE WHEN SUBSTR(売上日,5,2) = '04' THEN 売上金額 ELSE 0 END) AS 4月売上
             ・
             ・
             ・
  FROM 売上テーブル
  WHERE 売上日 BETWEEN '20210101' AND '20211231'
  GROUP BY 商品品種名
             
  
  
 

TesseractOCRアプリケーションの作成について

前回の記事(https://inakaprogrammer.blogspot.com/2020/12/visualstudioocr.html)で出したOCRアプリケーションについて、作成方法のメモを残す。

 <ソリューションの構成について>

 画像の変換処理にかかる手間を考えて、下記の3つのプロジェクトに分ける
  1.画像の読取枠の定義を行うWPFアプリケーション(プロジェクト名:TesseractOCR)
     1つのパソコンで一つの起動
  2.1から呼び出されて読取枠ごとに画像を分割し、3.を呼び出すコンソールアプリケーション(プロジェクト名:OCRManager)
     1つのパソコンで一つの起動
  3.Tesseractを使ってOCRを実行するコンソールアプリケーション(プロジェクト名:OCR)
     1つのパソコンで複数の起動(上限設定可能)
 
 1つの画像に対して複数の読取枠を用意し、処理を実行するため3.を複数起動できるようにして処理速度を向上させる。
 ただし、パソコンにかかる負荷の問題もあるので同時実行の最大上限の制限をOCRManager.exeによって行う。
 ※上限を設けないと例えば100ファイル(1ファイル当たり読取枠20個)の場合、OCR.exeが同時に2000個起動することになってしまう。

<フォルダ構成>

C:.
└─TesseractOCR
    │  ER_TesseractOCR.a5er
    │  OcrDb.sqlite3
    │  TesseractOCR.iedx
    │  TesseractOCR.sln
    │  
    ├─Common                ・・・ソリューションで共通で利用する関数や定数を格納するフォルダ
    │      DataBaseAccess.cs         ・・・データベースへアクセスするためのクラスを定義
    │      SolutionCommon.cs         ・・・共通関数や定数を定義
    │      
    ├─OCR                  ・・・OCR.exeのプロジェクトのフォルダ
    │  │  Config.xml            ・・・DBへのパスや他のexeへのパスを格納
    │  │  OCR.csproj
    │  │  OCR.csproj.user
    │  │  Program.cs            ・・・画像をOCRする処理を記述
    │  ├─bin
    │  ├─obj
    │  ├─Properties
    │  │      launchSettings.json
    │  │      
    │  └─tessdata             ・・・Tesseractが利用する学習済みのデータ
    │      │  eng.traineddata
    │      │  jpn.traineddata
    │      │  jpn_vert.traineddata
    │      │  pdf.ttf
    │      │  _jpn.traineddata
    │      └─traindata
    │              Best____jpn.traineddata
    │              Best____jpn_vert.traineddata
    │              Fast____jpn.traineddata
    │              OCR3.04.00____jpn.traineddata
    │              readme.txt
    │              Ubuntu___Japanese.traineddata
    │              Ubuntu___Japanese_vert.traineddata
    │              Ubuntu___jpn.traineddata
    │              Ubuntu___jpn_vert.traineddata
    │              
    ├─OCRManager              ・・・OCRManager.exeのプロジェクトのフォルダ
    │  │  Config.xml            ・・・DBへのパスや他のexeへのパスを格納
    │  │  OCRManager.csproj
    │  │  Program.cs            ・・・画像を分割し、OCR.exeを呼び出す
    │  │  
    │  ├─bin
    │  └─obj
    │  
    └─TesseractOCR             ・・・TesseractOCR.exeのプロジェクトのフォルダ
        │  App.xaml
        │  AssemblyInfo.cs
        │  Config.xml
        │  MainWindow.xaml
        │  pg01_0100_Main.xaml
        │  pg02_0100_Rpt.xaml
        │  pg02_0200_Rpt.xaml
        │  pg02_0210_Rpt.xaml
        │  pg03_0100_Job.xaml
        │  pg03_0200_Job.xaml
        │  pg03_0210_Job.xaml
        │  pg90_0100_Opt.xaml
        │  ProjectCommon.cs
        │  TesseractOCR.csproj
        │  TesseractOCR.csproj.user
        │  
        ├─backgroundimage
        ├─bin
        ├─controls
        │      BaseWindow.xaml
        │      CsButton.cs
        │      CsCheckBox.cs
        │      CsComboBox.cs
        │      CsDataGrid.cs
        │      CsPage.cs
        │      CsTextBox.cs
        │      CsWindow.cs
        │      
        ├─modelbase
        │      ViewModelBase.cs
        │      
        ├─obj
        │                  
        └─resource
            │  BaseResorce.xaml
            │  ControlResource.xaml
            │  ProjectResource.xaml
            │  
            └─converter
                    StringToNumberConverter.cs

<主なテーブル>

 配番テーブルや設定テーブルについては、画像を参照
 ※テーブルの作成は「A5Mk2」を利用


  【TesseractOCR.exe】でデータを登録する
  ・帳票定義基本テーブル
    帳票名と備考を登録する
  ・帳票定義詳細テーブル
    読取枠の定義(XY座標、高さ、読取タイプ等)を登録する
  ・OCRジョブ基本テーブル
    OCR処理の実行指示を登録する
    ジョブの状況やダウンロード済みかどうかを判断するためのダウンロード日時を登録する
  ・OCRジョブ詳細テーブル
    OCR処理の対象となるファイルを登録する
  【OCRManager.exe】でデータを登録する
  ・OCR実行指示テーブル
    OCRジョブ詳細テーブルで指定されているファイルを帳票定義詳細テーブルで定義されている読取枠ごとに分割して、tempフォルダに配置した結果を登録する。
  【OCR.exe】でデータを更新する
  ・OCR実行指示テーブル
    OCRの結果を登録する

<プログラムの作成手順>

 人によって違いはあるとは思うが、個人的にはモチベーションの維持のために今回は下記の手順でプログラムを作成していく。
  <手順1.OCR.exeを作成> 


sqlite3を.NETで利用する


いろんなソフトのデータの保存方法についてシリアライズ化して保存したXmlファイルをまとめて
Zipファイルにすることで対応しようとしていたが、複数人で触ることを考えてsqliteのほうがトラブルが少なそうだったので
データの保存の関連を修正した。

データの取得については汎用性を持たせたかったので、DataSet、DataTableで取得するようにしておいた。
データを取得するときにインテリセンスが効かないので微妙な気もするが、
SQL側で集計したり、分析した結果を表示したりして列名を色々作ることが多そうなのでDataTableを利用したほうが融通が利く気がする。
融通を聞かせすぎると統一感がなくなるけど、どうせそこまで統一したシステムなんて組めないからいいんじゃないかな・・・。

インジェクション対策としてパラメータも利用できるようにしてみた。

sqlite以外でもコネクション周りを修正すれば利用できるDataBaseAccessクラスを作成した。
SQL Serverの場合であればSQLiteConnectionStringBuilderなどを軒並みSQLConnectionに変更すれば対応できる、気がする。
※SQL文の書き方が違うのでそのあたりは頑張っていただくしかないが・・・。
 どこかの機会に遅いSQLを早くするコツも書いてみたいと思う。いつになるかわからないけど。

そのときにサンプルプログラムを作成し、調査した内容をメモしておく。

sqliteをNuGetパッケージの管理からインストールし、参照の追加を行う




下記の using を追加する。

  using System.Data;
  using System.Data.SQLite;


↓↓↓↓↓ サンプルソース ↓↓↓↓↓

private void btnSample_Click(object sender, RoutedEventArgs e)
{
    string path = @"C:\test\SampleDataBase.db";
    var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = path };

    // ▽▽▽ データベース作成 ▽▽▽
    DataBaseAccess dataBaseAccess = new DataBaseAccess();
    dataBaseAccess.CreateDataBase_NewFile(path);
    // △△△ データベース作成 △△△


    // ▽▽▽ データ登録処理 ▽▽▽

    List<DataBaseAccess.ExecutionSQL> lstInsUpd = new List<DataBaseAccess.ExecutionSQL>();

    //   データ登録(ファイル実体)
    DataBaseAccess.ExecutionSQL InsSqlFile = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.INSERT);
    InsSqlFile.ExecuteSQL = " INSERT INTO T_File(intProjectNo,intCardNo,txtCommentNo,intFileNo,bloFile)  "
                              + "             VALUES(@intProjectNo , @intCardNo , @txtCommentNo , @intFileNo , @bloFile) ";
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intCardNo", "1"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@txtCommentNo", "0"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intFileNo", "1"));
    //      ファイルをByte型配列で読み込み
    var data = System.IO.File.ReadAllBytes(@"C:\test\sample.txt");
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@bloFile", data));
    lstInsUpd.Add(InsSqlFile);

    //   データ登録(ファイル情報)
    DataBaseAccess.ExecutionSQL InsSqlFileInfo = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.INSERT);
    InsSqlFileInfo.ExecuteSQL = " INSERT INTO T_FileInfo(intProjectNo,intCardNo,txtCommentNo,intFileNo,txtFileName)  "
                              + "             VALUES(@intProjectNo , @intCardNo , @txtCommentNo , @intFileNo , @txtFileName) ";
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intCardNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@txtCommentNo", "0"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intFileNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@txtFileName", "sample.txt"));
    lstInsUpd.Add(InsSqlFileInfo);

    //   データ登録実行
    dataBaseAccess.ExecuteSQL_InsUpdDel(path, lstInsUpd);

    // △△△ データ登録処理 △△△

    // ▽▽▽ データ取得処理 ▽▽▽

    List<DataBaseAccess.ExecutionSQL> lstSelect = new List<DataBaseAccess.ExecutionSQL>();

    //   データを取得するためのSQL(ファイル実体)
    DataBaseAccess.ExecutionSQL SelSqlFile = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.SELECT);
    SelSqlFile.TableName = "T_File";
    SelSqlFile.ExecuteSQL = " SELECT * FROM  T_File ";
    lstSelect.Add(SelSqlFile);

    //   データを取得するためのSQL(ファイル情報)
    DataBaseAccess.ExecutionSQL SelSqlFileInfo = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.SELECT);
    SelSqlFileInfo.TableName = "T_FileInfo";
    SelSqlFileInfo.ExecuteSQL = " SELECT * FROM  T_FileInfo WHERE intProjectNo = @intProjectNo ";
    SelSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    lstSelect.Add(SelSqlFileInfo);

    //   SQLを実行し、結果をDataSetで取得する
    DataSet ds = dataBaseAccess.GetDatas(path, lstSelect);

    //   取得したファイルを別名で出力する
    System.IO.File.WriteAllBytes(@"C:\test\output.txt", (byte[])ds.Tables["T_File"].Rows[0]["bloFile"]);

    // △△△ データ登録処理 △△△

}


/// <summary>
/// データベースアクセス関連
/// </summary>
public class DataBaseAccess
{

    #region Public Method

    /// <summary>
    /// 新規ファイル作成時のSQL
    /// </summary>
    /// <param name="strFilePath"></param>
    public void CreateDataBase_NewFile(string strFilePath)
    {
        ExecuteSQL_DDL(strFilePath, _GetCreateTableSQL_V0100());
    }

    /// <summary>
    /// SQL実行処理
    /// </summary>
    /// <param name="strFilePath"></param>
    /// <param name="ExecutionSQL"></param>
    /// <returns></returns>
    public DataSet GetDatas(string strFilePath, ExecutionSQL ExecutionSQL)
    {
        List<ExecutionSQL> lst = new List<ExecutionSQL>();
        lst.Add(ExecutionSQL);
        return GetDatas(strFilePath, lst);
    }

    /// <summary>
    /// SQL実行処理
    /// </summary>
    public DataSet GetDatas(string strFilePath, List<ExecutionSQL> ExecutionSQLs)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };
        DataSet dataSet = new DataSet();

        try
        {
            using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
            {
                cn.Open();

                using (var cmd = new SQLiteCommand(cn))
                {
                    foreach (var param in ExecutionSQLs)
                    {
                        // ↓↓↓ チェック ↓↓↓
                        if (param.Type != ExecutionSQL.SqlType.SELECT) { throw new Exception("GetDatasメソッドはSELECT構文のみが実行可能です。SqlTypeを確認してください。"); }
                        // ↑↑↑ チェック ↑↑↑

                        cmd.CommandText = param.ExecuteSQL;

                        // SQLパラメータを設定
                        cmd.Parameters.Clear();
                        foreach (var item in param.Parameters) { cmd.Parameters.Add(new SQLiteParameter(item.Name, item.Value)); }

                        // SQLを実行し、データを取得する
                        using (var reader = cmd.ExecuteReader())
                        {
                            DataTable dt = new DataTable(param.TableName);
                            DataRow dr;

                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                dt.Columns.Add(new DataColumn(reader.GetName(i), reader.GetFieldType(i)));
                            }

                            while (reader.Read() == true)
                            {
                                dr = dt.NewRow();
                                for (int i = 0; i < reader.FieldCount; i++)
                                {
                                    dr[i] = reader[i];
                                }
                                dt.Rows.Add(dr);
                            }

                            dataSet.Tables.Add(dt);

                            reader.Close();
                        }

                    }

                }
            }

        }
        catch (Exception)
        {

            throw;
        }

        return dataSet;

    }

    /// <summary>
    /// SQL実行時の設定内容
    /// </summary>
    public class ExecutionSQL
    {
        public ExecutionSQL(SqlType sqlType)
        {
            this.Parameters = new List<SQLPara>();
            this.Type = sqlType;
        }

        public enum SqlType
        {
            INSERT, UPDATE, DELETE, SELECT
        }

        /// <summary>
        /// SELECT 構文の時には必須
        /// INSERT,UPDATE,DELETE 構文の時には不要
        /// </summary>
        public string TableName;

        /// <summary>
        /// 実行するSQL文
        /// </summary>
        public string ExecuteSQL;

        /// <summary>
        /// SQLのパラメータ
        /// </summary>
        public List<SQLPara> Parameters;

        public SqlType Type;
    }

    /// <summary>
    /// SQLパラメータ
    /// </summary>
    public class SQLPara
    {
        public SQLPara(string name, object value)
        {
            this.Name = name;
            this.Value = value;
        }
        public string Name;
        public object Value;
    }

    /// <summary>
    /// 登録・更新・削除のSQLを実行するためのメソッド
    /// </summary>
    public void ExecuteSQL_InsUpdDel(string strFilePath, List<ExecutionSQL> ExecutionSQLs)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };

        try
        {
            using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
            {
                cn.Open();
                using (var cmd = new SQLiteCommand(cn))
                {
                    using (SQLiteTransaction trans = cn.BeginTransaction())
                    {

                        foreach (var param in ExecutionSQLs)
                        {
                            // ↓↓↓ チェック ↓↓↓
                            if (param.Type == ExecutionSQL.SqlType.SELECT) { throw new Exception("ExecuteSQL_InsUpdDelメソッドはSELECT構文以外のみが実行可能です。SqlTypeを確認してください。"); }
                            // ↑↑↑ チェック ↑↑↑

                            cmd.CommandText = param.ExecuteSQL;

                            // SQLパラメータを設定
                            cmd.Parameters.Clear();
                            foreach (var item in param.Parameters) { cmd.Parameters.Add(new SQLiteParameter(item.Name, item.Value)); }

                            cmd.ExecuteNonQuery();

                        }

                        trans.Commit();
                    }

                }

            }
        }
        catch (Exception)
        {

            throw;
        }
    }

    #endregion

    #region Private Method 1st.

    /// <summary>
    /// DDL構文(Create、Drop)系列のSQLを実行する
    /// </summary>
    /// <param name="strFilePath">ファイルパス</param>
    /// <param name="strSQL">実行SQL</param>
    private void ExecuteSQL_DDL(string strFilePath, string strSQL)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };

        using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
        {
            cn.Open();
            using (var cmd = new SQLiteCommand(cn))
            {
                cmd.CommandText = strSQL;
                cmd.ExecuteNonQuery();
            }
        }
    }

    #region Private Method 2st.

    /// <summary>
    /// テーブル作成処理(バージョン01.00)
    /// </summary>
    /// <returns></returns>
    private string _GetCreateTableSQL_V0100()
    {
        string strSQL = "";
        strSQL = " create table T_File ( "
                + "   intProjectNo integer "
                + "   , intCardNo integer"
                + "   , txtCommentNo TEXT "
                + "   , intFileNo integer"
                + "   , bloFile BLOB "
                + "   , constraint T_File_PKC primary key (intProjectNo,intCardNo,txtCommentNo,intFileNo) "
                + " ); "
                + "  "
                + " create table T_FileInfo ( "
                + "   intProjectNo integer"
                + "   , intCardNo integer"
                + "   , txtCommentNo TEXT "
                + "   , intFileNo integer"
                + "   , txtFileName TEXT "
                + "   , constraint T_FileInfo_PKC primary key (intProjectNo,intCardNo,txtCommentNo,intFileNo) "
                + " ); ";
        return strSQL;
    }

    #endregion

    #endregion

}

↑↑↑↑↑ サンプルソース ↑↑↑↑↑

PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...