Visual Studio User Group

   ホーム      
 | 
  
   イベント      
 | 
  
   フォーラム      
 | 
  
   VSUG キャスト      
 | 
  
   記事      
 | 
  
   About VSUG   
会員サービス
メンバー ログイン
新規会員登録はこちら
Menu
ホーム
イベント
VSUG アカデミー
VSUG Day
Bar VSUG
過去のイベント
フォーラム
初心者フォーラム
Visual Studio
.NET 開発
プラットフォーム運用
フリートーク・お知らせ
フォーラムの利用案内
VSUG キャスト
第 1 回 近藤 和彦 氏
第 2 回 長沢 智治 氏
記事
連載
コラム
About VSUG
VSUGとは
フォーラムリーダー紹介
ガジェット
スタッフ日記

 

2009.4.23
WINGS プロジェクト かるあ(著)/山田祥寛(監修)(http://www.wings.msn.to/)   【連載 - 目次】

 

前回はカスタムクラスを ObjectDataSource に設定するため、ADO.NET の基礎を学びました。今回は前回作成したクラスに更に更新メソッドを追加し ObjectDataSource に割り当てる方法を解説します。

 

● 更新メソッドの作成
カスタムオブジェクトで検索用のメソッドを作成する場合、更新用のすべてのパラメータを受けるメソッドか、更新用のインスタンスを受けるようなメソッドを定義する必要があります。
今回の場合、次のいずれかのパラメータを受け取る必要があるということです。

 

・void Save(VSUG アカデミー newObject)
・void Save(string Id, string タイトル, DateTime 開催日, string セッション1, string セッション2)


更新の際に同時実行制御をおこないたい場合は、更新した値と読みだした時の値を比べることで、ほかのユーザーが同じデータを更新していないことを確認する必要があります。データソースコントロールでは後述する「更新以前の値(original_)」を使用して同時事項制御を行います。
このため更新メソッドは更新した値とは別に「更新以前の値(original_)」を引数として受け取れるようにする必要があります。更新のためのメソッドは次のような引数のリストが必要になります。


public Save(string Id,
            string タイトル,
            DateTime 開催日,
            string セッション1,
            string セッション2,
            string original_Id,
            string original_タイトル,
            DateTime original_開催日,
            string original_セッション1,
            string original_セッション2,
            object original_Updated);

 

今回はパラメータが多いので、[リスト 00160] の処理を切り出して簡単にパラメータを追加するためのユーティリティメソッドを先に追加しておきましょう。

 

[リスト 0018] 簡単にパラメータを追加するメソッド
Public Function AddInParameter(ByVal command As DbCommand, ByVal name As String, ByVal value As Object) As DbParameter
    Dim parameter = command.CreateParameter()
    parameter.ParameterName = name
    parameter.Direction = Data.ParameterDirection.Input
    parameter.Value = value
    command.Parameters.Add(parameter)
    Return parameter
End Function
-------------------------------------------------------------------------------
public DbParameter AddInParameter(DbCommand command , string name, object value)
{
    var parameter = command.CreateParameter();
    parameter.ParameterName = name;
    parameter.Direction = ParameterDirection.Input;
    parameter.Value = value;
    command.Parameters.Add(parameter);

    return parameter;
}

 

次に Save メソッドの実装をみていきます。
処理の手順は GetDataById メソッドとほぼ同じです。GetDataByIdメソッドでは検索した結果を command.ExecuteReader メソッドで取得した Reader で読みだしていたのに対して、Save メソッドでは、command.ExecuteQuery メソッドで処理を実行しています。

 

[リスト 0019] 更新メソッド
Public Sub Save(ByVal Id As String,  _
                ByVal タイトル As String, _
                ByVal 開催日 As DateTime, _
                ByVal セッション1 As String, _
                ByVal セッション2 As String, _
                ByVal original_Id As String, _
                ByVal original_タイトル As String, _
                ByVal original_開催日 As DateTime, _
                ByVal original_セッション1 As String, _
                ByVal original_セッション2 As String, _
                ByVal original_Updated As Object)

  Const sql As String = "UPDATE [VSUG アカデミー] SET [タイトル] = @ タイトル, [開催日] = @ 開催日, [セッション1] = @ セッション1, [セッション2] = @ セッション2 WHERE [Id] = @original_Id AND [Updated] = @original_Updated"

  Dim setting = ConfigurationManager.ConnectionStrings("サンプルデータベースConnectionString")
  Dim factory = DbProviderFactories.GetFactory(setting.ProviderName)
  Using connection = factory.CreateConnection
    connection.ConnectionString = setting.ConnectionString

    Using command = connection.CreateCommand()
      command.CommandText = sql
      AddInParameter(command, "タイトル", タイトル)
      AddInParameter(command, "開催日", 開催日)
      AddInParameter(command, "セッション1", If(セッション1 Is Nothing, DBNull.Value, DirectCast(セッション1, Object)))
      AddInParameter(command, "セッション2", If(セッション2 Is Nothing, DBNull.Value, DirectCast(セッション2, Object)))
      AddInParameter(command, "original_Id", original_Id)
      AddInParameter(command, "original_Updated", original_Updated)

      connection.Open()
      command.ExecuteNonQuery()
      connection.Close()
    End Using
  End Using
End Sub
-------------------------------------------------------------------------------

public void Save(string Id,
                 string タイトル,
                 DateTime 開催日,
                 string セッション1,
                 string セッション2,
                 string original_Id,
                 string original_タイトル,
                 DateTime original_開催日,
                 string original_セッション1,
                 string original_セッション2,
                 object original_Updated)
{
  const string sql = "UPDATE [VSUG アカデミー] SET [タイトル] = @ タイトル, [開催日] = @ 開催日, [セッション1] = @ セッション1, [セッション2] = @ セッション2 WHERE [Id] = @original_Id AND [Updated] = @original_Updated";

  var setting = ConfigurationManager.ConnectionStrings["サンプルデータベースConnectionString"];
  var factory = DbProviderFactories.GetFactory(setting.ProviderName);
  using (var connection = factory.CreateConnection())
  {
    connection.ConnectionString = setting.ConnectionString;

    using (var command = connection.CreateCommand())
    {
      command.CommandText = sql;
      AddInParameter(command, "タイトル", タイトル);
      AddInParameter(command, "開催日", 開催日);
      AddInParameter(command, "セッション1", ((セッション1 == null) ? DBNull.Value: (Object)セッション1));
      AddInParameter(command, "セッション2", ((セッション2 == null) ? DBNull.Value: (object)セッション2));
      AddInParameter(command, "original_Id", original_Id);
      AddInParameter(command, "original_Updated", original_Updated);

      connection.Open();
      command.ExecuteNonQuery();
      connection.Close();
    }
  }
}

 

更新時にも DBNull が厄介な問題です。
.NET Framework の null(Nothing) のままパラメータを作成すると、パラメータ自体の定義が消えてしまい、クエリの実行時に例外が発生してしまいます。データベースの NULL としてパラメータを作成する場合は、検索の逆の手順で、null(Nothing) を DBNull.Value に変換します。

 

● 同時実行制御
複数人が同時に同じデータを更新可能性がある場合は同時実行制御を行う必要があります。ADO.NET で標準サポートされている同時実行制御はオプティミックス同時実行制御(楽観的同時実行制御)と呼ばれるデータベースの更新チェック方法です。
例えばユーザー A が DetailsView にデータを表示して更新するまでの間にユーザー B がデータを更新した場合に、ユーザー B の更新内容をユーザー A の更新内容で上書きしてしまうのを防ぐ実行制御の方法です。

 

【データの競合】


今回のメソッドではオプティミックス同時実行制御を行うためにタイムスタンプ列を使用して Where 句を構築しています。タイムスタンプ列は行が更新された際に自動的に SQLServer が更新する列で、データベース内で一意の値が格納されます。つまりタイムスタンプ列は更新前の値と更新後の値は必ず異なる値が格納されるのです。

この特性を利用すると、更新時に更新件数を取得して 0 件だったら競合が発生、そうでない場合は更新が成功したと見なす事ができます。
更新時のコンフリクトの判断は[リスト 0021]のように SqlDataSource の Updated イベントで判断を行います。

 

[リスト 0021] コンフリクトの判断
Protected Sub SqlDataSource2_Updated _
    (ByVal sender As Object, _
     ByVal e As SqlDataSourceStatusEventArgs) _
     Handles SqlDataSource2.Updated

    If e.AffectedRows = 0 Then
        Response.Write("このレコードは他のユーザーによって更新されています。")
    End If
End Sub
-------------------------------------------------------------------------------
protected void SqlDataSource2_Updated
    (object sender, SqlDataSourceStatusEventArgs e)
{
    if (e.AffectedRows == 0)
    {
        Response.Write("このレコードは他のユーザーによって更新されています。");
    }
}

 

Updated イベントハンドラ の第2引数は更新後のクエリの情報が格納されています。
この中の AffectedRows プロパティは更新の結果何件を処理したかを表しています。
今回の場合、値が一つでも変更になっていると[リスト 0003]の SQL だと一件も処理されないため、すでに更新されていたり、削除されていると処理結果が 0 件になるということです。

 

○ original パラメータ
前述の同時実行制御に使用している original_xxxx と言うパラメータですが、これは何者でしょうか。その答えはデータソースの定義に隠れています。ASPX に定義されたデータソースの定義を確認します。[リスト 0022] を見てください。

 

[リスト 0022] original_ パラメータ
<asp:ObjectDataSource ID="ObjectDataSource2" runat="server"
    ConflictDetection="CompareAllValues"
    OldValuesParameterFormatString="original_{0}"


OldValuesParamaterFormatString の値を確認してください。この値は更新前データの変数名を表します。つまり original_{0} は original_タイトル という形で ViewState の中に隠され、同時実行制御の際に変更されたデータが存在しないかをチェックするために使用されます。

 

● データソースへの関連付け
作成したデータアクセスコンポーネントをデータソースに関連付けます。
ほぼ今までのウィザードの作業と同じですが、一部チェックボックスをつけたり、コードを修正する必要があります。


1.データソースの構成変更
スマートタグから、データソースの構成変更を選択します。


【データソースの構成変更】


 

2.カスタムオブジェクトを選択
デフォルトの状態では、DataSetのオブジェクトしか表示されていないので、「データコンポーネントのみを表示」のチェックを外し、VSUG アカデミー DAC を選択します。


【カスタムオブジェクトを選択】


 
3.検索メソッドの定義
検索用のメソッドに、作成した GetDataById メソッドを関連付けます。


【検索メソッドの定義】

 
4.更新メソッドの定義
更新用のメソッドに、作成した Save メソッドを関連付けます。


【更新メソッドの定義】


 
5.パラメータの定義
今までと同じように、GridView の選択に連動して DetailsView が検索されるように設定します。


【パラメータの定義】

 
6.DetailsView の変更を有効にする
DetailsView で更新ボタンを表示するために、スマートタグから更新をチェックします。

 

【DetailsView の変更を有効にする】


 
7.データソースの変更通知を ON にする
同時実行制御を有効にするため、ObjectDataSource の ConflictDetection プロパティを CompareAllValues に変更します。これによって同時実行制御が有効になり、メソッドに original パラメータを引き渡すことができるようになります。

 

[リスト 0021] ObjectDataSource で同時実行制御を有効にする
<asp:ObjectDataSource ID="ObjectDataSource2" runat="server"
    ConflictDetection="CompareAllValues" OldValuesParameterFormatString="original_{0}"
    TypeName="VSUGアカデミーDAC" SelectMethod="GetDataById"  UpdateMethod="Save" >


ConflictDetection のデフォルトは OverwriteChanges であとから更新したレコードが有効になるように設定されています。

 

8.TimeStamp をメソッドに引き渡す
DetailsView では、Fields のコレクションに含まれるものだけが、データアクセスコンポーネントのパラメータとして渡されます。このため、ウィザードで作成した状態では TimeStamp(Updated) 列が Fields コレクションに存在しないため、データアクセスコンポーネントに渡されません。
Updated を渡すためには、Fields コレクションに Updated を追加し、DataKeyNames に Updated を追加します(ただし、Updated列は非表示にします)。

 

[リスト 0022] DetailsView の更新時に TimeStamp 列を更新メソッドに受け渡す
<!-- DataKeyNames と Updated 用の BoundFiled を追加 -->
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"
  DataSourceID="ObjectDataSource2" Height="50px" Width="125px"
  DataKeyNames="Id, Updated">
  <Fields>
    <asp:BoundField DataField="Id" HeaderText="Id" SortExpression="Id" />
    <asp:BoundField DataField="タイトル" HeaderText="タイトル" SortExpression="タイトル" />
    <asp:BoundField DataField="開催日" HeaderText="開催日" SortExpression="開催日" />
    <asp:BoundField DataField="セッション1" HeaderText="セッション1"
      SortExpression="セッション1" />
    <asp:BoundField DataField="セッション2" HeaderText="セッション2"
      SortExpression="セッション2" />
    <asp:BoundField DataField="Updated" Visible="false" />
    <asp:CommandField ShowEditButton="True" />
  </Fields>
</asp:DetailsView>

 

DataKeyNamesプロパティは DetailsView の主キーフィールドを表すプロパティですが、非表示項目をデータアクセスコンポーネントに引き渡す際にも利用できます。

更新結果はテーブルアダプターを使用した時と同じなのでここでは掲載しませんが、テーブルアダプターと比べるとちょっと大変ですね。ただ、カスタムでデータアクセスコンポーネントにを作成した場合は、テーブルアダプターに比べカスタムのコードを入れやすいメリットがあります。業務システムで独自のフレームワークを構築している場合などや、テーブルアダプターが対応していない RDB や Web サービスなどからデータを取得する場合にも適用することができます。

 

● まとめ
データソースコントロールに触れてきました。宣言型のプログラミングは今までのプログラミングと違い最初は戸惑うかもしれません。ただ、Visual Studio のウィザードと宣言型プログラミングの相性はとても良く、アプリケーションの生産性を大幅にあげることができます。
今後 LINQ や XAML、モデリング言語など宣言的なプログラミングはどんどん増えていきます。まずはなじみの多いデータソースコントロールで宣言的なプログラミングに触れてみてはどうでしょうか。

ログイン | © Visual Studio User Group. All rights reserved. 著作権について | プライバシーポリシー | リンクポリシー | お問合せ