一、(引言)
小菜通过一个简单的例子来说明如何进行并发处理,大家不用当心例子太简单反映不了问题。
小菜通过运行两个实例来模拟两个客户端。

一个阿猫、阿狗引发的故事:

数据库中记录:
TaskVision并发处理
客户端1:加载
TaskVision并发处理
客户端2:加载
TaskVision并发处理

客户端1在本地具有一份数据副本,名字暂时称为 副本1
客户端2在本地也具有一份数据副本,名字暂时称为 副本2

客户端1修改记录:AnimalID = 1 and AnimalType = "狗" 修改成 AnimalID = 1 and AnimalType = "阿狗"
修改的只是副本1中的数据,点击保存,将修改更新到服务器。

客户端1:
 TaskVision并发处理
数据库中记录:
 TaskVision并发处理
看来数据更改成功。

客户端2:
TaskVision并发处理 
从图可以看出:客户端2并不知道客户端1已经把 AnimalType = "狗" 修改成了 AnimalType = "阿狗"

这时如果客户端2将 AnimalID = 1 and AnimalType = “狗”修改成了 AnimalID = 1 and AnimalType = “阿黄”

怎么办呢?(小菜也不知道怎么办,因为我们不知道用户想干什么)
用户目的是:将“狗”改成“阿黄”
如果用户知道了别人已经将“狗”改成了“阿狗”
用户可能会继续更改,也可能放弃更改。(这就出现了两种可能!!!)

所以我们应该让用户自己来选择:
TaskVision并发处理
 
以上解决并发处理的方案称为:开放式并发处理.
对应的保守式并发处理:
是当用户编辑数据库中的某行数据时,保守式并发会使此数据一直保持锁定.在锁定期间,其他用户不能更改数据,这样能够最高程度的保证数据的完整性,但是可用性低.

代码下载:https://files.cnblogs.com/a-peng/SmartClient_Chapter05.rar

(二)、分析
1、使用TableAdapter + 类型化DataSet 完成开放式并发处理

演化一:
新建Web服务:Server
添加数据集:DataSetAnimals(配置略,注意使用开放式并发 和 刷新数据表两个选项)

 TaskVision并发处理

添加Web服务:DataService.asmx
代码如下:

TaskVision并发处理public class DataService : System.Web.Services.WebService
}
添加Windows项目:Client

添加Web服务引用,名称:DataWS
添加窗体:MainForm
代码如下:

TaskVision并发处理using System;
TaskVision并发处理
using System.Windows.Forms;
TaskVision并发处理
TaskVision并发处理
using Client.DataWS;
TaskVision并发处理
TaskVision并发处理
namespace Client
}
这是我们的第一份代码,我们来试试并发时效果。

TaskVision并发处理 
左边为客户端1,右边为客户端2。
客户端1将狗,猫,蛇 修改成 阿狗,阿猫,阿蛇,并保存,更新到数据库。
客户端2将狗,猫,蛇 修改成 小狗,小猫,小蛇,并保存,
效果图如下:
TaskVision并发处理

效果并不好,我们没有给用户做出选择的权利。

演化二:
修改主窗体中保存按钮响应代码:

TaskVision并发处理private void btnSave_Click(object sender, EventArgs e)
}
和演化一,一样的测试,效果如下:
TaskVision并发处理 
我们来看看CollisionForm的代码:
TaskVision并发处理using System;
TaskVision并发处理
using System.Windows.Forms;
TaskVision并发处理
TaskVision并发处理
using Client.DataWS;
TaskVision并发处理
TaskVision并发处理
namespace Client
}
到这时,我们就基本完成任务了。不过有哪些不足呢?
你要是注意看就会发现上面显示的,Previous Change的AnimalType为“狗”,而我们希望显示的是“阿狗”。
怎么办呢,我们通过m_errorRow["AnimalType", System.Data.DataRowVersion.Original].ToString();获取得原始只可能是“狗”。这就需要我们在Web服务上动手脚。

演化三:
修改DataService.asmx中的UpdateAnimals代码
TaskVision并发处理[WebMethod]
TaskVision并发处理
public DataSetAnimals UpdateAnimals(DataSetAnimals dsAnimals)
}
效果如下:
TaskVision并发处理
上面的代码有这么大魔力?
TaskVision并发处理DataRow proposedRow = dbEx.Row.Table.NewRow();
TaskVision并发处理DataRow databaseRow 
= ds.Tables[0].Rows[0];
TaskVision并发处理
TaskVision并发处理proposedRow.ItemArray 
= dbEx.Row.ItemArray; // 将错误行全部信息拷贝到proposedRow中
TaskVision并发处理

TaskVision并发处理dbEx.Row.Table.Columns[
"AnimalID"].ReadOnly = false;
TaskVision并发处理dbEx.Row.ItemArray 
= databaseRow.ItemArray; // 将数据库中的行拷贝到错误行dbEx.Row中
TaskVision并发处理
dbEx.Row.AcceptChanges(); // 接受更改,将dbEx.Row的状态修改成UnChanged
TaskVision并发处理
dbEx.Row.ItemArray = proposedRow.ItemArray; // 恢复错误行信息
TaskVision并发处理
dbEx.Row.Table.Columns["AnimalID"].ReadOnly = true;
上面这几句好像在做无用功。改了又恢复,其实主要就是更改原始值为databaseRow.ItemArray。

2、不使用TableAdapter完成一样的效果。
DataService.asmx代码如下:
TaskVision并发处理public class DataService : System.Web.Services.WebService
}

存储过程:GetAnimals
TaskVision并发处理CREATE PROCEDURE [GetAnimals]
TaskVision并发处理 
AS
TaskVision并发处理
SET NOCOUNT ON;
TaskVision并发处理
SELECT AnimalID, AnimalType FROM Animals
TaskVision并发处理
GO

存储过程:UpdateAnimal
TaskVision并发处理CREATE PROCEDURE UpdateAnimal
TaskVision并发处理(
TaskVision并发处理    
@AnimalID int,
TaskVision并发处理    
@AnimalType varchar(50),
TaskVision并发处理    
@Original_AnimalType varchar(50)
TaskVision并发处理)
TaskVision并发处理
AS
TaskVision并发处理
SET NOCOUNT OFF;
TaskVision并发处理
UPDATE [Animals] SET [AnimalType] = @AnimalType WHERE (([AnimalID] = @AnimalID
         AND
 ([AnimalType] = @Original_AnimalType));
TaskVision并发处理    
TaskVision并发处理
SELECT AnimalID, AnimalType FROM Animals WHERE (AnimalID = @AnimalID)
TaskVision并发处理
GO

存储过程:InsertAnimal
TaskVision并发处理CREATE PROCEDURE [InsertAnimal] 
TaskVision并发处理(
TaskVision并发处理    
@AnimalType varchar(50)
TaskVision并发处理)
TaskVision并发处理
AS
TaskVision并发处理
SET NOCOUNT OFF;
TaskVision并发处理
INSERT INTO [Animals] ([AnimalType]VALUES (@AnimalType);
TaskVision并发处理    
TaskVision并发处理
SELECT AnimalID, AnimalType FROM Animals WHERE (AnimalID = SCOPE_IDENTITY())
TaskVision并发处理
GO

存储过程:DeleteAnimal
TaskVision并发处理CREATE PROCEDURE [DeleteAnimal] 
TaskVision并发处理(
TaskVision并发处理    
@AnimalID int,
TaskVision并发处理    
@Original_AnimalType varchar(50)
TaskVision并发处理)
TaskVision并发处理
AS
TaskVision并发处理
SET NOCOUNT OFF;
TaskVision并发处理
DELETE FROM [Animals] WHERE (([AnimalID] = @AnimalIDAND ([AnimalType] = @Original_AnimalType))
TaskVision并发处理
GO

存储过程:GetAnimal
TaskVision并发处理CREATE PROCEDURE [GetAnimal]
TaskVision并发处理(
TaskVision并发处理    
@AnimalID int
TaskVision并发处理)
TaskVision并发处理 
AS
TaskVision并发处理
SET NOCOUNT ON;
TaskVision并发处理
SELECT AnimalID, AnimalType FROM Animals WHERE AnimalID = @AnimalID
TaskVision并发处理
GO

其它代码一样。

我们还可以使用IssueVision的并发处理策略,解决的方法很优雅。
我们可以在DataSetAnimals中新建一个表,和Animals表一样,引用相同的表结构,名称为Conficts,当成冲突记录用。
使用RowUpdating,出现错误行就将其添加Conficts中,更新完成后返回。客户端只需取出DataSetAnimals中的Conficts就可以获取冲突信息。

详细可参看IssueVision。


*************************************************************************
作者:a-peng
出处:http://a-peng.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出
原文连接,否则保留追究法律责任的权利。
*************************************************************************

相关文章:

  • 2021-12-02
  • 2021-11-21
  • 2021-12-27
  • 2021-12-11
  • 2022-01-06
猜你喜欢
  • 2022-03-07
  • 2021-11-25
  • 2022-02-22
  • 2022-02-09
  • 2021-11-21
  • 2021-10-02
相关资源
相似解决方案