WebSurfer's Home

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

ACE OleDb で Excel のブック作成

by WebSurfer 2012年1月26日 23:52

注意:
ダウンロードページ「Microsoft Access データベース エンジン 2010 再頒布可能コンポーネント」の「詳細」にある条件に "ファイル作成の手段としての使用" はできないと書いてあります。ということは、この記事に書いてあるような Excel のブックを作るようなことはライセンス的に許可されてないのかもしれませんので注意してください。

ADO.NET と ACE OleDb プロバイダを利用して Excel のブックを新規作成する話です。

備忘録として残しておきたかったのは、作成するブックが xls 形式の時は接続文字列の Extended Properties に "Excel 8.0" を、xlsx 形式の時は "Excel 12.0 Xml" を指定するということです。

それを書いただけではブログの記事としては寂しいので、以下を追加しました。(笑)

ACE OleDb プロバイダで作成した Excel ファイル

JET でも ACE でも、接続文字列の Data Source に存在しないファイル名を指定して、CREATE TABLE クエリを実行すると、そのファイル名でブックを新規作成できます。

ACE の場合は以下のような感じです。この例では、c:\workbooks というフォルダに test.xlsx という名前のファイルが作成されます。

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

namespace OleDbExcel
{
  class Program
  {
    static void Main(string[] args)
    {
      DataTable table = CreateDataTable();
      string createQuery = CreateQuery(table);
      string insertQuery = InsertQuery(table);
      string connString = 
        "Provider=Microsoft.ACE.OLEDB.12.0;" + 
        "Data Source=C:\\workbooks\\test.xlsx;" + 
        "Extended Properties=\"Excel 12.0 Xml;HDR=YES\"";
            
      using (OleDbConnection connection = 
        new OleDbConnection(connString))
      {
        // Excel ブックを作成
        connection.Open();
        using (OleDbCommand command = 
          new OleDbCommand(createQuery, connection))
        {                    
          command.ExecuteNonQuery();
        }

        // Excel ブックにデータを INSERT
        using (OleDbCommand command = 
          new OleDbCommand(insertQuery, connection))
        {                    
          foreach (DataColumn column in table.Columns)
          {
            command.Parameters.Add(
              "@" + column.ColumnName, 
              OleDbType.VarWChar);                        
          }

          foreach (DataRow row in table.Rows)
          {
            foreach (DataColumn column in table.Columns)
            {
              command.
                Parameters["@" + column.ColumnName].
                Value = 
                  row[column.ColumnName];
            }
            command.ExecuteNonQuery();
          }
        }
      }
    }

    // Excel ブックを作る基になるデータを SQL Server の
    // サンプルデータベース Northwind の Customers テー
    // ブルより取得し DataTable を作る。
    protected static DataTable CreateDataTable()
    {
      DataTable dataTable = new DataTable();
      SqlDataAdapter adapter = new SqlDataAdapter();
      string connString = Properties.Settings.Default.MyDB;
      string query = 
        "SELECT CustomerID, CompanyName, ContactName, ContactTitle" + 
        " FROM Customers";
      SqlConnection connection = new SqlConnection(connString);
      SqlCommand command = new SqlCommand(query, connection);
      adapter.SelectCommand = command;
      adapter.Fill(dataTable);
      return dataTable;
    }

    // DataTable の DataColumn を基に CREATE TABLE クエリ
    // を作成する。この例では以下のようになる。
    // CREATE TABLE [MySheet](CustomerID char(255),
    // CompanyName char(255),ContactName char(255),
    // ContactTitle char(255))
    protected static String CreateQuery(DataTable table)
    {
      StringBuilder sb1 = new StringBuilder();
      sb1.Append("CREATE TABLE [MySheet](");
      foreach (DataColumn column in table.Columns)
      {
        sb1.Append(column.ColumnName + " char(255),");
      }
      sb1.Remove(sb1.Length - 1, 1);
      sb1.Append(")");
      return sb1.ToString();
    }

    // DataTable の DataColumn を基に INSERT クエリを作成
    // する。この例では以下のようになる。
    // INSERT INTO [MySheet] (CustomerID,CompanyName,
    // ContactName,ContactTitle) values (?,?,?,?)
    protected static String InsertQuery(DataTable table)
    {
      StringBuilder sb1 = new StringBuilder();
      StringBuilder sb2 = new StringBuilder();
      sb1.Append("INSERT INTO [MySheet] (");
      sb2.Append(" values (");
      foreach (DataColumn column in table.Columns)
      {
        sb1.Append(column.ColumnName + ",");
        sb2.Append("?,");
      }
      sb1.Remove(sb1.Length - 1, 1);
      sb2.Remove(sb2.Length - 1, 1);
      sb1.Append(")");
      sb2.Append(")");
      return sb1.ToString() + sb2.ToString();
    }
  }
}

作成したファイルを Excel アプリケーションで開いたのが上の画像です。クエリで指定したテーブル名のシート(例では、MySheet)が生成されています。

上のコードで、接続文字列の Extended Properties="Excel 12.0 Xml;HDR=YES" の Excel 12.0 Xml の部分はソースデータベースの種類の指定です。HDR=YES は、シートの 1 行目がヘッダであることを指定します。

ソースデータベースの種類は、作成するブックの形式によって、以下のように変わります。

ブック形式 指定する DB 種類
xls Excel 8.0
xlsx Excel 12.0 Xml
xlsb Excel 12.0
xlsm Excel 12.0 Macro

xls は Excel 2003 以前の形式のブック、その他は Excel 2007 以降の Microsoft Office Open XML 形式のブックです。

8.0 または 12.0 という数字は ISAM (Indexed Sequential Access Method) ドライバのバージョンです。

注意すべきは、xlsx 形式のブックを作る時は "Excel 12.0 Xml" のように Xml を付けなければならないところです。JET の場合のように単に "Excel 12.0" とすると、中身は xlsb 形式(バイナリ形式)になってしまいます。

Excel アプリケーションでそのファイル開こうとすると、"Excel でファイルを開くことができません。ファイル形式または・・・" という警告メッセージが出て開けません(拡張子を xlsb に変更すれば開けますが)。

ブックを作った後、SELECT, INSERT, UPDATE(Excel の場合、DELETE はできません)操作を行う場合は、Microsoft サポートの記事 Visual Basic .NET と ADO.NET を使用して Excel ブックのレコードの取得と変更を行う方法 が参考になると思います。

Tags: , ,

ADO.NET

TableAdapterManager

by WebSurfer 2011年12月21日 22:19

Visual Studio には、型指定された DataSet + TableAdapter をウィザードベースで自動生成する機能があります。さらに、Visual Studio 2008 以降では、階層更新を実現する TableAdapterManager クラスが追加で自動生成されるようになりました。

今さらながらですが、TableAdapterManager について調べて、いろいろ発見があったので忘れないように書いておきます。

TableAdapterManager を利用したアプリケーション

階層更新とは、簡単に言うと、複数のテーブルで構成される DataSet が持つ更新データを、データベースの整合性に関する規則(参照整合性規則)を守って、データベースに挿入、更新、削除するプロセスのことです。(詳しい説明は MSDN ライブラリの 階層更新 を参照してください)

例として、上の画像のような、Northwind サンプルデータベースの Customers と Orders テーブル両方を同時に管理するアプリケーションを考えます。(Customers テーブルは CustomerID を主キーとして持ち、Orders テーブルは CustomerID を外部キーとして持っています。)

新しい顧客から注文があった場合は、先に新しい顧客レコードを Customers テーブルに Insert してから、注文レコードを Orders テーブルに Insert しなければなりません。

Customers テーブルから顧客レコードを Delete する場合は、先に Orders テーブルの当該顧客の注文レコードをすべて Delete してからにしなければなりません。

さらに、Insert、Update、Delete の順序も重要です。例えば、Customers テーブルの既存のレコードの主キー値を変更する場合、以下のような手順にしなければなりません。

  1. Customers テーブルに、新しい CustomerID 値で、新しいレコードを Insert
  2. Orders テーブルの当該レコードの CustomerID を新しい値に Update
  3. Customers テーブルの旧 CustomerID のレコードを Delete

従って、Insert => Update => Delete の順で行う必要があります(TableAdapterManager のデフォルトがこれ。他に、Update => Insert => Delete とすることも可能)。

Visual Studio 2005 以前のバージョンでは、上記のような参照整合性規則を守って更新を行う(即ち、階層更新を実現する)ためのコードを自力で実装する必要がありました。具体的には以下ようなコードを実装しなければなりません。

  1. まず、DataTable の Select メソッドを使用して特定の RowState(Added、ModifiedCurrent、Deleted)を持つ行だけを参照する DataRow 配列を取得します。(DataAdapter によるデータ ソースの更新 の「挿入、更新、削除の順序」のセクション参照)
  2. その後、各テーブルの TableAdapter の Update(DataRow[]) メソッドを適切な順序で呼び出し、各テーブルから RowState 別に取得した DataRow 配列を適切な順序で渡して処理します。
  3. 当然ながらトランザクション処理も必要です。

Visual Studio 2008 から新しく追加された TableAdapterManager クラスには、階層更新を実現するロジックを持つ UpdateAll メソッドが実装されています。MS-DTC を使わない手動トランザクション処理も実装されています。

実際に、MSDN ライブラリのチュートリアル SQL Server Express データベースの作成SQL Server Express データベース内のデータへの接続 (Windows フォーム) を参考に、型指定された DataSet + TableAdapter を作って、その中の TableAdapterManager のコードを見てみました。(もっと詳しいチュートリアルがありました。下の 2011/12/22 追記の「その1」を参照ください)

TableAdapterManager の UpdateAll メソッドは、Customers => Orders の順で両方のテーブルに対して Insert を行い、次に Customers => Orders の順で両方のテーブルに対して Update を行い、そして、最後に Orders => Customers の順で両方のテーブルに対して Delete を行うというように、上に述べた参照整合性規則を守って更新を実行するためのコードが実装されているのが確認できました。

TableAdapterManager は万能ではないと思いますが、多くのケースで、自力で階層更新を実現するためのコードを書く必要がなくなったのではないでしょうか。

自動生成されたコードで対応できない場合は、TableAdapterManager クラスは partial として定義されているので、自力でコードを書いてそれを拡張すれば、Visual Studio 2005 以前のときと比べて比較的簡単に対応可能と思います。

------------- 2011/12/22 追記 -------------

その1

Customers テーブルと Orders テーブルを使ったアプリケーションの作成は、上記のページより、10 行でズバリ !! 非接続型のデータ アクセス (ADO.NET) (C#) の方が詳しく書いてあって、参考になると思います。

その2

単一テーブルのアプリケーションにおいて、既存のレコードを Delete して、そのレコードと同じ主キーを持つレコードを新たに Insert する場合、Delete => Insert の順でないとうまくいきません。

TableAdapterManager は、Insert => Update => Delete(デフォルト)または Update => Insert => Delete のいずれかしか更新順序は選べませんので、上記のケースには対応できないということになります。

本文に述べた Customers テーブルの既存のレコードの主キー値を変更するケースより、上記のケースの方が多そうな気がするのですが、Microsoft は何故 Delete を最初にしなかったのでしょう? 気になります。(2015/11/12 追記: Delete => Insert などという乱暴なことを許すと、ユーザーのミスで問題が出る可能性が高くなるからではないかと最近思い初めています。Update すれば済む話ですから)

Tags:

ADO.NET

TableAdapter と Transaction

by WebSurfer 2011年12月18日 20:33

Visual Studio を利用してウィザードベースで自動生成する型付 DataSet + TableAdapter を使ってデータベースを更新する際、手動トランザクション処理するにはどうしたらいいでしょうか?(MS-DTC を使う自動トランザクションではなくて)

Visual Studio 2008 から TableAdapter に Transaction プロパティが追加されたので、それと、それ以前からある Connection プロパティを使えば、Visual Studio 2005 以前と比較して手動トランザクションが容易に設定できます。

でも、Visual Studio 2008 以降を使っているなら、そもそも手動トランザクションをかけるコードを自力で書く必要はなくて、自動生成される TableAdapterManager クラスの UpdateAll メソッドを使用すればいいです。

実は知らなかったのですが、TableAdapterManager.UpdateAll メソッドのコードをよく見てみると、そのメソッドの中でトランザクションがかかるようになってました。

もともと、TableAdapterManagerは 階層更新 を実現する ため(即ち、複数のテーブルを、データベース内の制約によって定義される一貫性規則を守って関連レコードの挿入、更新、および削除を制御するため)のものということらしいですが、単一テーブルの場合もトランザクションは有効に働きます。

という訳で、Visual Studio 2008 以降なら TableAdapterManager の UpdateAll メソッドを使うのが正解だと思います。

ちなみに、Visual Studio 2005 以前の場合は Transaction プロパティは自動生成されたコードには定義されません。TableAdapter は partial クラスとして定義されているので、自動生成されたファイルとは別に、Visual Studio 2008 以降で生成されるコードを参考に、自力で Transaction プロパティを定義して使うのがいいと思います。SqlClient の場合は以下のような感じです。

private System.Data.SqlClient.SqlTransaction _transaction;

internal System.Data.SqlClient.SqlTransaction Transaction {
  get {
    return this._transaction;
  }
  set {
    this._transaction = value;
    for (int i = 0; i < this.CommandCollection.Length; i++) {
      this.CommandCollection[i].Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.DeleteCommand != null))) {
      this.Adapter.DeleteCommand.Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.InsertCommand != null))) {
      this.Adapter.InsertCommand.Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.UpdateCommand != null))) {
      this.Adapter.UpdateCommand.Transaction = this._transaction;
    }
  }
}

どうも、Visual Studio 2008 から TableAdapter に Transaction プロパティが追加された理由は、TableAdapterManager.UpdateAll メソッドで手動トランザクションをかけるためのようです。

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