by WebSurfer
2017年2月11日 15:52
SQL Server Management Studio(以下、SSMS と書きます) を使ってデータの編集ができますが、その際に楽観的同時実行制御がかかっているようです。(SQL Server 2008 Express SP4 で試しました)
上の画像が SSMS を使ってのデータの編集中に楽観的同時実行制御に引っかかって警告のダイアログが表示されたところです。
上の例は、EmployeeID = 1 の LastName を編集して他の行に移動した際、編集されたデータを使ってデータベースの更新(UPDATE クエリの発行)をトライしたが、SSMS にデータを表示してから UPDATE クエリが発行されるまでの間に他のユーザーがデータを変更していたという状況です。
SSMS での編集に楽観的同時実行制御がかかるって知ってました? 実は自分は知らなかったです。後勝ちルールになると思ってました。(汗)
このデータを更新するアプリと SSMS を一緒に動かしていたら上の画像のダイアログが出てきたので、楽観的同時実行制御がかかっていることを初めて知ったという次第です。
新発見ということでブログに書いておくことにしました。知らなかったのは自分だけかもしれませんが。(笑)
by WebSurfer
2012年6月24日 00:21
楽観的同時実行制御を有効にした時、クエリの WHERE 句には [xxx] = @original_xxx というような条件が設定されます。SqlDataSource と DetailsView などを使った場合の削除操作の際、@original_xxx はどこから取得されるでしょうか?
上の画像の例では、ItemTemplate に Label を配置して文字列を表示するようにしていますが、削除操作を行う場合、@original_xxx はどこから取得されるかというと、ItemTemplate に配置した Label の Text プロパティからです。
Label に文字列を表示しているのにブラウザの表示で改行されているのは DetailsView.PreRender イベントのハンドラで Environment.NewLine を "<br />" に置き換えているからです。
従って、このように表示される文字列をプログラムで書き換えてしまうと、DELETE クエリの WHERE 句で [xxx] = @original_xxx が成立しないので、削除に失敗します。(これは DetailsView に限らず、GridView などでも同じです)
ちなみに、Text='<%# Bind("text") %>' の Bind を Eval に変更すると、 DetailsViewDeleteEventArgs.Values["text"] は null になります。その 結果、削除ボタンクリックで InvalidOperationException がスローされます。
というわけで、楽観的同時実行制御を行って、この例のように Environment.NewLine を "<br />" に置き換えて表示するような場合、削除操作直前に、@original_text に代入される文字列を元に戻すしかなさそうです。
それは、以下のコードのように、SqlDataSource.Deleting イベントのハンドラで行うのがよさそうです。
<%@ 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">
// ウィザードで作っていくと Label も Text='<%# Bind("text") %>'
// となる。なぜなら、@original_text に代入される文字列は Text
// プロパティから取得するから。それゆえ、以下のようにプログラム
// で表示する文字列を書き換えると [text] = @original_text が
// true にならず、削除に失敗する。
// Text='<%# Bind("text") %>' の Bind を Eval に変更すると、
// DetailsViewDeleteEventArgs.Values["text"] は null になる。
// 結果、[削除]ボタンクリックで InvalidOperationException
// がスローされる。
protected void DetailsView1_PreRender(object sender,
EventArgs e)
{
Control control =
((DetailsView)sender).FindControl("Label1");
if (control != null)
{
Label label = (Label)control;
label.Text =
label.Text.Replace(Environment.NewLine, "<br />");
}
}
// 検証用
protected void DetailsView1_ItemDeleting(object sender,
DetailsViewDeleteEventArgs e)
{
// Environment.NewLine が <br /> に変更されて
// いることが確認できる。
string text = (string)e.Values["text"];
}
protected void SqlDataSource1_Deleting(object sender,
SqlDataSourceCommandEventArgs e)
{
// 削除を可能するには @original_text に代入される文字
// 列を元に戻すしかない。
string text =
(string)e.Command.Parameters["@original_text"].Value;
text = text.Replace("<br />", Environment.NewLine);
e.Command.Parameters["@original_text"].Value = text;
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConflictDetection="CompareAllValues"
ConnectionString="<%$ ConnectionStrings:MyDB %>"
DeleteCommand=
"DELETE FROM [Table3]
WHERE [id] = @original_id AND [text] = @original_text"
InsertCommand=
"INSERT INTO [Table3] ([text]) VALUES (@text)"
OldValuesParameterFormatString="original_{0}"
SelectCommand="SELECT [id], [text] FROM [Table3]"
UpdateCommand=
"UPDATE [Table3]
SET [text] = @text
WHERE [id] = @original_id AND [text] = @original_text"
OnDeleting="SqlDataSource1_Deleting">
<DeleteParameters>
<asp:Parameter Name="original_id" Type="Int32" />
<asp:Parameter Name="original_text" Type="String" />
</DeleteParameters>
<InsertParameters>
<asp:Parameter Name="text" Type="String" />
</InsertParameters>
<UpdateParameters>
<asp:Parameter Name="text" Type="String" />
<asp:Parameter Name="original_id" Type="Int32" />
<asp:Parameter Name="original_text" Type="String" />
</UpdateParameters>
</asp:SqlDataSource>
<asp:DetailsView ID="DetailsView1"
runat="server"
AllowPaging="True"
AutoGenerateRows="False"
DataKeyNames="id"
DataSourceID="SqlDataSource1"
Width="550px"
OnPreRender="DetailsView1_PreRender"
OnItemDeleting="DetailsView1_ItemDeleting">
<Fields>
<asp:BoundField DataField="id"
HeaderText="id"
InsertVisible="False"
ReadOnly="True"
SortExpression="id" />
<asp:TemplateField HeaderText="text"
SortExpression="text">
<EditItemTemplate>
<asp:TextBox ID="TextBox1"
runat="server"
Rows="15"
Text='<%# Bind("text") %>'
TextMode="MultiLine"
Width="500px">
</asp:TextBox>
</EditItemTemplate>
<InsertItemTemplate>
<asp:TextBox ID="TextBox1"
runat="server"
Rows="15"
Text='<%# Bind("text") %>'
TextMode="MultiLine"
Width="500px">
</asp:TextBox>
</InsertItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1"
runat="server"
Text='<%# Eval("text") %>'>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField
ShowDeleteButton="True"
ShowEditButton="True"
ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
</div>
</form>
</body>
</html>