ID("identity")属性を利用するときはトランザクションのaspectを設定する必要がある

S2Dao.NETではSQL ServerのIdentity列をプライマリー キーに指定しているテーブルに対して、DTOの値を自動インサートしてくれる機能があります。例えば

CREATE TABLE [Company] (
  [CompanyID] [int] IDENTITY (1, 1) NOT NULL,
  [CompanyName] [nvarchar] (50)
) 

というようなテーブルがあった場合、

public class Company
{
    private int companyID;
    private string companyName;

    [ID("identity")]
    public int CompanyID
    {
         set { this.companyID = value; }
         get { return this.companyID; }
    }

    public string CompanyName
    {
        set { this.companyName = value; }
        get { return this.companyName; }
    }

    public override string ToString()
    {
        return string.Format(
           "CompanyID:[{0}] CompanyName:[{1}]", 
           CompanyID, CompanyName);
    }
}

というDTOを用意して、

[Bean(typeof(Company))]
public interface ICompanyDao
{
    void AddCompany(Company company);

    .....
}

というDaoインターフェースを定義しておくとS2Dao.NETが自動的にSQLクエリを実行するクラスを生成してくれます。

public int AddCompany()
{
    Company company = new Company();
    company.CompanyName = "ABC会社";
    this._companyDao.AddCompany(company);
    return company.CompanyID;
}

後は上のコードのようにDaoインターフェースで定義したメソッドを呼び出すだけです。
この例で分かるように、CompanyIDを設定しなくてもデータベースのCompanyテーブルにデータが正しく挿入され、Identity列として定義したCompany.CompanyIDには挿入した時点のID値をS2DaoInterceptorから呼び出されるIdentityIdentifierGeneratorがセットして戻してくれます。

とても便利な機能なんですが、一点注意があります。Daoを呼び出すコンポーネントトランザクションアスペクトを指定しておく必要があります。これを忘れると内部で実行しているSQL文"select @@IDENTITY"の戻り値がDBNullとなり、対応するプロパティがSystem.Int32などのプリミティブ型の場合、キャスト例外が発生します。

理由はLocalRequiredTxHandlerなどのトランザクションを有効にするハンドラをアスペクトで指定しないと、TransactionContext.OpenConnectionメソッドが呼び出されず、その結果、BasicSelectHandler.Executeメソッドは毎回新しいSqlConnectionオブジェクトを作成し、Openするからです。SQL Serverの@@Identityは同一コネクションであればトランザクションで囲まなくても値が取得できますが、コネクションが異なるとNULLを返します。

S2Dao.NETのこの振る舞いはSQL Serverに慣れたプログラマは混乱するかもしれません。またソースを深く追いかけないと原因の特定も難しいです。ただ、今のこのデザインを変えるのは難しいと思いますので、ドキュメントで明記するのが良いのではないでしょうか。


ひょっとして、これってS2Dao.NETユーザーにとっては衆知の事実なんでしょうか?(^_^;)