Entity Framework 4.0 在默认时并不处理并发的情况,也就是出现并发忽略它们。但EF支持处理并发的情况,有两种方法,一种是在存储过程中自行处理。另一种是EF的MODEL上增加一个TimeStamp,EF支持这个TimeStamp来处理并发。看下面EF的模型EDM中SSDL节:
1: <EntityType Name="Categories2">
2: <Key>
3: <PropertyRef Name="CategoryID" />
4: </Key>
5: <Property Name="CategoryID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
6: <Property Name="CategoryName" Type="nvarchar" MaxLength="15" />
7: <Property Name="Description" Type="ntext" />
8: <Property Name="Picture" Type="image" />
9: <Property Name="RowVersion" Type="timestamp" Nullable="false" StoreGeneratedPattern="Computed" />
10: </EntityType>
CSDL节:
1: <EntityType Name="Categories2">
2: <Key>
3: <PropertyRef Name="CategoryID" />
4: </Key>
5: <Property Type="Int32" Name="CategoryID" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
6: <Property Type="String" Name="CategoryName" MaxLength="15" FixedLength="false" Unicode="true" />
7: <Property Type="String" Name="Description" MaxLength="Max" FixedLength="false" Unicode="true" />
8: <Property Type="Binary" Name="Picture" MaxLength="Max" FixedLength="false" />
9: <Property Type="Binary" Name="RowVersion" Nullable="false" MaxLength="8" FixedLength="true" annotation:StoreGeneratedPattern="Computed" ConcurrencyMode="Fixed" />
10: </EntityType>
注意第9行,我们新增加一个列RowVersion. 它的ConcurrencyMode等Fixed,这个是关键之处。这里我们使用POCO:
1: [DataContract]
2: public partial class Categories2
3: {
5: [DataMember]
6: public virtual int CategoryID
7: {
8: get;
9: set;
10: }
11: [DataMember]
12: public virtual string CategoryName
13: {
14: get;
15: set;
16: }
17: [DataMember]
18: public virtual string Description
19: {
20: get;
21: set;
22: }
23: [DataMember]
24: public virtual byte[] Picture
25: {
26: get;
27: set;
28: }
29: [DataMember]
30: public virtual byte[] RowVersion
31: {
32: get;
33: set;
34: }
注意,那个attribute不是必须的。接下来看到主要代码:
1: using (var context = new DBEntities())
2: {
3: //Add new categroy
4: Categories2 newcategroy = new Categories2() { CategoryName = "testname" };
5: context.Categories2.AddObject(newcategroy);
6: context.SaveChanges();
7: //Get it
8: var categoriesfromdb = context.Categories2.Where(c => c.CategoryID == newcategroy.CategoryID).First();
9: Console.WriteLine("Just insert category Id: {0}", categoriesfromdb.CategoryID);
10:
11: //使用SQL执行一个Update模拟并发的情况
12: context.ExecuteStoreCommand(@"update Categories2 set CategoryName='456' where CategoryID = @p0"
13: , categoriesfromdb.CategoryID );
14:
15: categoriesfromdb.CategoryName = "testname333";
16:
17: try
18: {
19: context.SaveChanges();
20: Console.WriteLine("No concurrency exception.");
21: }
22: catch (OptimisticConcurrencyException)
23: {
24: ///出现并发冲突的处理
25: Console.WriteLine(" concurrency exception happend");
26: //testname333
27: //implement last record wins strategy
28: context.Refresh(System.Data.Objects.RefreshMode.ClientWins, categoriesfromdb);
29: //456
30: //context.Refresh(System.Data.Objects.RefreshMode.StoreWins, categoriesfromdb);
31:
32: Console.WriteLine("categoriesfromdb.CategoryName: {0} ", categoriesfromdb.CategoryName);
33: Console.WriteLine("OptimisticConcurrencyException handled and changes saved");
34:
35: context.SaveChanges();
36: }
37:
38: //Clear
39: context.DeleteObject(newcategroy);
40: context.SaveChanges();
41: }
你可以把上面的代码放到一个UnitTest中, 先增加一个Entity,然后从DB查询出,然后用T-SQL语句修改它, 后面接着又修它的属性。运行时我们将Catch到OptimisticConcurrencyException。
输出:
Just insert category Id: 58
concurrency exception happend
categoriesfromdb.CategoryName: 456
OptimisticConcurrencyException handled and changes saved
1 passed, 0 failed, 0 skipped, took 0.83 seconds (Ad hoc).
EF中有两种处理模式强制用户数据到服务器(ClientWins)和用服务器数据刷新用户数据(StoreWins),上面代码使用Context来RefreshModel.ClientWins,最后Name属性是testname333,实现了并发中“最后一个赢”的效果。相反,使用StoreWins策略那个属性的值将是456。 最后清理刚才测试的对象。
这是上面通过Command运行的Update T-SQL:
exec sp_executesql N'update Categories2 set CategoryName=''456'' where CategoryID = @p0',N'@p0 int',@p0=60
我们再看来EF是怎么实现的, SaveChange时生成的T-SQL是:
1: exec sp_executesql N'update [dbo].[Categories2]
2: set [CategoryName] = @0
3: where (([CategoryID] = @1) and ([RowVersion] = @2))
4: select [RowVersion]
5: from [dbo].[Categories2]
6: where @@ROWCOUNT > 0 and [CategoryID] = @1',N'@0 nvarchar(15),@1 int,@2 binary(8)',@0=N'testname333',@1=60,@2=0x0000000000008521
注意TimeStamp做为了where查询的子句,这样保证了这条数据的被更新。 我们还可以使用SQL SERVER 2008 中 rowversion 类型 来做为这个列的类型。
希望这篇POST对您开发有帮助。
作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
该文章也同时发布在我的独立博客中-Petter Liu Blog。