当前位置: 移动技术网 > IT编程>开发语言>.net > 在ASP.NET 2.0中操作数据之四十八:对SqlDataSource控件使用开放式并发

在ASP.NET 2.0中操作数据之四十八:对SqlDataSource控件使用开放式并发

2017年12月12日  | 移动技术网IT编程  | 我要评论

英雄大作战0.75,50磅等于多少公斤,采之网

导言:

  在前面的教程里,我们考察了如何为sqldatasource控件添加插入、更新、删除功能。简而言之,就是为其nsertcommand, updatecommand和deletecommd属性赋以相应的insert,update和deletesql语句,并将相应的参数放置在<insertparameters>, <updateparameters>和<deleteparameters>标签里。我们可以手工书写这些代码,也可以通过在设置数据源向导里单击“高级”按钮,选择“自动生成insert, update和delete命令”,自动的生成语句。

  在“高级sql生成选项”对话框里有个“使用开放式并发”选项(见图1)。当
选择该项后,数据库中的数据在自上一次成功保存以来没发生任何改变的情况下,才能成功地执行更新或删除操作。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909002291.jpg
图1:在“高级sql生成选项”对话框添加开放式并发支持

  在教程路我们探讨了开放式并发控制的基本原理以及如何对objectdatasource控件使用开放式并发。在本教程我们看如何对sqldatasource控件使用开放式并发。

新的开放式并发

  在一个允许多人同时编辑或删除相同数据的应用程序里,有这种可能:一个人修改后的记录意外地被另一个人修改的记录所覆盖。在implementing optimistic concurrency 这篇教程我们例举过这样的例子:

  例如,假设两个用户,jisun和sam,都访问我们的应用软件中的一个页面,这个页面允许访问者通过一个gridview控件更新和删除产品数据。他们都同时点击gridview控件中的edit按钮。jisun把产品名称更改为“chai tea”并点击update按钮,实质结果是向数据库发送一个update语句,它将更新此产品的所有可修改的字段(尽管jisun实际上只修改了一个字段:productname)。在这一刻,数据库中包含有这条产品记录“chai tea”—种类为beverages、供应商为exotic liquids、等该产品的详细信息。然而,在sam的屏幕中的gridview里,当前编辑行里显示的产片名称依旧是“chai”。在jisun的更改被提交后片刻,sam把种类更改为“condiments”并点击update按钮。这个发送到数据库的update语句的结果是将产品名称更改为“chai”、categoryid字段的值是种类beverages对应的id,等等。jisun所作的对产品名称的更改就被覆盖了。

图2展示了这些连续的事件

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909005049.jpg
图2:当两个用户同时更新一条记录,则存在一个用户的更改覆盖另一个的更改的可能性

  为了应对这种可能性,我们必须执行某种并发控制。本文的焦点——开放式并发控制便是其中之一,它适合于这种情况:假定并发冲突只是偶尔发生,绝大多数的时候并不会出现。 当发生一个冲突时,仅仅简单的告知用户,他所作的更改不能保存,因为别的用户已经修改了同一条记录。

  注意:对应用程序来说,假定并发冲突经常发生,且无法容忍。在这种情况下最后用保守式并发控制。关于保守式并发控制的更多讨论,请参考implementing optimistic concurrency 教程。


  开放式并发控制的作用在于:确保要更新或删除的记录的值与该记录在updating or deleting阶段的值相同。比如,例如,当在一个可编辑的gridview里点击编辑按钮时,该记录的原始值从数据库中读取出来并显示在textbox和其他web控件中。这些原始的值保存在gridview里。随后,当用户完成他的修改并点击更新按钮,这些原始值加上修改后的新值发送到业务逻辑层,然后到数据访问层。数据访问层必定发出一个sql语句,它将仅仅更新那些开始编辑时的原始值根数据库中的值一致的记录。图3描述了这些事件发生的顺序。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909012664.jpg
图3:为了更新或删除能够成功,原始值必须与数据库中相应的值一致

  有多种方法可以实现开放式并发控制(查看peter a. bromberg的文章  optmistic concurrency updating logic,从摘要中看到许多选择)。sqldatasource控件使用该方法(就像数据访问层中ado.net类型的数据集使用的那样)扩展where字句,用以包含用来做比较的原始值。例如下面的update语句,当当前数据库中的值与gridview中开始编辑的原始值一致才更新某个产品的名称和价格。@productname 和 @unitprice参数包含的是用户输入的新值,而参数@original_productname 和 @original_unitprice则包含最初点击编辑按钮时加载到gridview中的值:

update products set
 productname = @productname,
 unitprice = @unitprice
where
 productid = @original_productid and
 productname = @original_productname and
 unitprice = @original_unitprice

  就像我们将在本教程看到的一样,使sqldatasource能实现开放式并发控制是很简单的事情。

第一步:创建一个支持开放式并发的sqldatasource控件

  打开sqldatasource文件夹中的optimisticconcurrency.aspx页面,从工具箱拖一个sqldatasource控件到页面,设置其id为productsdatasourcewithoptimisticconcurrency。在其智能标签里点“设置数据源”,数据库选为“northwindconnectionstring”,点下一步。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909012193.jpg
图4:选“orthwindconnectionstring”数据库

  在此例子里,我们将添加一个gridview控件以编辑表products。所以在“configure the select statement”界面选择从表products返回productid, productname, unitprice和discontinued列,如图5所示:

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909011722.jpg
图5:从表products返回productid, productname, unitprice和discontinued列

  然后,点“高级”按钮,打开“advanced sql generation options”对话框,选择“generate insert, update, and delete statements”和“use optimistic concurrency”2项,点“ok”(见图1)。再点下一步、完成,结束设置。

  完成设置数据源向导后,花几分钟查看deletecommand和updatecommand属性,以及deleteparameters和updateparameters标签。最快的方法是切换到“源模式”直接在页面代码查看,你会看到updatecommand的值像这样:

update [products] set
 [productname] = @productname,
 [unitprice] = @unitprice,
 [discontinued] = @discontinued
where
 [productid] = @original_productid and
 [productname] = @original_productname and
 [unitprice] = @original_unitprice and
 [discontinued] = @original_discontinued

同时在<updateparameters>标签里有7个参数:

<asp:sqldatasource id="productsdatasourcewithoptimisticconcurrency"
 runat="server" ...>
 <deleteparameters>
 ...
 </deleteparameters>
 <updateparameters>
 <asp:parameter name="productname" type="string" />
 <asp:parameter name="unitprice" type="decimal" />
 <asp:parameter name="discontinued" type="boolean" />
 <asp:parameter name="original_productid" type="int32" />
 <asp:parameter name="original_productname" type="string" />
 <asp:parameter name="original_unitprice" type="decimal" />
 <asp:parameter name="original_discontinued" type="boolean" />
 </updateparameters>
 ...
</asp:sqldatasource>

同样的,deletecommand属性和<deleteparameters>标签如下:

delete from [products]
where
 [productid] = @original_productid and
 [productname] = @original_productname and
 [unitprice] = @original_unitprice and
 [discontinued] = @original_discontinued
<asp:sqldatasource id="productsdatasourcewithoptimisticconcurrency"
 runat="server" ...>
 <deleteparameters>
 <asp:parameter name="original_productid" type="int32" />
 <asp:parameter name="original_productname" type="string" />
 <asp:parameter name="original_unitprice" type="decimal" />
 <asp:parameter name="original_discontinued" type="boolean" />
 </deleteparameters>
 <updateparameters>
 ...
 </updateparameters>
 ...
</asp:sqldatasource>

选择了“use optimistic concurrency”选项后,不仅扩展了updatecommand 和deletecommand属性里的where字句(同时在相关参数集里添加了参数),同时调整了以下2个属性:

1. 将conflictdetection属性由“overwritechanges”(默认值)改为   “compareallvalues ”
2. 将oldvaluesparameterformatstring属性由“{0}”(默认值)改为    “original_{0}”

  当数据web控件调用sqldatasource的update()或delete()方法时,它将传递原始值。当sqldatasource的conflictdetection属性设置为“compareallvalues”时,就会将这些原始值添加到命令中。而oldvaluesparameterformatstring属性则为这些原始值提供了命名规范,向导以“original_{0}”的形式为 updatecommand和deletecommand中的原始值以及<updateparameters>和<deleteparameters>中的参数命名。

  注意:由于我们没有使用sqldatasource控件的插入功能,因此可以将insertcommand 属性和<insertparameters>标签清除。

正确地处理null值

  不幸的是,当使用开放式并发的时候,由设置数据源向导自动生成的、扩展成包含where字句的update和 delete命令不能处理那些含有null值的记录。为什么呢?先看sqldatasource的updatecommand语句:

update [products] set
 [productname] = @productname,
 [unitprice] = @unitprice,
 [discontinued] = @discontinued
where
 [productid] = @original_productid and
 [productname] = @original_productname and
 [unitprice] = @original_unitprice and
 [discontinued] = @original_discontinued

  表products的unitprice列的值允许为null,如何某条记录的unitprice确实为null,那么where字句的“[unitprice] = @original_unitprice”总是为false,null = null总是返回false。所以凡是y包含null值的记录不能被编辑或删除,因为update和delete命令中的where字句不能返回记录。

  注意:这个漏洞最早于2004年6月报告给微软,据业内传言,微软将在asp.net的下一个版本修补该漏洞。

  为修补该漏洞,我们需要在updatecommand和deletecommand属性里手工修改所有允许为null值的列。一般来说,将[columnname] = @original_columnname to改成:

(
 ([columnname] is null and @original_columnname is null)
 or
 ([columnname] = @original_columnname)
)

  你可以在属性窗口的updatequery或deletequery选项的代码声明里修改,或者在设置数据源向导的“指定自定义sql语句或存储过程”选项的“更新”和“删除”标签里修改。确保在updatecommand和deletecommand的where字句里做相同的修改。如下:

update [products] set
 [productname] = @productname,
 [unitprice] = @unitprice,
 [discontinued] = @discontinued
where
 [productid] = @original_productid and
 [productname] = @original_productname and
 (([unitprice] is null and @original_unitprice is null)
 or ([unitprice] = @original_unitprice)) and
 [discontinued] = @original_discontinued
delete from [products]
where
 [productid] = @original_productid and
 [productname] = @original_productname and
 (([unitprice] is null and @original_unitprice is null)
 or ([unitprice] = @original_unitprice)) and
 [discontinued] = @original_discontinued

第2步:为gridview控件添加编辑和删除项

  当设置sqldatasource控件支持开放式并发时,我们需要在页面上添加一个数据web控件,以便执行开放式并发控制。本章我们添加一个提供编辑和删除功能的gridview控件。从工具箱拖一个gridview到页面上,设置其id为products,并绑定到第一步添加的sqldatasource控件productsdatasourcewithoptimisticconcurrency,最后启用其“编辑”和“删除”功能。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909018777.jpg
图6:将gridview绑定到sqldatasource并启用编辑和删除功能

  添加gridview控件后,优化其界面。将productid列移除;将productname列的headertext属性设置为“product”;同样,unitprice列的设置为“price”。另外,我们最好为productname添加一个requiredfieldvalidator控件;为unitprice添加一个comparevalidator控件(确保其为格式化的数字值)。参考教程customizing the data modification interface看如何自定义gridview界面。

  注意:必须确保激活gridview控件的view state(视图状态),因为gridview控件传递原始值时,将原始值保存在view state中。

  对gridview控件做了这些修改后,gridview控件和sqldatasource控件的声明代码看起来和下面的差不多:

<asp:sqldatasource id="productsdatasourcewithoptimisticconcurrency"
 runat="server" conflictdetection="compareallvalues"
 connectionstring="<%$ connectionstrings:northwndconnectionstring %>"
 deletecommand=
 "delete from [products]
  where [productid] = @original_productid
  and [productname] = @original_productname
  and (([unitprice] is null and @original_unitprice is null)
  or ([unitprice] = @original_unitprice))
  and [discontinued] = @original_discontinued"
 oldvaluesparameterformatstring=
 "original_{0}"
 selectcommand=
 "select [productid], [productname], [unitprice], [discontinued]
  from [products]"
 updatecommand=
 "update [products]
  set [productname] = @productname, [unitprice] = @unitprice,
  [discontinued] = @discontinued
  where [productid] = @original_productid
  and [productname] = @original_productname
  and (([unitprice] is null and @original_unitprice is null)
  or ([unitprice] = @original_unitprice))
 and [discontinued] = @original_discontinued">
 <deleteparameters>
 <asp:parameter name="original_productid" type="int32" />
 <asp:parameter name="original_productname" type="string" />
 <asp:parameter name="original_unitprice" type="decimal" />
 <asp:parameter name="original_discontinued" type="boolean" />
 </deleteparameters>
 <updateparameters>
 <asp:parameter name="productname" type="string" />
 <asp:parameter name="unitprice" type="decimal" />
 <asp:parameter name="discontinued" type="boolean" />
 <asp:parameter name="original_productid" type="int32" />
 <asp:parameter name="original_productname" type="string" />
 <asp:parameter name="original_unitprice" type="decimal" />
 <asp:parameter name="original_discontinued" type="boolean" />
 </updateparameters>
</asp:sqldatasource>
<asp:gridview id="products" runat="server"
 autogeneratecolumns="false" datakeynames="productid"
 datasourceid="productsdatasourcewithoptimisticconcurrency">
 <columns>
 <asp:commandfield showdeletebutton="true" showeditbutton="true" />
 <asp:boundfield datafield="productname" headertext="product"
  sortexpression="productname" />
 <asp:boundfield datafield="unitprice" headertext="price"
  sortexpression="unitprice" />
 <asp:checkboxfield datafield="discontinued" headertext="discontinued"
  sortexpression="discontinued" />
 </columns>
</asp:gridview>

  来实际地感受一下开放式并发控制。在2个浏览器里同时打开optimisticconcurrency.aspx页面,且都点击第一条记录的编辑按钮。在第一个浏览器里改变产品名称并点“编辑”。浏览器将发生回传,gridview控件又回到“预编辑”状态,显示新的产品名称。

  在第2个浏览器里,改变产品的价格(不要改产品名称)后,点“编辑”。发生回传,gridview控件又回到“预编辑”状态,和第1个浏览器显示的结果一样——产品的名称改变了但价格没改变,第2个浏览器做的修改失败了。然而,一切都发生的那么静悄悄,没有任何提示刚才发生了并发冲突!

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909018306.jpg
图7:第2个浏览器所做的修改悄悄的丢失了

  第2个浏览器更新失败的原因在于:update命令中where字句过滤掉了所以的记录,没有影响到任何一行记录(即没找到满足条件的记录)。我们再来看update 语句:

update [products] set
 [productname] = @productname,
 [unitprice] = @unitprice,
 [discontinued] = @discontinued
where
 [productid] = @original_productid and
 [productname] = @original_productname and
 (([unitprice] is null and @original_unitprice is null) or
 ([unitprice] = @original_unitprice)) and
 [discontinued] = @original_discontinued

  当第2个浏览器更新记录时,where字句里的原始产品名(即chai)与当前任意一条记录的产品名不匹配(因为第1个浏览器将chai改为了chai tea)。所以表达式“[productname] = @original_productname ”返回false,导致更新失败。

  注意:删除的原理于此相同。同时打开2个浏览器,第1个先对某个产品作更改,再在第2个浏览器删除该产品,同样是因为原始值与更新后的值不匹配,删除失败。

  在最终用户(更新失败的那个)看来,他点“更新”按钮后,gridview控件返回“预编辑”状态,但提交的修改丢失了。然而没有任何直观的提醒表明修改失败。当用户的更新因并发冲突失败时,我们最好提醒用户,比如将gridview控件保持在“编辑”状态。下面我们来看如何实现这一点。

第3步:并发冲突的处理

  因为并发冲突拒绝用户的更改,所以当发生并发冲突时最好提示用户。在页面上添加一个label控件,其id为concurrencyviolationmessage,设置其text 属性为“you have attempted to update or delete a record that was simultaneously updated by another user. please review the other user's changes and then redo your update or delete”,设置其cssclass属性为“warning”,它定义在styles.css中。最后,把visible和enableviewstate属性设置为“false” 。这样label控件将不可见,除非发生了某些回传事件(我们在这些回传事件里指定label控件的visible属性为true)

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909029592.jpg
图8:在页面添加一个label控件用以显示提醒信息

  执行更新或删除操作时,当gridview的数据源控件完成更新或删除后,才开始执行gridview控件的rowupdated和rowdeleted事件处理器(event handler)。我们可以在这些事件处理器里计算影响了多少条记录。假如影响了0条记录,亦即操作失败,我们希望将label控件concurrencyviolationmessage显示出来。

为rowupdated和rowdeleted事件创建处理器,添加如下代码:

protected void products_rowupdated(object sender, gridviewupdatedeventargs e)
{
 if (e.affectedrows == 0)
 {
 concurrencyviolationmessage.visible = true;
 e.keepineditmode = true;
 // rebind the data to the gridview to show the latest changes
 products.databind();
 }
}
protected void products_rowdeleted(object sender, gridviewdeletedeventargs e)
{
 if (e.affectedrows == 0)
 concurrencyviolationmessage.visible = true;
}

  在这2个事件处理器中我们都要检验e.affectedrows 属性,如果为0,设置label控件concurrencyviolationmessage的visible属性为true。特别的,在rowupdated事件处理器中,我们通过将gridview控件的keepineditmode属性设置为true,使其保持在编辑状态。这样的话,通过gridview的databind() 方法,将他人已经成功更新的数据显示在编辑状态。

如图9所示,当发生并发冲突时,显示提示信息

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121909021878.jpg
图9:当发生并发冲突时,显示提示信息:

总结:

  创建一个应用程序时,当多人同时编辑相同数据的时候,要考虑到并发冲突的问题。在默认情况下,asp.net数据web控件和数据源控件没有采取并发控制。就像我们在本章看到的一样,对sqldatasource控件使用开放式并发控制还是比较迅速和容易的。通过在update和delete语句里扩展where字句,sqldatasource能应对绝大部分情况,但就像在“正确处理null值”部分探讨的那样,对包含null值列的处理有漏洞。

  本章是对sqldatasource考察的完结篇,接下来的教程继续探讨层次结构以及用bjectdatasource处理数据。

  祝编程快乐!

作者简介

  本系列教程作者 scott mitchell,著有六本asp/asp.net方面的书,是4guysfromrolla.com的创始人,自1998年以来一直应用 微软web技术。大家可以点击查看全部教程《[翻译]scott mitchell 的asp.net 2.0数据教程》,希望对大家的学习asp.net有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网