博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

TaskVision并发处理

Posted on 2008-10-14 19:20  a-peng  阅读(2400)  评论(4编辑  收藏  举报

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

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

数据库中记录:
image
客户端1:加载
image
客户端2:加载
image

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

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

客户端1:
 image
数据库中记录:
 image
看来数据更改成功。

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

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

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

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

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

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

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

 image

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

public class DataService : System.Web.Services.WebService
{
    
private DataSetAnimalsTableAdapters.AnimalsTableAdapter m_daAnimals;

    
public DataService()
    
{
        m_daAnimals 
= new DataSetAnimalsTableAdapters.AnimalsTableAdapter();
    }


    [WebMethod]
    
public DataSetAnimals GetAnimals()
    
{
        DataSetAnimals dsAnimals 
= new DataSetAnimals();
        m_daAnimals.Fill(dsAnimals.Animals);

        
return dsAnimals;
    }


    [WebMethod]
    
public DataSetAnimals UpdateAnimals(DataSetAnimals dsAnimals)
    
{
        
try
        
{
            m_daAnimals.Update(dsAnimals.Animals);
        }

        
catch (DBConcurrencyException) 
        
{
            
// 更新失败,发生并发错误,dsAnimals中含有错误行信息.
            return dsAnimals;
        }


        
// 更新成功,返回最新数据   dsAnimals.Clear();
        m_daAnimals.Fill(dsAnimals.Animals);

        
return dsAnimals;
    }

}
添加Windows项目:Client

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

using System;
using System.Windows.Forms;

using Client.DataWS;

namespace Client
{
    
public partial class MainForm : Form
    
{
        
private DataService m_dataService; // Web服务代理
        private DataSetAnimals m_dsAnimals;

        
public MainForm()
        
{
            InitializeComponent();

            m_dataService 
= new DataService();
        }


        
private void btnLoad_Click(object sender, EventArgs e)
        
{
            m_dsAnimals 
= m_dataService.GetAnimals();
            
this.dataGridView1.DataSource = m_dsAnimals.Animals.DefaultView; // DataGridView控件数据源绑定
        }


        
private void btnSave_Click(object sender, EventArgs e)
        
{
            UpdateAnimals();
        }


        
private void UpdateAnimals()
        
{
            DataSetAnimals dsAnimals 
= null;

            
try
            
{
                dsAnimals 
= m_dataService.UpdateAnimals(m_dsAnimals);
            }

            
catch
            
{ }

            
if (dsAnimals != null)
            
{
                m_dsAnimals.Clear();
                m_dsAnimals.Merge(dsAnimals);
            }

        }

    }

}
这是我们的第一份代码,我们来试试并发时效果。

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

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

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

private void btnSave_Click(object sender, EventArgs e)
{
    
do
    
{
        UpdateAnimals();

        
if (m_dsAnimals.HasErrors) // 存在错误行
        {
            DataSetAnimals.AnimalsRow errorRow 
= null;
            
foreach (DataSetAnimals.AnimalsRow dr in m_dsAnimals.Animals.Rows)
            
{
                
if (dr.HasErrors)
                
{
                    errorRow 
= dr;
                    
break;
                }

            }


            
// 使用乐观并行处理
            CollisionForm cForm = new CollisionForm(errorRow);
            cForm.ShowDialog();
        }

        
else // 不存在错误行
        {
            
return;
        }


    }
 while (true);
}
和演化一,一样的测试,效果如下:
image 
我们来看看CollisionForm的代码:
using System;
using System.Windows.Forms;

using Client.DataWS;

namespace Client
{
    
public partial class CollisionForm : Form
    
{
        
private DataSetAnimals.AnimalsRow m_errorRow;

        
public CollisionForm(DataSetAnimals.AnimalsRow errorRow)
        
{
            InitializeComponent();

            m_errorRow 
= errorRow;
        }


        
private void CollisionForm_Load(object sender, EventArgs e)
        
{
            
// 绑定当前值
            txtAnimalID.Text = m_errorRow.AnimalID.ToString();
            txtAnimalType.Text 
= m_errorRow.AnimalType;

            
// 绑定原始值
            txtOriginalAnimalID.Text = m_errorRow["AnimalID", System.Data.DataRowVersion.Original].ToString();
            txtOriginalAnimalType.Text 
= m_errorRow["AnimalType", System.Data.DataRowVersion.Original].ToString();
        }


        
// 保存更改
        private void btnSave_Click(object sender, EventArgs e)
        
{
            
if (txtAnimalType.Text.Trim().Length > 0)
            
{
                m_errorRow.ClearErrors();

                m_errorRow.AnimalType 
= txtAnimalType.Text;

                
this.Close();
            }

        }


        
// 忽略更改
        private void btnIgnore_Click(object sender, EventArgs e)
        
{
            m_errorRow.ClearErrors();
            m_errorRow.RejectChanges();

            
this.Close();
        }

    }

}
到这时,我们就基本完成任务了。不过有哪些不足呢?
你要是注意看就会发现上面显示的,Previous Change的AnimalType为“狗”,而我们希望显示的是“阿狗”。
怎么办呢,我们通过m_errorRow["AnimalType", System.Data.DataRowVersion.Original].ToString();获取得原始只可能是“狗”。这就需要我们在Web服务上动手脚。

演化三:
修改DataService.asmx中的UpdateAnimals代码
[WebMethod]
public DataSetAnimals UpdateAnimals(DataSetAnimals dsAnimals)
{
    
try
    
{
        m_daAnimals.Update(dsAnimals.Animals);
    }

    
catch (DBConcurrencyException dbEx)
    
{
        SqlConnection conn 
= new SqlConnection("Data Source=A-PENG;Initial Catalog=Test;User ID=sa;Password=password");
        SqlCommand cmd 
= conn.CreateCommand();
        cmd.CommandText 
= "Select AnimalID,AnimalType From Animals Where AnimalID=@AnimalID";

        SqlDataAdapter da 
= new SqlDataAdapter(cmd);
        da.SelectCommand.Parameters.AddWithValue(
"@AnimalID", dbEx.Row["AnimalID"]);

        DataSet ds 
= new DataSet();
        da.Fill(ds);

        
if (ds.Tables[0].Rows.Count > 0)
        
{
            DataRow proposedRow 
= dbEx.Row.Table.NewRow();
            DataRow databaseRow 
= ds.Tables[0].Rows[0];

            proposedRow.ItemArray 
= dbEx.Row.ItemArray;

            dbEx.Row.Table.Columns[
"AnimalID"].ReadOnly = false;
            dbEx.Row.ItemArray 
= databaseRow.ItemArray;
            dbEx.Row.AcceptChanges();
            dbEx.Row.ItemArray 
= proposedRow.ItemArray;
            dbEx.Row.Table.Columns[
"AnimalID"].ReadOnly = true;

            
// 更新失败,发生并发错误,dsAnimals中含有错误行信息.
            return dsAnimals;
        }

        
else
        
{
            dbEx.Row.Delete();
            dbEx.Row.AcceptChanges();
        }

    }


    
// 更新成功,返回最新数据
    dsAnimals.Clear();
    m_daAnimals.Fill(dsAnimals.Animals);

    
return dsAnimals;
}
效果如下:
image
上面的代码有这么大魔力?
DataRow proposedRow = dbEx.Row.Table.NewRow();
DataRow databaseRow 
= ds.Tables[0].Rows[0];

proposedRow.ItemArray 
= dbEx.Row.ItemArray; // 将错误行全部信息拷贝到proposedRow中

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

2、不使用TableAdapter完成一样的效果。
DataService.asmx代码如下:
public class DataService : System.Web.Services.WebService
{
    
private const string m_connectionString = "Data Source=A-PENG;Initial Catalog=Test;User ID=sa;Password=password";
    
private SqlConnection m_conn;

    
private SqlDataAdapter m_daAnimals;
    
private SqlCommand m_selectAnimalsCommand;
    
private SqlCommand m_updateAnimalCommand;
    
private SqlCommand m_insertAnimalCommand;
    
private SqlCommand m_deleteAnimalCommand;

    
public DataService () 
    
{
        m_conn 
= new SqlConnection(m_connectionString);

        m_daAnimals 
= new SqlDataAdapter();

        
// Select
        m_selectAnimalsCommand = new SqlCommand();
        m_selectAnimalsCommand.Connection 
= m_conn;
        m_selectAnimalsCommand.CommandText 
= "GetAnimals";
        m_selectAnimalsCommand.CommandType 
= CommandType.StoredProcedure;

        
// Update
        m_updateAnimalCommand = new SqlCommand();
        m_updateAnimalCommand.Connection 
= m_conn;
        m_updateAnimalCommand.CommandText 
= "UpdateAnimal";
        m_updateAnimalCommand.CommandType 
= CommandType.StoredProcedure;
        m_updateAnimalCommand.Parameters.Add(
new SqlParameter("@AnimalID", SqlDbType.Int, 4"AnimalID"));
        m_updateAnimalCommand.Parameters.Add(
new SqlParameter("@AnimalType", SqlDbType.VarChar, 50"AnimalType"));
        m_updateAnimalCommand.Parameters.Add(
new SqlParameter("@Original_AnimalType", SqlDbType.VarChar, 50, ParameterDirection.Input, false00"AnimalType", DataRowVersion.Original, null));

        
// Insert
        m_insertAnimalCommand = new SqlCommand();
        m_insertAnimalCommand.Connection 
= m_conn;
        m_insertAnimalCommand.CommandText 
= "InsertAnimal";
        m_insertAnimalCommand.CommandType 
= CommandType.StoredProcedure;
        m_insertAnimalCommand.Parameters.Add(
new SqlParameter("@AnimalType", SqlDbType.VarChar, 50"AnimalType"));

        
// Delete
        m_deleteAnimalCommand = new SqlCommand();
        m_deleteAnimalCommand.Connection 
= m_conn;
        m_deleteAnimalCommand.CommandText 
= "DeleteAnimal";
        m_deleteAnimalCommand.CommandType 
= CommandType.StoredProcedure;
        m_deleteAnimalCommand.Parameters.Add(
new SqlParameter("@AnimalID", SqlDbType.Int, 4"AnimalID"));
        m_deleteAnimalCommand.Parameters.Add(
new SqlParameter("@Original_AnimalType", SqlDbType.VarChar, 50, ParameterDirection.Input, false00"AnimalType", DataRowVersion.Original, null));

        
// Initial Adapter
        m_daAnimals.SelectCommand = m_selectAnimalsCommand;
        m_daAnimals.UpdateCommand 
= m_updateAnimalCommand;
        m_daAnimals.InsertCommand 
= m_insertAnimalCommand;
        m_daAnimals.DeleteCommand 
= m_deleteAnimalCommand;

    }


    [WebMethod]
    
public DataSetAnimals GetAnimals()
    
{
        DataSetAnimals dsAnimals 
= new DataSetAnimals();
        m_daAnimals.Fill(dsAnimals.Animals);

        
return dsAnimals;
    }


    [WebMethod]
    
public DataSetAnimals UpdateAnimals(DataSetAnimals dsAnimals)
    
{
        
do
        
{
            
try
            
{
                m_daAnimals.Update(dsAnimals.Animals);
                
break;
            }

            
catch (DBConcurrencyException dbEx)
            
{
                SqlCommand cmd 
= m_conn.CreateCommand();
                cmd.CommandText 
= "GetAnimal";
                cmd.CommandType 
= CommandType.StoredProcedure;

                SqlDataAdapter da 
= new SqlDataAdapter(cmd);
                da.SelectCommand.Parameters.AddWithValue(
"@AnimalID", dbEx.Row["AnimalID"]);

                DataSet ds 
= new DataSet();
                da.Fill(ds);

                
if (ds.Tables[0].Rows.Count > 0)
                
{
                    DataRow proposedRow 
= dbEx.Row.Table.NewRow();
                    DataRow databaseRow 
= ds.Tables[0].Rows[0];

                    proposedRow.ItemArray 
= dbEx.Row.ItemArray;

                    dbEx.Row.Table.Columns[
"AnimalID"].ReadOnly = false;
                    dbEx.Row.ItemArray 
= databaseRow.ItemArray;
                    dbEx.Row.AcceptChanges();
                    dbEx.Row.ItemArray 
= proposedRow.ItemArray;
                    dbEx.Row.Table.Columns[
"AnimalID"].ReadOnly = true;

                    
// 更新失败,返回失败数据,含错误行
                    return dsAnimals;
                }

                
else
                
{
                    dbEx.Row.Delete();
                    dbEx.Row.AcceptChanges();
                }

            }

        }
 while (true);

        
// 更新成功,返回最新数据
        dsAnimals.Clear();
        m_daAnimals.Fill(dsAnimals.Animals);

        
return dsAnimals;
    }

}

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

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

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

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

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

其它代码一样。

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

详细可参看IssueVision。


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