WebSurfer's Home

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

SqlDataSource とトランザクション

by WebSurfer 2010年11月16日 22:52

SqlDataSource + DetailsView による更新が成功したら、変更されたフィールド名、更新前/後の値、変更日時をログとして SQL Server データベースに残す処置をトランザクションを切って行う方法です。

更新前/後の値の取得と、SqlDataSorce コントロールによる UPDATE 操作と ADO.NET のコードによる INSERT 操作のトランザクションをどのように設定するかがポイントだと思います。

まず、更新前/後の値ですが、これらは DetailsView.ItemUpdating イベントのハンドラにおいて、引数の DetailsViewUpdateEventArgs オブジェクトから Keys, OldValues, NewValues プロパティによって取得できます。

トランザクションの開始および SqlTranscation オブジェクトの取得、SqlCommand.Transaction プロパティのへの SqlTranscation オブジェクトの設定は、SqlDataSource.Updating イベントで行います。イベントハンドラの引数 SqlDataSourceCommandEventArgs から SqlConnection, SqlCommand オブジェクトへの参照を取得できますので、それらを用いて設定します。

ログを残すための INSERT 操作は、SqlDataSource.Updated イベントのハンドラで行います。イベントハンドラの引数 SqlDataSourceStatusEventArgs オブジェクトの Exception, AffectedRows プロパティを用いて、UPDATE 操作で例外が発生しなかったことと更新が行われたことが判定できます。

例外の発生がなく更新が行われたと判定されたら、ログを残すための操作(データベースへの INSERT 操作)を行います。INSERT 操作は、更新されたフィールドが複数の場合、同じ数だけ行うことになります。 INSERT 操作の途中で失敗した場合は、SqlDataSource による UPDATE 操作まで巻き戻すことになります。

そのために、引数 SqlDataSourceStatusEventArgs オブジェクトから、SqlDataSource が UPDATE 操作に用いた SqlConnection, SqlCommand, SqlTransaction オブジェクトへの参照を取得し、それらを用いて、SqlDataSource が行う UPDATE 操作と同じコネクション/トランザクションで INSERT 操作を行うように設定します。

サンプルコードは以下のとおりです。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  IOrderedDictionary keys;
  IOrderedDictionary oldValues;
  IOrderedDictionary newValues;
    
  protected void SqlDataSource2_Updating(object sender, 
    SqlDataSourceCommandEventArgs e)
  {
    SqlCommand command = (SqlCommand)e.Command;
    SqlConnection connection = command.Connection;
    connection.Open();
    SqlTransaction tx = connection.BeginTransaction();
    command.Transaction = tx;
  }
    
  protected void SqlDataSource2_Updated(object sender, 
    SqlDataSourceStatusEventArgs e)
  {
    SqlCommand command = (SqlCommand)e.Command;
    SqlConnection connection = command.Connection;
    SqlTransaction tx = command.Transaction;
    if (e.Exception == null && e.AffectedRows == 1)
    {
      string insertQuery = 
        "INSERT INTO History " + 
        "(FieldName, NewValue, OldValue, DateAndTime) " +
        "VALUES (@FieldName, @NewValue, @OldValue, @DateAndTime)";
      SqlCommand sqlCom = 
       new SqlCommand(insertQuery, (SqlConnection)connection);
      sqlCom.Transaction = (SqlTransaction)tx;
      sqlCom.Parameters.Add("@FieldName", SqlDbType.NVarChar);
      sqlCom.Parameters.Add("@NewValue", SqlDbType.NVarChar);
      sqlCom.Parameters.Add("@OldValue", SqlDbType.NVarChar);
      sqlCom.Parameters.Add("@DateAndTime", SqlDbType.DateTime);
      try
      {
        foreach (DictionaryEntry entry in oldValues)
        {
          if ((string)entry.Value != 
            (string)newValues[entry.Key])
          {
            sqlCom.Parameters["@FieldName"].Value = 
              (string)entry.Key;
            if (newValues[entry.Key] == null)
            {
              sqlCom.Parameters["@NewValue"].Value = DBNull.Value;
            }
            else
            {
              sqlCom.Parameters["@NewValue"].Value = 
                (string)newValues[entry.Key];
            }
            if (entry.Value == null)
            {
              sqlCom.Parameters["@OldValue"].Value = DBNull.Value;
            }
            else
            {
              sqlCom.Parameters["@OldValue"].Value = 
                (string)entry.Value;
            }
            sqlCom.Parameters["@DateAndTime"].Value = DateTime.Now;
            sqlCom.ExecuteNonQuery();
          }
        }
        tx.Commit();
      }
      catch (Exception)
      {
        if (tx != null)
        {
          tx.Rollback();
        }
        throw;
      }
      finally
      {
        connection.Close();
      }            
    }
    else
    {
      if (tx != null)
      {
        tx.Rollback();
      }
      connection.Close();
    }

    DropDownList1.DataBind();
  }

  protected void DetailsView1_ItemUpdating(object sender, 
    DetailsViewUpdateEventArgs e)
  {
    keys = e.Keys;
    oldValues = e.OldValues;
    newValues = e.NewValues;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:MyDB %>" 
      SelectCommand="SELECT [id], [name] FROM [table]">
    </asp:SqlDataSource>
    <asp:DropDownList ID="DropDownList1" 
      runat="server" 
      DataSourceID="SqlDataSource1" 
      DataTextField="name" 
      DataValueField="id" 
      AutoPostBack="True">
    </asp:DropDownList>
    <hr />
    <asp:SqlDataSource ID="SqlDataSource2" runat="server" 
      ConnectionString="<%$ ConnectionStrings:MyDB %>" 
      SelectCommand=
        "SELECT [id], [name], [price], [memo] 
        FROM [table] 
        WHERE ([id] = @id)"             
      UpdateCommand=
        "UPDATE [table] 
        SET [name] = @name, [price] = @price, [memo] = @memo 
        WHERE [id] = @original_id" 
      OnUpdated="SqlDataSource2_Updated" 
      OnUpdating="SqlDataSource2_Updating" 
      OldValuesParameterFormatString="original_{0}">
      <SelectParameters>
        <asp:ControlParameter ControlID="DropDownList1" 
          Name="id" 
          PropertyName="SelectedValue" 
          Type="Int32" />
      </SelectParameters>
      <UpdateParameters>
        <asp:Parameter Name="name" Type="String" />
        <asp:Parameter Name="price" Type="Decimal" />
        <asp:Parameter Name="memo" Type="String" />
        <asp:Parameter Name="original_id" Type="Int32" />
      </UpdateParameters>
    </asp:SqlDataSource>
    <asp:DetailsView ID="DetailsView1" 
      runat="server" 
      AutoGenerateRows="False" 
      DataKeyNames="id" 
      DataSourceID="SqlDataSource2" 
      OnItemUpdating="DetailsView1_ItemUpdating" 
      EnableModelValidation="True">
      <Fields>
        <asp:BoundField DataField="id" 
          HeaderText="id" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="id" />
        <asp:BoundField DataField="name" 
          HeaderText="name" 
          SortExpression="name" />
        <asp:BoundField DataField="price" 
          HeaderText="price" 
          SortExpression="price" />
        <asp:BoundField DataField="memo" 
          HeaderText="memo" 
          SortExpression="memo" />
        <asp:CommandField ShowEditButton="True" />
      </Fields>
    </asp:DetailsView>    
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

CheckBox 付き Calendar コントロール

by WebSurfer 2010年11月14日 17:42
CheckBox 付き Calendar コントロール

Calendar コントロールに CheckBox を配置して、それにチェックを入れた日付をポストバックしてサーバー側で取得するサンプルです。

CheckBox にチェックを入れる/外すたびにポストバックして、チェックを入れた/外した日付を ViewState に追加/削除し、Show selected dates ボタンクリックでチェックが入っている日付を Label に表示します。

「チェックを入れる/外すたびにポストバック」というのがちょっとわずらわしい感じですね。(汗)

でも、月をまたがって日付にチェックを入れる(例えば、1 月 15 日と 3 月 15 日にチェックを入れる)場合も対応できるようにするには、この方法しか思いつかないです。

チェックを入れる/外すたびにいちいちポストバックしないでもすむ方法を考え付いたら、別途記事を書きます・・・が、期待はしないでください。(笑)

2013/12/12 追記:今さらながらですが、チェックを入れる/外すたびにいちいちポストバックしないでもすむサンプルも作ってみました。CheckBox 付き Calendar(その2)を見てください。月を移動する時、ちょっと(かなり?)重い感じです。(汗)

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Implements Interface="System.Web.UI.IPostBackEventHandler" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  protected List<DateTime> checkedDates = new List<DateTime>();

  protected void Calendar1_DayRender(object sender, DayRenderEventArgs e)
  {
    CheckBox cb = new CheckBox();
    cb.ID = e.Day.Date.ToShortDateString();        
    string script =
      Page.ClientScript.GetPostBackEventReference(this, cb.ID);
    cb.Attributes.Add("onclick", script);
    foreach (DateTime day in checkedDates)
    {
      if (e.Day.Date == day)
      {
        cb.Checked = true;
        break;
      }
      cb.Checked = false;
    }
    e.Cell.Controls.Add(cb);
  }
    
  public void RaisePostBackEvent(string eventArgument)
  {
    foreach (DateTime day in checkedDates)
    {
      if (eventArgument == day.ToShortDateString())
      {
        checkedDates.Remove(DateTime.Parse(eventArgument));                
        ViewState["CheckedDates"] = checkedDates;
        return;
      }
    }
        
    checkedDates.Add(DateTime.Parse(eventArgument));            
    ViewState["CheckedDates"] = checkedDates;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    object obj = ViewState["CheckedDates"];
    if (obj != null)
    {
      checkedDates = (List<DateTime>)obj;
    }
  }

  protected void Button1_Click(object sender, EventArgs e)
  {
    string str = "Selected Dates:";
    foreach (DateTime day in checkedDates)
    {
      str += "<br />" + day.ToShortDateString();
    }
    Label1.Text = str;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Calendar with CheckBox</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Calendar ID="Calendar1" 
      runat="server" 
      ondayrender="Calendar1_DayRender" 
      SelectionMode="None" >
    </asp:Calendar>
    <asp:Button ID="Button1" 
      runat="server" 
      Text="Show selected dates" 
      onclick="Button1_Click" />
    <br />
    <asp:Label ID="Label1" runat="server" />        
  </div>
  </form>
</body>
</html>

------------ 2010/4/24 追記 ------------

この記事で紹介したコードを実際に動かして試せるよう 実験室 にアップしました。興味のある方は試してみてください。

Tags: ,

ASP.NET

ListView でページ指定

by WebSurfer 2010年11月13日 16:25
ListView でページ指定

GridView や ListView でページングを行っている場合、プログラムで表示するページを指定したいケースがあると思います。

GridView の場合は簡単で、PageIndex プロパティを用いてページの指定ができます。

ところが、ListView には、GridView の PageIndex プロパティのような、表示するページを指定できるプロパティがありません。

これは、GirdView にはページング機能が統合されているのに対し、ListView そのものにはページング機能は実装されていないからです。

通常、ListView でページングを行うには、DataPager コントロールを用います。これを利用すれば、GridView.PageIndex プロパティと同様に、ListView でもプログラムで表示するページを指定することができます。

以下に、ListView において、プログラムで表示するページを指定する例を書いておきます。

まず、DataPager フィールドに NumericPagerField クラスを実装する必要があります。その HandleEvent メソッドを利用します。

HandleEvent メソッドは、引数の CommandEventArgs オブジェクトの CommandName プロパティの値を見て、それにページ番号が指定されている場合、対応するページに移動させます。

上の画像のサンプルのコードは以下の通りです。TextBox にページ番号を入力してボタンをクリックすると、指定したページに飛びます。

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  protected void Button1_Click(object sender, EventArgs e)
  {
    int page = 0;
    if (Int32.TryParse(TextBox1.Text, out page))
    {
      page -= 1;
    }
    CommandEventArgs commandEventArgs = 
      new CommandEventArgs(page.ToString(), "");
    DataPager dp = 
      (DataPager)ListView1.FindControl("DataPager1");
    NumericPagerField numericPagerField = 
      dp.Fields[2] as NumericPagerField;
    if (numericPagerField != null)
    {
      numericPagerField.HandleEvent(commandEventArgs);
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <style type="text/css">
    table.style1
    {
      border-style: solid;
      border-width: 2px;
      border-color: Black;
      text-align: center;
      border-collapse: collapse;
    }
       
    table.style1 th
    {
      border-style: solid;
      border-width: 2px 1px 2px 1px;
      border-color: Black;
      background-color: #6699FF;
      color: #FFFFFF;
    }
        
    table.style1 td
    {
      border-style: solid;
      border-width: 1px;
      border-color: Black;        
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    Page: 
    <asp:TextBox ID="TextBox1" runat="server">
    </asp:TextBox>
    <asp:Button ID="Button1" 
      runat="server" 
      Text="Go" 
      OnClick="Button1_Click" />
    <hr />
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      EnablePaging="True" 
      SelectCountMethod="GetNumberOfMessages" 
      SelectMethod="GetDataByIndex" 
      TypeName="OrdersDataSetTableAdapters.OrdersTableAdapter">
      <SelectParameters>
        <asp:Parameter Name="employeeid" DefaultValue="-1" />
      </SelectParameters>
    </asp:ObjectDataSource>
    <asp:ListView ID="ListView1" 
      runat="server" 
      DataKeyNames="OrderID" 
      DataSourceID="ObjectDataSource1">
      <LayoutTemplate>
        <table runat="server">
          <tr runat="server">
            <td runat="server">
              <table ID="itemPlaceholderContainer" 
                runat="server" 
                class="style1">
                <tr runat="server">
                  <th id="Th1" runat="server">
                    OrderID</th>
                  <th id="Th2" runat="server">
                    CustomerID</th>
                  <th id="Th3" runat="server">
                    OrderDate</th>
                  <th id="Th4" runat="server">
                    Freight</th>
                  <th id="Th5" runat="server">
                    ShipCountry</th>
                </tr>
                <tr ID="itemPlaceholder" 
                  runat="server">
                </tr>
              </table>
            </td>
          </tr>
          <tr runat="server">
            <td runat="server">
              <asp:DataPager ID="DataPager1" 
                runat="server" 
                PageSize="10">
                <Fields>
                  <asp:TemplatePagerField>              
                    <PagerTemplate>
                      <b>
                        Page
                        <asp:Label runat="server" 
                          ID="CurrentPageLabel" 
                          Text="<%# Container.TotalRowCount>0 ? (Container.StartRowIndex / Container.PageSize) + 1 : 0 %>" />
                        of
                        <asp:Label runat="server" 
                          ID="TotalPagesLabel" 
                          Text="<%# Math.Ceiling ((double)Container.TotalRowCount / Container.PageSize) %>" />
                        (
                        <asp:Label runat="server" 
                          ID="TotalItemsLabel" 
                          Text="<%# Container.TotalRowCount%>" />
                        records)
                        <br />
                      </b>
                    </PagerTemplate>
                  </asp:TemplatePagerField>
                  <asp:NextPreviousPagerField
                    ButtonType="Button"
                    ShowFirstPageButton="true"
                    ShowNextPageButton="false"
                    ShowPreviousPageButton="false" />
                  <asp:NumericPagerField 
                    PreviousPageText="< Prev 5"
                    NextPageText="Next 5 >"
                    ButtonCount="5" />
                  <asp:NextPreviousPagerField
                    ButtonType="Button"
                    ShowLastPageButton="true"
                    ShowNextPageButton="false"
                    ShowPreviousPageButton="false" />
                </Fields>
              </asp:DataPager>
            </td>
          </tr>
        </table>
      </LayoutTemplate>
      <ItemTemplate>
                <tr>
                  <td>
                    <asp:Label ID="OrderIDLabel" 
                     runat="server" 
                     Text='<%# Eval("OrderID") %>' />
                  </td>
                  <td>
                    <asp:Label ID="CustomerIDLabel" 
                     runat="server" 
                     Text='<%# Eval("CustomerID") %>' />
                  </td>
                  <td>
                    <asp:Label ID="OrderDateLabel" 
                     runat="server" 
                     Text='<%# Eval("OrderDate", "{0:yyyy/MM/dd}") %>' />
                  </td>
                  <td>
                    <asp:Label ID="FreightLabel" 
                     runat="server" 
                     Text='<%# Eval("Freight", "${0:N2}") %>' />
                  </td>
                  <td>
                    <asp:Label ID="ShipCountryLabel" 
                     runat="server" 
                     Text='<%# Eval("ShipCountry") %>' />
                  </td>
                </tr>
      </ItemTemplate>
    </asp:ListView>    
  </div>
  </form>
</body>
</html>

なお、上記は Resetting the Page Index in a ListView を参考にしています(参考というよりほぼ丸写しですが)。

Tags: , ,

Paging

About this blog

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

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar