Skip to content

UserGuide_BetterUse&FAQ.ja

daisuke nishino edited this page Jul 22, 2026 · 3 revisions

Open 棟梁 利用ガイド (ベターユース、FAQ 編)

2026年7月更新版

はじめに

本ドキュメントの対象

  • Open 棟梁を用いたアプリケーション開発を行う、SE・開発者

本ドキュメントの概要

本ドキュメントは、フレームワークのベターユースについて纏めています。

他社所有名称に対する表示

本ドキュメントに記載の会社名・商品名は、各社の商標または登録商標です。

目次

1. クラスの分割方法

2. P層に関するトピック

3. B層に関するトピック

4. D層に関するトピック

5. ASP.NET Mobile Web 開発

6. ログの見方

7. FAQ

1. クラスの分割方法

1.1 B層クラス

  • イベント処理の B層:1 画面に 1 つ作成を基本とする。各イベント処理はコントロール名をメソッド名に使用すると良い。(図1.1.1)
  • 共通部品の B層:システムで 1 つ、または共通部品の区分毎に 1 つ作成を基本とする。区分名をメソッド名に使用すると良い。(図1.1.2)

図 1.1.1 B層クラスの分割例(イベント処理)

図 1.1.2 B層クラスの分割例(共通部品)

1.2 引数・戻り値クラス

  • イベント処理の場合:1 画面に 1 つ作成を基本とし、複雑でイベント数の多い画面ではいくつかのクラスに分けて作成すると良い。
  • 共通部品の場合:共通部品の区分毎に 1 つ、または共通部品毎に 1 つ作成を基本とする。

1.3 D層クラスの粒度

D層 (Dao) クラスの作成基準は以下に従うと良いでしょう。

  • 個別に Dao クラスを使用:インテリジェンスな D層クラス [1] が必要なら、機能毎に「自作 Dao クラス」を実装する。
  • 汎用的な Dao クラスを使用:入力したパラメタをそのまま設定するだけで実行できる SQL の場合は、汎用インターフェイスを持つ「共通 Dao クラス」を使用する (4.6 節参照)。
  • 自動生成した Dao クラスを使用:D層自動生成ツールでテーブル単位の CRUD 部品を自動生成し、更新系処理はこの「自動生成 Dao クラス」を使用する。
  • 集約する場合:B層から「共通 Dao クラス」「自動生成 Dao クラス」を集約して使用する「Dao 集約クラス」も可能。「データアクセス制御クラス」を引き継ぐ必要がある。
/// <summary>Dao集約クラスのベースクラスの例</summary>
public class BaseConsolidateDao {
  /// <summary>データアクセス制御クラス</summary>
  private BaseDam _dam;
  /// <summary>データアクセス制御クラス</summary>
  protected BaseDam Dam {
    get { return this._dam; }
  }
  /// <summary>コンストラクタ</summary>
  /// <param name="dam">データアクセス制御クラス</param>
  public BaseConsolidateDao(BaseDam dam) {
    this._dam = dam;
  }
}

図1.3 Dao集約クラスのベースクラスの例

2. P層に関するトピック

  • 2.1 P層イベント処理の対象外のイベント:ClientCallback によるサーバ側イベントなど。この場合、イベント処理から B層・D層を直接呼び出す (B層のエラー処理・トランザクション管理・ログ出力は正常に機能する)。
  • 2.2 P層イベント処理の対象コントロール イベント追加:サンプルを流用すればチェックボックス コントロールのチェック チェンジ イベントに対応した状態で利用可能。
  • 2.3 不正操作防止機能の局所化CanCheckIllegalOperation フラグにより、ページ単位・ボタン単位で局所化が可能。
/// <summary>不正操作防止機能の局所化</summary>
void Page_Init(object sender, EventArgs e) {
  foreach (string key in Request.Form.Keys) {
    if (key.IndexOf("btnIllegalOperationCheckOFF") != -1) {
      // btnIllegalOperationCheckOFFボタンで
      // サブミットされた場合、不正操作防止機能をOFFにする。
      this.CanCheckIllegalOperation = false;
    }
  }
}

図2.3‐1 不正操作防止機能の局所化の例 一部のボタンだけ不正操作防止機能を無効にする方法、ポストバックのダウンロード処理などで有効

/// <summary>不正操作防止機能の局所化</summary>
void Page_Init(object sender, EventArgs e) {
  // 当該画面だけ、不正操作防止機能をONにする(*.configはOFFに設定)。
  this.CanCheckIllegalOperation = true;
  foreach (string key in Request.Form.Keys) {
    if (key.IndexOf("btnIllegalOperationCheckON") != -1) {
      // btnIllegalOperationCheckONボタンで
      // サブミットされた場合、不正操作防止機能をONにする。
      this.CanCheckIllegalOperation = true;
    }
  }
}

図2.3‐2 不正操作防止機能の局所化の例 一部のボタンだけ不正操作防止機能を有効にする方法、更新ボタンだけに適用したい場合などに有効

  • 2.4 暫くお待ちくださいダイアログの一時無効化:ポストバックのダウンロード処理などで、ダウンロード処理を行うポストバック ボタンに JavaScript を実装して抑止する。
<cc1:WebCustomButton ID="btnButton33" runat="server"
  Text="ダウンロード" Width="220px" OnClientClick="IsDownload = true;" />

図2.4 暫くお待ちくださいダイアログの一時無効化の例

  • 2.5 IFRAME へのダウンロード処理(二重送信防止機能の一時無効化):IFRAME へのダウンロード時、「二重送信防止機能」により操作が抑止され続ける場合、処理を実装して一時的にキャンセルする。
this.Form.Attributes.Remove("onSubmit");

図2.5 二重送信防止機能の一時無効化

  • 2.6 セッションの削除方法:「Session タイムアウト検出機能」使用時に Session.Abandon() を呼ぶと必ず Session タイムアウト例外が発生する。例外を発生させないには this(BaseController).FxSessionAbandon() を使用し、検出用 Cookie を削除 [2] してから解放する。ただし実行後に同一画面でポストバックすると「不正操作防止機能」エラーになるため、解放後はメニュー画面等へ Get 画面で遷移すること。
this.FxSessionAbandon(); // Session タイムアウト検出用 Cookie を削除したうえで 、セッションの解放を行う。

図2.6 セッションの削除方法

2.7 ダウンロード処理方式

表 2.7 ダウンロード処理の推奨方式(FAQ 抜粋)

項番 問題 対応
ファイルをダウンロードした場合、Loading... ダイアログの表示が消えない。 JavaScript の Loading... ダイアログ表示機能に起因。ダウンロード前に JavaScript でこの機能を無効にする (最新版ではフラグで一時無効化可能、2.4 節参照)。または別ウィンドウ/IFRAME (EnableViewState=False) からのリクエストでファイルをレスポンスする。
ダウンロード後の操作で不正操作例外が発生する。 不正操作防止機能に起因。config 設定で無効にする (最新版では画面プロパティで局所化可能、2.3 節参照)。または別ウィンドウ/IFRAME を使用する。
Yes/No ダイアログの Yes/No クリックでダウンロードすると、次の PostBack でも Yes/No のイベント ハンドラが動作する。 Submit フラグが更新されないために発生。別ウィンドウ/IFRAME を使用する (不正操作防止機能の局所化には Submit フラグが利用できる)。
ダウンロード後に任意のアプリでファイルを開けない。 キャッシュ無効化の HTTP ヘッダが付与されている可能性。ダウンロード前に Response.Clear() で HTTP ヘッダをクリアしてから、キャッシュ・コントロールの HTTP ヘッダを再設定する。
IFRAME の更新後に IFRAME 親画面の操作ができなくなる。 readyState が complete にならず interactive になるため、二重送信防止機能により操作が抑止され続けることに起因。2.5 節の方法で対策可能。
別ウィンドウ・ダイアログ表示の際にポストバックが発生する。 仕様上、ShowNormalScreen()/ShowModalScreen() でフラグを立てクライアント側から起動するため。最新版ではクライアント側 JS から直接起動する GetScriptToShowNormalScreen()/GetScriptToShowModalScreen() を追加。

3. B層に関するトピック

3.1 データアクセス制御クラス(Dam)の手動生成

手動のトランザクション管理や、2 層 C/S で 2 本目のコネクションを必要とする場合などは、データアクセス制御クラス (Dam) を手動で生成して、コネクション・トランザクションを管理します。トランザクション・コネクションの解放処理 (CommitTransactionConnectionClose メソッド) には try...catch...finally を使用しても良いですが、通常 GC により解放される場合、トランザクションはロールバックされ、コネクションも切断 (または接続プールに戻る) されます。

/// <summary>業務処理を実装</summary>
/// <param name="testParameter">引数クラス</param>
private void UOC_メソッド名(TestParameterValue testParameter)
{
 //メソッド引数にBaseParameterValueの派生の型を定義可能。
 // 戻り値クラスを生成して、事前に戻り地に設定しておく。
 TestReturnValue testReturn = new TestReturnValue();
 this.ReturnValue = testReturn;
 // ↓業務処理-----------------------------------------------------
 // データアクセス制御クラス(Dam)
 // SQL Server / SQL Client用のDamを手動で生成
 BaseDam dam = new DamSqlSvr();
 // 接続文字列をロード
 string connstring = GetConfigParameter.GetConnectionString("ConnectionString_SQL");
 // コネクションをオープンする。
 dam.ConnectionOpen(connstring);
 // 手動トランザクション(規定の分離レベル)
 dam.BeginTransaction(DbEnum.IsolationLevelEnum.ReadCommitted);
 // 個別Dao
 LayerD myDao = new LayerD(dam);
 //myDao.xxxx(testParameter, ref testReturn);
 // 共通Dao
 CmnDao cmnDao = new CmnDao(dam);
 cmnDao.ExecSelectScalar();
 // トランザクション、コネクションの解放
 dam.CommitTransaction();
 dam.ConnectionClose();
 // ↑業務処理-----------------------------------------------------
}

図3.1 データアクセス制御クラス(Dam)を手動で生成して、コネクション、トランザクションを管理する例

4. D層に関するトピック

4.1 パラメタライズド・クエリを使用した条件検索 + あいまい検索処理

動的 SQL には動的パラメタライズド・クエリが有効です。条件検索 + あいまい検索という複雑な SQL でも、SQL 定義 (XML) とプログラムで容易に対応可能です。

<?xml version="1.0" encoding="shift_jis" ?>
<ROOT>
  SELECT
  A, B, C
  FROM T
  WHERE
  <IF>AND A = @A</IF>
  <IF name="A_LIKE">AND A LIKE @LIKE_A</IF>
  <IF>AND B = @B</IF>
  <IF name="B_LIKE">AND B LIKE @LIKE_B</IF>
  <IF>AND C = @C</IF>
  ORDER BY <IF name="SEQUENCE">A<ELSE>B</ELSE></IF>
</ROOT>
図4.1‐1 SQL定義(XML) 
' ファイルから読み込む
Me.SetSqlByFile2("KO0200_Get_Customer.xml")
' パラメタ設定
' パラメータA
If PV.A <> "" Then
  If PV.A_IsAimai Then
    ' LIKE前方一致検索
    Me.SetParameter("LIKE_A", PV.A & "%")
  Else
   ' 通常の=一致検索
   Me.SetParameter("A", PV.A)
  End If
End If

' パラメータB
If PV.B <> "" Then
  If PV.B_IsAimai Then
    ' LIKE前方一致検索
    Me.SetParameter("LIKE_B", PV.B & "%")
  Else
    ' 通常の=一致検索
    Me.SetParameter("B", PV.B)
  End If
End If

' パラメータC
Me.SetParameter("C", PV.C)
' 並び順
Me.SetParameter("SEQUENCE ", PV.SEQ)
' 結果セットの取得
RV.dt = New DataTable()
Me.ExecSelectFill_DT(RV.dt)

図 4.1-2 D層のプログラム(VB)

4.2 パラメタライズド・クエリ実行時の「暗黙の型変換」による性能劣化の対処方法

検索条件の「パラメタのデータ型」と「DB の列のデータ型」が不一致になると、列のデータがパラメタのデータ型にキャストされて検索されます (「暗黙の型変換」)。これが発生するとインデックスが正しく使われず、性能が大きく劣化します。

SELECT * FROM AAA WHERE BBB = @P1

本フレームワークでは、SetParameter メソッドからパラメタのデータ型を指定できますが、データ型の明示が必要になるのは「暗黙の型変換」が発生した場合のみです。通常はデータ型を明示せず、性能劣化が確認された場合に外出しの SQL ファイルを編集 (パラメタのデータ型を SQL 内でキャスト) する方法を推奨します。

SELECT * FROM AAA WHERE BBB = CONVERT(varchar, @P1)

※ 動的パラメタライズド・クエリの「LIST タグ」を使用した場合は、パラメタが自動展開されるため SQL ファイル編集で対策できません。この場合は API からデータ型を明示するか、「LIST タグ」ではなく「VAL タグ」を使い、各 DBMS の変換関数を仕掛けたパラメタを必要な数だけ埋め込んで対処します。

4.3 DataSet、DataTable を使用したバッチ更新処理の実装方法

.NET の CommandBuilder による自動生成バッチ更新用 DataAdapter は、テーブル単位の SELECT にしか対応しない、タイムスタンプアンマッチ行が把握できない、IDENTITY 列も INSERT に含める、全列比較の楽観排他で性能問題がある、などの問題があり、本フレームワークではサポートしません (マスタメンテなど限られた範囲では有効)。

代わりに、AcceptChanges メソッド [5] と DataRow の RowState プロパティ (Added/Modified/Deleted)、DataRowVersion.Original [6] を活用してバッチ更新を実装します。

dr["(列名)", DataRowVersion.Original] (dr は DataRow クラス)

自動生成した CRUD 用 Dao を利用すると更に容易です。サンプルは ~\root\programs\C#\Samples\2CS_sample\DenDaoAndBatUpd_sample にあります。注意点は、Webで、複数ポストバックに跨ってデータ編集を行う場合、編集中の DataSet、DataTable を Session などに保持する必要があることで、この場合、サーバ メモリの消費量などに注意が必要。

4.4 ストアド プロシージャの実行方法

本フレームワークではストアド プロシージャの実行も可能です。「戻り値」の例では Parameter.Direction プロパティに ParameterDirection.ReturnValue を指定します (出力パラメタは .Output.InputOutput)。最新版では、コマンド オブジェクトを直接参照しなくても GetParameter メソッドで取得可能です。

/// <summary>「sp_help」ストアドプロシージャを実行する。</summary>
/// <param name="objectName">「sp_help」ストアドプロシージャの「objname」パラメタに指定する文字列</param>
/// <param name="ds">「sp_help」ストアドプロシージャが返す結果セット(複数返る)</param>
/// <returns>「sp_help」ストアドプロシージャの戻り値(成功:「」、失敗:「」)</returns>
public int sp_help(string objectName, out DataSet ds)
{
  // ストアドプロシージャを指定する。
  this.SetSqlByCommand("sp_help", CommandType.StoredProcedure);
  // 引数パラメタを設定する。
  this.SetParameter("objname", objectName);
  // 戻り値パラメタを設定する。
  this.SetParameter("ret", null, null, -1, ParameterDirection.ReturnValue);
  // ストアドプロシージャを実行し、データリーダを戻す。
  SqlDataReader drd = (SqlDataReader)this.ExecSelect_DR();
  // 結果セットを取得する
  ds = new DataSet();
  // 結果セットは複数取得可能
  do
  {
    // DataTableを生成
    DataTable dt = new DataTable();
    // データリーダから結果をロード
    dt.Load(drd);
    // DataTableを格納
    ds.Tables.Add(dt);
  } while (!drd.IsClosed);
  // データリーダを閉じる。
  drd.Close();
  // ストアドプロシージャの戻り値パラメタの値を取得。
  return (int)((DamSqlSvr)this.GetDam()).DamSqlCommand.Parameters["ret"].Value;
}

図 4.4-1 ストアド プロシージャの実行サンプル(SqlClientでの例)

4.5 配列バインド(ODP.NET)の実行方法

本フレームワークでは ODP.NET でサポートされる「配列バインド [7]」の実行も可能です。

// 配列データを作成
object[] temp1 = new string[] { "aaa", "bbb", "ccc" };
object[] temp2 = new string[] { "aaa", "bbb", "ccc" };
object[] temp3 = new string[] { "aaa", "bbb", "ccc" };
// ODP.NETの配列バインドの場合は、ArrayBindCountを指定
((DamOraOdp)this._dam).DamOracleCommand.ArrayBindCount = temp.Length;
// 配列バインド(型情報が必要)
((BaseDam)this._dam).SetParameter("P1", temp1, OracleDbType.Varchar2);
((BaseDam)this._dam).SetParameter("P2", temp2, OracleDbType.Varchar2);
((BaseDam)this._dam).SetParameter("P3", temp3, OracleDbType.Varchar2);

図 4.5 配列バインド(ODP.NET)の実行サンプル

Dam の取得方法:B層では ((DamOraOdp)this.GetDam())、「Dao 集約クラス」では ((DamOraOdp)this.Dam)、「自作 Dao クラス」では ((DamOraOdp)this._dam)。「共通 Dao クラス」「自動生成 Dao クラス」で配列バインドを行う場合はカスタマイズが必要です。

4.6 共通 Dao の使用方法

汎用的な Dao クラスとして [Touryo].Infrastructure.Business.Dao 名前空間の「共通 Dao クラス」(CmnDao) を利用できます。主に参照系の処理を実行し、必要に応じてカスタマイズ可能です。ストアド プロシージャ (参照/更新) の実行も可能です。

  • SQL設定処理:SQLFileName、SQLText メソッド
  • SQLパラメタ設定処理:SetUserParameter、SetParameter、GetParameter、ClearParameters メソッド
  • クエリの実行:ExecSelectScalar、ExecSelectFill_DT、ExecSelectFill_DS、ExecSelect_DR、ExecInsUpDel_NonQuery メソッド
// 共通Daoで参照系SQLを実行
CmnDao cmnDao = new CmnDao(this.GetDam());
// 動的SQLを指定
cmnDao.SQLFileName = "ShipperSelect.xml";
// パラメタ ライズド クエリのパラメタに対して、動的に値を設定する。
cmnDao.SetParameter("P1", testParameter.ShipperID);
// 戻り値 dt
DataTable dt = new DataTable();
// 共通Daoを実行
cmnDao.ExecSelectFill_DT(dt);

図4.6‐1 共通Daoクラスの使用サンプル

// 共通Daoでストアドを実行
CmnDao cmnDaoSP = new CmnDao(this.GetDam());
// ストアドを指定
cmnDaoSP.SQLText = "sp_help";
// パラメタを指定する(入力、出力)。
cmnDaoSP.SetParameter("objname", "Shippers");
cmnDaoSP.SetParameter("ret", null, null, -1, ParameterDirection.ReturnValue);
// 共通Daoを実行
SqlDataReader drd = (SqlDataReader)cmnDaoSP.ExecSelect_DR();
// 結果セットを取得する
DataSet ds = new DataSet();
// 結果セットは複数取得可能
do {
  // DataTableを生成
  DataTable dt = new DataTable();
  // データリーダから結果をロード
  dt.Load(drd);
  // DataTableを格納
  ds.Tables.Add(dt);
} while (!drd.IsClosed);
// データリーダを閉じる。
drd.Close();
// 戻り値を取得
int i = (int)cmnDaoSP.GetParameter("ret");

図4.6‐2 共通Daoクラスの使用サンプル(ストアド)

4.7 性能を考慮した動的パラメタライズド・クエリの連続実行方法

動的パラメタライズド・クエリは XML 編集を伴うため、タグ数が多い場合 (100 タグ以上が目安) 性能が劣化することがあります。一度実行すると内部的に XML から SQL に変換されるため、この SQL を保持する Dam を再利用すれば連続実行の性能を向上できます。ただし一度実行すると Command オブジェクトにパラメタが残るため、Command.Parameters.Clear() でクリアしてから再設定する必要があります。「共通 Dao クラス」「自動生成 Dao クラス」では実行の度に SQL が再設定されるため、「自作 Dao クラス」で実行するか、SQL を再設定しないメソッドを追加実装する必要があります [8]。

object obj;
// 動的SQLを読み込む場合。
this.SetSqlByFile2("DaoShippers_D1_Insert.xml");
// この状態では動的SQL(IsDPQプロパティを確認)。
System.Diagnostics.Debug.WriteLine(this.GetDam().IsDPQ.ToString());

// 実行1回目
this.SetParameter("CompanyName", "1回目");
this.SetParameter("Phone", "1回目");
obj = this.ExecInsUpDel_NonQuery();
// 以降は静的SQLとなる(IsDPQプロパティを確認)。
System.Diagnostics.Debug.WriteLine(this.GetDam().IsDPQ.ToString());
// 前回の実行で設定したパラメタ(Command.Parameters)をクリア。
((DamSqlSvr)this.GetDam()).DamSqlCommand.Parameters.Clear();

// 実行2回目
this.SetParameter("CompanyName", "2回目");
this.SetParameter("Phone", "2回目");
obj = this.ExecInsUpDel_NonQuery();
// 前回の実行で設定したパラメタ(Command.Parameters)をクリア。
((DamSqlSvr)this.GetDam()).DamSqlCommand.Parameters.Clear();

// 実行3回目
// ・・・

図4.7 性能を考慮して動的パラメタライズド・クエリを連続実行する例

4.8 大量データ更新の実行方法

フレームワーク経由の実行は 1 件あたり約 0.5m 秒のオーバヘッドがあるため、必要に応じて Transact-SQL、ADO.NET 直実行、配列バインド、または INSERT/UPDATE のバッチ実行方式を検討します。これらをサポートするクラス・メソッドとして、Touryo.Infrastructure.Public.Db.SQLUtility クラス、Touryo.Infrastructure.Framework.Dao.BaseDao.ExecGenerateSQL メソッドを追加してあります (現状 SQL Server のみ、Oracle・HiRDB は配列バインドを使用)。

INSERT INTO XXXX(xxx, yyy, zzz) VALUES(xxx, yyy, zzz);
INSERT INTO XXXX(xxx, yyy, zzz) VALUES(xxx, yyy, zzz);
INSERT INTO XXXX(xxx, yyy, zzz) VALUES(xxx, yyy, zzz);
UPDATE SET xxx=xxx, yyy=yyy, zzz=zzz, WHERE id = 1;
UPDATE SET xxx=xxx, yyy=yyy, zzz=zzz, WHERE id = 2;
UPDATE SET xxx=xxx, yyy=yyy, zzz=zzz, WHERE id = 3;

図4.8 INSERT, UPDATEなどのSQLを連続して記述

INSERT ステートメントで複数の値を指定 - 松本崇博 Blog (SQL Server Tips) http://d.hatena.ne.jp/matu_tak/20100113/1263856222

INSERT INTO [t1]
VALUES
( 1, 'AAA' ),
( 2, 'BBB' ),
( 3, 'CCC' ),
( 4, 'DDD' )
  • 4.8.1 SQLUtility クラスGetInsertSQLPartsGetUpdateSQLParts メソッドでバッチ処理用の SQL パーツを生成できます。
// データテーブル作成
DataTable dt = new DataTable();
・・・データ作成コードは省略・・・

// SQLパーツの生成
// 第2・3引数は省略可能(第2の既定値はnvarchar)。
SQLUtility su = new SQLUtility(DbEnum.DBMSType.SQLServer, "varchar", "yyyy/MM/dd");
string[] strs = su.GetInsertSQLParts(dt);

// SQLパーツの組立
string collist = "";
StringBuilder sb = new StringBuilder();
foreach (string str in strs)
{
  if (string.IsNullOrEmpty(collist))
  {
    collist = str;
  }
  else
  {
    sb.Append(str + ",");
  }
}

// 最後のカンマを削る。
string temp = sb.ToString();
temp = temp.Substring(0, temp.Length - 1);

// 共通Daoでバッチ・インサート
CmnDao cd = new CmnDao(this.GetDam());
cd.SQLText = string.Format("INSERT INTO XXX{0} VALUES{1}", collist, temp);
cd.ExecInsUpDel_NonQuery();
INSERT INTO XXX
 ([aaa],[bbb],[ccc],[ddd],[eee],[fff])
VALUES
 (Convert(varchar,'a'),Convert(varchar,'aaa'),0xFF,0xFFFFFF,'2013/09/20',1),
 (Convert(varchar,'b'),Convert(varchar,'bbb'),0xFE,0xFEFEFE,'2013/09/20',2),
 (Convert(varchar,'c'),Convert(varchar,'ccc'),0xFD,0xFDFDFD,'2013/09/20',3)

図4.8.1‐1 SQLUtilityクラスの、GetInsertSQLPartsメソッドを使用したバッチ・インサートの例

// データテーブル作成
DataTable dt = new DataTable("XXX");
・・・データ作成コードは省略・・・
// SQLパーツの生成
// 第2・3引数は省略可能(第2の既定値はnvarchar)。
SQLUtility su = new SQLUtility(DbEnum.DBMSType.SQLServer, "varchar", "yyyy/MM/dd");
string[] strs = su.GetUpdateSQLParts(dt, new string[] { "aaa" });
// SQLパーツの組立
StringBuilder sb = new StringBuilder();
foreach (string str in strs)
{
  sb.Append("UPDATE " + dt.TableName + " " + str + ";");
}
// 共通Daoでバッチ・インサート
CmnDao cd = new CmnDao(this.GetDam());
cd.SQLText = sb.ToString();
cd.ExecInsUpDel_NonQuery();
UPDATE XXX
SET [bbb] = Convert(varchar,'aaa'),[ccc] = 0xFF,[ddd] = 0xFFFFFF,[eee] = '2013/09/20',[fff] = 1
WHERE [aaa] = Convert(varchar,'a');
UPDATE XXX
SET [bbb] = Convert(varchar,'bbb'),[ccc] = 0xFE,[ddd] = 0xFEFEFE,[eee] = '2013/09/20',[fff] = 2
WHERE [aaa] = Convert(varchar,'b');
UPDATE XXX
SET [bbb] = Convert(varchar,'ccc'),[ccc] = 0xFD,[ddd] = 0xFDFDFD,[eee] = '2013/09/20',[fff] = 3
 WHERE [aaa] = Convert(varchar,'c');

図4.8.1‐2 SQLUtilityクラスの、GetUpdateSQLPartsメソッドを使用したバッチ・アップデートの例

  • 4.8.2 ExecGenerateSQL メソッドBaseDam クラス [9] の ExecGenerateSQL メソッドで、SQL を実行しないで SQL のみ生成できます (詳細は RerunnableBatch_sample2 を参照)。
//Orders2テーブルに複数件まとめて追加する。
StringBuilder sb = new StringBuilder();
for (int index = 0; index < dataTable.Rows.Count; index++)
{
  DataRow row = dataTable.Rows[index]; //1件分のデータ
  //todo:編集処理など
  // ↓DBアクセス-----------------------------------------------------
  // 自動生成Daoを生成
  DaoOrders2 dao = new DaoOrders2(this.GetDam());
  // パラメータを設定
  dao.PK_OrderID = row["OrderID"];
  dao.CustomerID = row["CustomerID"];
  dao.EmployeeID = row["EmployeeID"];
  ・・・
  dao.ShipRegion = row["ShipRegion"];
  dao.ShipPostalCode = row["ShipPostalCode"];
  dao.ShipCountry = row["ShipCountry"];
  // 自動生成Daoを実行
  sb.Append(dao.ExecGenerateSQL(
  "DaoOrders2_S1_Insert.sql", new SQLUtility(DbEnum.DBMSType.SQLServer)) + ";\r\n");
  // ↑DBアクセス-----------------------------------------------------
}
// 共通Daoでバッチ・インサート
CmnDao cd = new CmnDao(this.GetDam());
cd.SQLText = sb.ToString();
cd.ExecInsUpDel_NonQuery();
INSERT INTO [Orders2]
 ( [OrderID], [CustomerID], [EmployeeID], [OrderDate], [RequiredDate], [ShippedDate], [ShipVia],
 [Freight], [ShipName], [ShipAddress], [ShipCity], [ShipRegion], [ShipPostalCode], [ShipCountry] )
VALUES
 ( 10248, Convert(nvarchar,'VINET'), 5, '1996/07/04 00:00:00.000', '1996/08/01 00:00:00.000', '1996/07/16 00:00:00.000',
 3, 32.3800, Convert(nvarchar,'Vins et alcools Chevalier'), Convert(nvarchar,'59 rue de l''Abbaye'),
 Convert(nvarchar,'xxxxxx'), NULL, Convert(nvarchar,'51100'), Convert(nvarchar,'France'));
INSERT INTO [Orders2]
 ( [OrderID], [CustomerID], [EmployeeID], [OrderDate], [RequiredDate], [ShippedDate], [ShipVia],
 [Freight], [ShipName], [ShipAddress], [ShipCity], [ShipRegion], [ShipPostalCode], [ShipCountry] )
VALUES
 ( 10249, Convert(nvarchar,'TOMSP'), 6, '1996/07/05 00:00:00.000', '1996/08/16 00:00:00.000', '1996/07/10 00:00:00.000',
 1, 11.6100, Convert(nvarchar,'Toms Spezialitäten'), Convert(nvarchar,'Luisenstr. 48'),
Convert(nvarchar,'Münster'), NULL,Convert(nvarchar,'44087'), Convert(nvarchar,'Germany'));
・・・

図4.8.2 BaseDamクラスの、ExecGenerateSQLメソッドを使用したバッチ・インサートの例

4.9 「初回例外」のエラー メッセージ

D層処理のデバッグ実行中などに 'System.Xml.XmlException' の初回例外が System.Xml.dll で発生しました。 と表示されることがあります。これは、動的パラメタライズド・クエリのチェック ロジック内で XML フォーマットに違反するコマンド (通常の SQL や静的パラメタライズド・クエリなど) が指定された場合に出力されるメッセージで、品質には問題ありません。

5. ASP.NET Mobile Web 開発

discon

6. ログの見方

6.1 ACCESS ログ

ACCESS ログは正常系・異常系の形式で出力されます。P層側でのスタック トレースの出力は B層のスタック トレース情報を含まないため、B層でエラーが発生した場合は B側のエラー情報のスタック トレースを確認します。

[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),-,(ScreenID)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),--->,(ScreenID),(controlId)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),--->>,(ScreenID),(controlId),(ActionType)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),<<---,(ScreenID),(controlId),(ActionType),(実行時間),(CPU 時間)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),<---,(ScreenID),(controlId),(実行時間),(CPU 時間)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),-,(ScreenID)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),--->,(ScreenID),(controlId)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),--->>,(ScreenID),(controlId),(ActionType)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),<<---,(ScreenID),(controlId),(ActionType),(実行時間),(CPU 時間)
[(ログ ヘッダー)],[INFO ],[1],(USER 名),(IP),<---,(ScreenID),(controlId),(実行時間),(CPU 時間)

図 6.1-1 ACCESS ログ(正常系)

[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),-,(ScreenID)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),--->,(ScreenID),(controlId)
[(ログ ヘッダー)],[INFO],[1],(USER 名),(IP),--->>,(ScreenID),(controlId),(ActionType)
[(ログ ヘッダー)],[ERROR],[1],(USER 名),(IP),<<---,(ScreenID),(controlId),(ActionType),(実行時間),(CPU 時間),
メッセージ ID, メッセージ
 場所 (スタックトレースの情報1) :行 xxx
 場所 (スタックトレースの情報2) :行 xxx
 場所 (スタックトレースの情報3) :行 xxx
 場所 (スタックトレースの情報・・・) :行 xxx
[(ログ ヘッダー)],[ERROR],[1],(USER 名),(IP),<---,(ScreenID),(controlId),(ActionType),(実行時間),(CPU 時間),
メッセージ ID, メッセージ
 場所 (スタックトレースの情報1) :行 xxx
 場所 (スタックトレースの情報2) :行 xxx
 場所 (スタックトレースの情報3) :行 xxx
 場所 (スタックトレースの情報・・・)

図 6.1-2 ACCESS ログ(異常系)

6.2 SQL トレース ログ

SQL トレース ログも正常系・異常系の形式で出力されます。エラー発生時のログも出力されるためデバッグに活用でき、DBMS に投げている SQL に問題がないか確認にも利用できます。

  • 正常系
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:INSERT INTO Shippers (CompanyName, Phone) VALUES (@P2, @P3),[commandParameter]:P2=@@@,P3=@@@,
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:SELECT COUNT(*) FROM Shippers,[commandParameter]:
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:SELECT * FROM Shippers,[commandParameter]:
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:SELECT ShipperID, CompanyName, Phone FROM Shippers WHERE ShipperID = @P1,[commandParameter]:P1=6,
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:UPDATE Shippers SET CompanyName = @P2, Phone = @P3 WHERE ShipperID = @P1,[commandParameter]:P1=6,P2=%%%,P3=%%%,
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:SELECT * FROM Shippers,[commandParameter]:
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:DELETE Shippers WHERE ShipperID = @P1,[commandParameter]:P1=6,
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(〇)正常終了],
[commandText]:SELECT * FROM Shippers,[commandParameter]:

図6.2-1 SQLトレース ログ(正常系)

  • 異常系
[(ログ ヘッダー)],[INFO ],[1],(実行時間),(CPU 時間),[(×)異常終了],
[commandText]:INSERT INTO Shippers (CompanyName, Phone) VALUES (@P2, @P3),[commandParameter]:P2=;XXX,P3=XXX,

図6.2-2 SQLトレース ログ(異常系)

SQL ファイルとログの対応をとる場合、SQL コメントに SQL ファイル名を入力しておくと良いでしょう。

7. FAQ

以下、要件毎の対応方法や、問い合わせ対応を FAQ 化したものです。Open 棟梁は柔軟性が高く、殆どの要件に対応可能です。

7.1 P層フレームワーク

7.1.1 ASP.NET

質問 回答
複数画面対応の Web アプリケーションに対応しているか? どのようなパターンでも対応可能。複数画面対応の開発を支援する機能を活用できる。
クロス ブラウザに対応しているか? IE6/7/8、Firefox、Safari、Chrome、Opera で動作確認。制限事項:業務モーダル ダイアログの表示のみ IE 限定 (OK、Yes/No メッセージ ダイアログはクロス ブラウザ対応、ただし Opera はダイアログ未対応)。LinkButton、ImageMap など href の __doPostBack() を使うコントロールで二重送信防止機能が IE6 のみ有効にならない。
Internet 系で Session タイムアウト時も処理を続行させたい。 Session タイムアウト検出機能と、Session を継続的に使用するフレームワーク機能 (セッション領域の自動削除、ボタン履歴情報記録、不正操作防止) を全て OFF にすることで対応できる。
負荷分散クラスタでフェイル オーバー後、業務続行可能か? SQL Server/Oracle いずれかの Session モードを選択し、専用の Session 状態保持用サーバを新設すれば可能 (二重化できない StateServer は選択肢から除外)。
マスタ ページの利用は必須か? 基盤の定義をマスタ ページに持つため必須。ただし基盤の定義以外は空のマスタ ページでも問題ない。
マスタ ページのネストをサポートしているか? 最新バージョンでサポート。
P層イベント処理機能の対応コントロールを追加したい。 コントロールのプレフィックスでハンドルするようになっており、ベースクラス2のコントロール取得処理や中継イベント ハンドラをカスタマイズすることで追加できる。
P層イベント処理機能をキャンセルしたい。 *.config に指定するコントロール毎のプレフィックスを空に指定すればキャンセルできる。
ログイン画面で Session タイムアウト例外が発生する。 P層フレームワークは Session を必要とするため、Internet 環境下のログイン画面には使用しないか、Windows 認証や専用認証基盤 (SiteMinder 等) を利用する。IsNoSession フラグを true にすると当該画面でボタン履歴記録・不正操作防止を OFF にできる。
ダウンロード関連 (#10〜14) 2.7 節参照。
OK、Yes/No ダイアログのメッセージに改行が入らない。 メッセージ文字列に含まれる改行コードを <br/> に変更する。
HTML のタイトルは何処で指定しているか? ベースクラス2の CMN_FormInit メソッドで this.Page.Title に設定。
Ajax、jQuery のタブ切り替えがうまく動かない。 ViewState が無いためポストバックの都度、初期化処理が必要。初期化処理から Fx_Document_OnLoad()SetTimeout() で時間差を付けて呼び出すなどの対策が必要 (最新版では対応済み)。
P層イベント処理機能は Web ユーザ コントロールをサポートしているか? 最新版でサポート。ページ側と Web ユーザ コントロール/マスタ ページのコントロール名称が衝突しないようプレフィックス追加などを検討する。
予期せぬ Session タイムアウト例外が発生する。 Session.Abandon() に加え、Session タイムアウト検出用 Cookie の削除も併せて行う (最新版では this.FxSessionAbandon() に集約)。
ASP.NET4.0 の新しい HTML 出力に対応しているか? web.config に <pages controlRenderingCompatibilityVersion="3.5" /> を付与するか、common.js を同梱の別のものに差し替える。clientIDMode="AutoID" が前提。
ASP.NET MVC に対応しているか? 対応していない。P層以外の B層・D層フレームワークや通信制御機能などは利用可能。

7.1.2 Windows Forms

質問 回答
リッチクライアントに対応しているか? Windows Forms、WPF/XBAP、Silverlight、Windows ストアアプリに対応。このうち P層フレームワークは Windows Forms のみサポート。
Windows Forms の P層でイベント処理対応コントロール・イベントが多くなり過ぎる。 ベースクラス2のカスタマイズで追加可能だがカスタマイズ量が多くなるため、.NET 標準のイベントハンドラから隠しボタン (HiddenButton) の DoClick メソッドで Click イベントを発生させることも可能。
MenuItem コントロール・イベントは P層イベント処理機能にサポートしているか? サポート。検索方法が他と異なるためベースクラス2をカスタマイズせず、UOC_FormInit で MenuItem の Click イベントに共通 Click イベント ハンドラ (Item_Click) を設定する。

7.2 B層フレームワーク

質問 回答
1 クラス 1 メソッドになってしまわないか? 既定の入口は DoBusiness() の 1 つだが、内部で if/select 振り分けを行うか、レイトバインド機能による振り分け機能付きベースクラス2を利用する (現在は振り分け機能付きが既定方式)。
B、D層を設けるのが面倒。 P層で Dam を直接生成してデータアクセスすることも可能 (P層のみ or P・D層)。D層に共通 Dao/自動生成 Dao を使用すれば自作 Dao の開発を割愛できる。
トランザクション管理の実装は? B層ベースクラス2の UOC_ConnectionOpen() でトランザクションを開始し、B層処理完了時にフレームワーク側で自動的にコミット or ロールバックする (例外発生時は自動ロールバック)。2 層 C/S では開始のみ自動化。
分割コミットなどは可能か? this.GetDam().CommitTransaction(); this.GetDam().BeginTransaction(); を B層から呼び出すことで可能。
手動のトランザクション管理は可能か? Dam を直接生成してデータアクセスすることで可能。2C/S 用フレームワークでは 2 本目の接続が可能。

7.3 D層フレームワーク

質問 回答
バインド変数へのバインド方法は? 名前バインドにのみ対応。順番バインドはサポートしない。OLEDB、ODBC、HiRDB では、コメント中にパラメタ記号を含むパラメタ名と同じ文字列があると正しく動作しない。
ストアド プロシージャ、無名 PL/SQL ブロックの実行や戻り値の取得は可能か? 最新版では動的パラメタライズド・クエリ機能を使用できる。
ODP.NET、HiRDB の配列バインドをサポートしているか? サポート。
LINQ to SQL/Oracle/Entities をサポートしているか? サポートしない。D層フレームワークの使用を推奨 (LINQ to Object/XML/DataSet など DB アクセス以外の LINQ は問題ない)。
IN 句に複数のパラメタを指定したい。 動的パラメタライズド・クエリの LIST タグを使用 (SetUserParameter() による文字列置換でも代替可能)。
検索条件の IN 句 (副問合せ) を動的化したいがエラーになる。 IF タグではなく、タグのネストが可能な SUB タグを使用する。
デッドロック、ロックタイムアウト、キー重複などの例外をリトライしたい。 B層ベースクラス2の例外処理をカスタマイズし、リトライ対象例外を業務例外に振り替えて P層に正常系の戻り値を戻す。
コマンド タイムアウト値を設定するには? 最新版では共通のコマンド タイムアウト値を *.config に設定できる。
SQL で暗黙の型変換が発生し性能劣化する。 SetParameter() のオーバーロードで型・サイズを指定可能。SQL 定義ファイル中に型キャストを明示することでも対応可能 (4.2 節参照)。
XML ファイルのサイズなどにより性能が劣化しないか? 1 つの XML に 150 タグ以上あると性能劣化が始まるため不要なタグは記述しない。バッチ更新はラウンドトリップ軽減のため配列バインドを併用する。PARAM/DIV タグは性能劣化の原因にならない。
Like 句を使用した曖昧検索をサポートしているか? サポート。パラメタにワイルドカードを含めた Like の検索条件を渡す (D層自動生成ツールの生成物は非対応)。
パラメタライズド・クエリの初歩 Like 検索は標準・動的パラメタライズド・クエリでサポート。null を渡したい場合は DBNull を使用する (動的では null は特殊な制御パラメタ)。テキスト内パラメタとタグ内パラメタが混在する場合はテキスト内パラメタが優先される。

7.4 各機能(通信制御)

質問 回答
プロキシ経由やプロキシ認証をサポートしているか? サポート。プロキシへの URL や Credentials を XML 定義ファイルに指定可能 (API から直接指定も可能)。
Windows 認証を使用した SSO をサポートしているか? サポート。ケルベロス認証をサポートし、ダブル〜トリプル ホップも可能 (ベース クライアント セキュリティ モデルは通常推奨しない)。
WCF-TCP/IP などの通信プロトコルはサポートしているか? サポート。サービス インターフェイス、サービス プロキシを追加開発することで対応可能。

7.5 その他

質問 回答
ログ出力機能で 1 系 ⇔ 2 系のローリングを実現可能か? log4net の類似機能 (MaxSizeRollBackups、CountDirection) で代替する。
ログ出力でユーザ毎に異なるファイル出力が可能か? リッチクライアント (特にターミナルサービス) では、ファイル出力のアペンダ設定に <param name="File" value="${USERPROFILE}\Log\ACCESS" /> と記述する。
同一プロセス毎に多重起動に対応する方法は? 環境変数や PID などの変数を利用する (log4net.Util.PatternString%processid など)。
リッチクライアントの定義ファイルの保存先にユーザ毎に異なるディレクトリを指定可能か? 環境変数を使用してデータ保存先を選択できる (リソースローダ部品のパスに %USERPROFILE%\AppData を指定)。
ASP.NET の web.config/app.config への変更の反映タイミングは? web.config は変更されると自動的に IIS が再起動し直ちに反映。app.config は IIS が再起動しないため iisreset コマンドで手動反映する。
Forms 認証ログイン前に JS ファイルなどを参照できない。 web.config に <location path="Framework/Js"><allow users="*"/> を追加し、JavaScript ファイルを認証対象外にする。
.NET Framework Client Profile で動作&コンパイルできない。 Client Profile はサポートしない (存在しないサーバサイド API を多数使用しているため。Client Profile は 4.5 でドロップされた)。

脚注

  1. インテリジェンスな D層 (Dao) クラス。
  2. Session タイムアウト検出用 Cookie を削除する。
  3. 「パラメタのデータ型」(nvarchar) と「DB の列のデータ型」(varchar) が不一致。
  4. SQL Server の Transact-SQL の変換関数。
  5. DataSet/DataTable の AcceptChanges メソッド。
  6. 楽観排他に利用するオリジナル データを取得する。
  7. ODP.NET でサポートされる配列バインド。
  8. 共通 Dao/自動生成 Dao (テンプレート) を修正する。
  9. BaseDam クラス。

-以上-

Clone this wiki locally