WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

OleDb + JET 用で名前付きパラメータは使えるか?

by WebSurfer 2021年5月16日 12:39

ADO.NET + JET (または ACE) + OleDb プロバイダで Access を操作する場合、この記事の一番下に書いたサンプルコードのように SQL 文に用いるパラメータには疑問符 (?) で指定される位置パラメータマーカーを使用することになっています。そこを名前付きパラメータを使ったらどうなるかという話を書きます。

実行結果1

エラーになると思っていたのですが、そうはならず少なくとも見かけは期待通り動いている例がみられたので、ちょっと調べてみた次第です。

Microsoft のドキュメント「パラメーターおよびパラメーターのデータ型の構成 (ADO.NET)」に概略が書いてありますが、SQL 文をパラメータ化する場合、パラメータのプレースホルダーの構文はデータソースとプロバイダに依存するそうです。

ADO.NET で Access を操作する際、プロバイダには .NET Framework Data Provider for OLE DB を利用するのが普通と思います。

その場合、SQL 文には疑問符 (?) で指定される位置パラメータマーカーを使用するということになっています。Microsoft のドキュメント OleDbCommand.Parameters Property(日本語版は翻訳がアレです)に以下のように書いてあります。

"Remarks: The OLE DB .NET Provider does not support named parameters for passing parameters to an SQL statement or a stored procedure called by an OleDbCommand when CommandType is set to Text. In this case, the question mark (?) placeholder must be used."

"must" は書いてあることが仕様の絶対的要件であることを意味するそうです。

それに反して名前付きパラメータを使ったらどうなるか試してみました。下のコードの INSERT 文で、...VALUES (?, ?) のところを ...VALUES (Info1, Info2) に変えてみます。実行結果は上の画像と同様に問題なしでした。

しかし、...VALUES (Info, Info) とか ...VALUES (a, a) というように同じ名前を使うと、[Info1], [Info2] には最初のパラメータ(下のコード例では "Info1" と名付けた方)に代入した値のみしか INSERT されないという不可解な動きになります。その結果が下の画像です。

実行結果2

個人的には、疑問符 (?) が must ならそれに反することをしたら即エラーにするのが良いと思うのですがどうでしょう? エラーも出さないで不可解な結果になって、それに気が付かないということもあるでしょうから。

以下にこの記事を書くのに使った .NET Framework 4.8 のコンソールアプリのサンプルコードを載せておきます。

using System;
using System.Data.OleDb;

namespace ConsoleAccess
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\surfe\...\Access.mdb";
            var connString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + path;
            var sqlInsert = "INSERT INTO [XXX] ([Info1], [Info2]) VALUES (?, ?)";

            using (var connection = new OleDbConnection(connString))
            {
                connection.Open();
                using (var command = new OleDbCommand())
                {
                    command.Connection = connection;
                    command.CommandType = System.Data.CommandType.Text;
                    command.CommandText = sqlInsert;
                    
                    // パラメータ名を付けるのは以下のような使い方をする場合必要
                    command.Parameters.Add(new OleDbParameter("Info1", OleDbType.Char));
                    command.Parameters.Add(new OleDbParameter("Info2", OleDbType.Char));
                    
                    // コードの並び順を変えても名前で指定したパラメータに代入される
                    command.Parameters["Info2"].Value = "info2";
                    command.Parameters["Info1"].Value = "info1";

                    var i = command.ExecuteNonQuery();

                    Console.WriteLine($"Affected Rows: {i}");
                }

                int id = -1;
                using (var command = new OleDbCommand("SELECT @@IDENTITY", connection))
                {
                    object obj = command.ExecuteScalar();
                    if (obj != null && obj.GetType() != typeof(DBNull))
                    {
                        id = (int)obj;
                    }
                    Console.WriteLine($"New id: {id}");
                }

                var sqlSelect = "SELECT [ID], [Info1], [Info2] FROM [XXX] WHERE ID = ?";
                using (var command = new OleDbCommand())
                {
                    command.Connection = connection;
                    command.CommandType = System.Data.CommandType.Text;
                    command.CommandText = sqlSelect;

                    command.Parameters.Add(new OleDbParameter("ID", OleDbType.Integer));
                    command.Parameters["ID"].Value = id;

                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine($"ID: {reader[0]}, " +
                                $"Info1: { reader[1]}, Info2; {reader[2]} ");
                        }
                    }
                }
            }
        }
    }
}

Tags: , ,

ADO.NET

Access の更新

by WebSurfer 2010年9月4日 20:01

Visual Studio のウィサードを使うと、SQL Server や Access のテーブルを表示して、レコードを INSERT, DELETE, UPDATE するプログラムが簡単に作れます。

ただし、Access でオートナンバー(SQL Server で言うと IDENTITY)を使っている場合、INSERT した時に DB 側で設定したオートナンバー値を DataSet に書き込むところまでは面倒を見てくれません。(SQL Server の場合は面倒見てくれます)

これは、JET データベースエンジン(Microsoft Access およびその他の小規模アプリケーションで使用されている)では、単一バッチで複数のステートメントを実行できないということが理由のようです。

詳しくは、MSDN ライブラリの @@IDENTITY クライシスを管理する の「Microsoft Access/JET の問題」のセクションを参照してください。

ここでは、DataAdapter の RowUpdated イベントを使用して自動生成されたコードとは別のクエリを実行し、オートナンバー値を DataSet に書き込む具体的な例を紹介します。

まず、いつもの手順で型付 DataSet を作り、データソースウィンドウからテーブルを Form にドラッグ&ドロップしてアプリケーションを作ります。以下の画像がその例です。ここまではコードは一行も書く必要がありません。

Visual Studio でのアプリ作成

ただし、ここまでの実装では、新しいレコードを INSERT したとき、オートナンバーとなっている ID の値が DataSet に書き戻されていないので、DataGridView に表示されている ID 値は正しくありません。

そこで、INSERT 直後に発生する DataAdapter の RowUpdated イベントのハンドラで、"SELECT @@IDENTITY" クエリを使って新規 ID 値を取得し、それを DataSet に書き込んでやります。

具体的には、TableAdapter を partial class を使って拡張します。ソリューションにクラスファイルを追加して、以下のようなコードを実装します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.OleDb;

namespace AccessWithAutoNumberAndDataSet.DataSet1TableAdapters 
{
  public partial class XXXTableAdapter
  {
    public void SetHandler()
    {
      this.Adapter.RowUpdated += 
        new OleDbRowUpdatedEventHandler(Adapter_RowUpdated);
    }

    private OleDbCommand cmd = 
      new OleDbCommand("SELECT @@IDENTITY", null);

    private void Adapter_RowUpdated(Object sender, OleDbRowUpdatedEventArgs e)
    {
      cmd.Connection = e.Command.Connection;
      cmd.Transaction = e.Command.Transaction;

      if (e.StatementType == StatementType.Insert && 
        e.Status == UpdateStatus.Continue)
      {
        object obj = cmd.ExecuteScalar();

        if (obj != null && obj.GetType() != typeof(DBNull))
        {
          e.Row["ID"] = (int)obj;
          e.Row.AcceptChanges();
        }
      }
    }
  }
}

これだけでは、ハンドラがイベントにアタッチされていないので、自動生成された Form のコードのコンストラクタに、上記 partial class で定義した SetHandler メソッドを追記します。以下のような感じです。

namespace AccessWithAutoNumberAndDataSet
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();

      // これを追加。
      this.xXXTableAdapter.SetHandler();
    }

これで、新規 ID 値が DataSet に書き込まれ、DataGridView に表示されるようになります。

Tags: , ,

ADO.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar