2011-06-07 77 views
1

我有一个数据结构,使得:OptimisticConcurrencyException实体框架上的UPDATE,影响0行

SECURITYPOLICY 1 < --- * SecurityPolicyRule

因此,SECURITYPOLICY可以有0个,一个或多个SecurityPolicyRules。

我正在使用Julie Lerman的实体框架书来实现某种程度的并发检查,TDD和POCO支持。

我知道每个表都应该有一个rowversion/timestamp字段,它被标记为ConcurrencyMode == Fixed。

我决定在存储过程中实施CUD。我的UPDATE存储过程如下:

create PROCEDURE dbo.sp_M2_Core_UpdateSecurityPolicy 
    @ID int, 
    @Name nvarchar(256), 
    @Comment nvarchar(max)=null, 
    @timestamp timestamp 
AS 

declare @nameExists nvarchar(256) 

    select @nameExists= [Name] from M2_Core_SecurityPolicy where [Name][email protected] and [ID]<>@id 
    if (not @nameExists is null) 
    begin 
     raiserror (N'Name is already in use: %s', 
     11, 
     1, 
     @Name) 
    end 
    else 
    begin 
     update M2_Core_SecurityPolicy 
      set [Name][email protected], 
       [Comment][email protected] 
       where [email protected] and [timestamp][email protected] 
     IF @@ROWCOUNT>0 
      SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicy WHERE [email protected] 
    end 

go 

create PROCEDURE dbo.sp_M2_Core_UpdateSecurityPolicyRule  
    (
    @id int, 
    @RoleName nvarchar(256), 
    @Rank int, 
    @CanReadExecute bit=null, 
    @CanWrite bit=null, 
    @CanDelete bit=null, 
    @CanExport bit=null, 
    @Timestamp timestamp 
    ) 

AS 

    declare @roleExists nvarchar(256) 
    declare @securityPolicyID int 

    select @roleExists= [RoleName] from vw_aspnet_Roles where [RoleName][email protected] 
    if (@roleExists is null) 
    begin 
     raiserror (N'Role is not defined: %s', 
     11, 
     1, 
     @roleName) 
    end 
    else 
    begin 
     select @securityPolicyID=[SecurityPolicyID] from M2_Core_SecurityPolicyRule where [id][email protected] 

     -- move all other rules up in priority 
     IF (SELECT COUNT(*) FROM M2_Core_SecurityPolicyRule WHERE [ID]<>@ID AND [SecurityPolicyID][email protected] AND [Rank][email protected]) > 0 
     BEGIN 
      UPDATE M2_Core_SecurityPolicyRule 
       SET [Rank]=[Rank]+1 
       WHERE [Rank] >= @rank 
        AND [SecurityPolicyID][email protected] 
        AND [ID]<>@ID 
     END 

     update M2_Core_SecurityPolicyRule 
      set [RoleName][email protected], 
       [Rank][email protected], 
       [CanReadExecute][email protected], 
       [CanWrite][email protected], 
       [CanDelete][email protected], 
       [CanExport][email protected]    
       where [email protected] and [timestamp][email protected] 
     IF @@ROWCOUNT>0 
      SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicyRule WHERE [email protected] 

    end 

    RETURN 

go 

我测试这个使用一些代码:

  1. 创建一个安全策略
  2. 添加一个创建的安全策略规则的安全策略
  3. 再添安全策略
  4. 保存更新
  5. 将1添加到安全策略规则的等级
  6. 保存更新

测试低于:

[TestMethod()] 
     public void AddWithSecurityPolicyRuleChangeRankTest() 
     { 
      ICoreContext coreContext = new CoreEntities(_coreDbConnectionString); 
      CoreUnitOfWork coreUnitOfWork = new CoreUnitOfWork(coreContext); 
      SecurityPolicyRepository target = new SecurityPolicyRepository(coreUnitOfWork); 
      int originalCount = coreContext.SecurityPolicies.Count(); 
      string securityPolicyName = "addwithsecuritypolicyrulechangeruletest"; 
      int originalRank = 1; 
      SecurityPolicy entity = new SecurityPolicy() 
      { 
       Comment = null, 
       Name = securityPolicyName, 
       SecurityPolicyRules = new FixUpCollection<SecurityPolicyRule>() 
      }; 
      entity.SecurityPolicyRules.Add(
       new SecurityPolicyRule() 
       { 
        CanDelete = null, 
        CanExport = null, 
        CanReadExecute = null, 
        CanWrite = null, 
        Rank = originalRank, 
        RoleName = "User" 
       }); 
      target.Add(entity); 
      coreUnitOfWork.Save(); 

      entity.SecurityPolicyRules[0].Rank=originalRank+1; 
      coreUnitOfWork.Save(); // <-- exception thrown here 
      SecurityPolicy savedSecurityPolicy = target.GetAll().Single(q => q.Name.Equals(securityPolicyName, StringComparison.CurrentCultureIgnoreCase)); 
      Assert.AreEqual(originalRank+1,savedSecurityPolicy.SecurityPolicyRules[0].Rank); 
     } 

然而,当我运行它,它抛出在突出显示的行异常。唯一的例外是:

System.Data.OptimisticConcurrencyException 了未处理由用户代码
消息=商店更新,插入,或 删除语句受影响的行(0)的 意想不到数。 实体可能已被修改或 已删除,因为实体已加载。 刷新ObjectStateManager条目。
源= System.Data.Entity的
堆栈跟踪: 在System.Data.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64类型 的RowsAffected,更新命令源) 在System.Data.Mapping.Update.Internal.UpdateTranslator。更新(IEntityStateManager stateManager,IEntityAdapter适配器) 在System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache) 在System.Data.Objects.ObjectContext.SaveChanges(SaveOptions 选项) 在System.Data.Objects.ObjectContext .SaveChanges() at MIGTurbo2.Core.Data.CoreEntities.Save()在 d:\ dev的\ migturbo2.0 \ MIGTurbo2.Core \数据\ Core.Context.cs:线在MIGTurbo2.Repositories.CoreUnitOfWork.Save() 在 d:\ dev的\ migturbo2.0 \ MIGTurbo2.Repositories \ CoreUnitOfWork.cs:行在MIGTurbo2.Core.Tests。IntegrationTests.SecurityPolicyRepositoryTest.AddWithSecurityPolicyRuleChangeRankTest() 在 d:\ dev的\ migturbo2.0 \ MIGTurbo2.Core.Tests \ IntegrationTests \ SecurityPolicyRepositoryTest.cs:行 524的InnerException:

果然,没有数据已经​​改变。即。 [Rank]在第一次更新时仍然是1(因此,INSERT)。但是,通过SQL Profiler和Ayende的EF Profiler运行它,甚至不需要调用数据库来进行更新。所以时间戳/ rowversion的相关性肯定是不相关的?

这可能是什么原因造成的?我不想在每次保存时刷新数据库!

更新1

已经运行应该执行SQL:

declare @t timestamp 
select @t=[timestamp] from M2_Core_SecurityPolicyRule where ID=1 
exec [sp_M2_Core_UpdateSecurityPolicyRule] @id=1, @roleName='User',@Rank=2,@[email protected] 

它工作正常。有内部发生的是EF东西阻塞调用

更新2

通过通过密码破译,我觉得会出现以下情况:

  1. 创建的项目(明显,时间戳为空)
  2. 该项目被添加(时间戳仍为空)
  3. 保存的更改(此问题INSERT)
  4. 的[时间戳]字段,然后不从DB
  5. 因此更新后,后续的更新失败,因为[时间戳] IS NULL

那么,为什么会在[时间戳]字段不能更新?

回答

1

看来,有可能我误解了朱莉·勒曼的书或有需要,她是如何实现自己的存储过程略有变化。

我已经改变了模式和存储过程使得存储过程返回的日期和时间以及型号它捡起。因此这意味着[时间戳]字段不会为空。

所以INSERT存储过程现在看起来像:

create PROCEDURE dbo.sp_M2_Core_InsertSecurityPolicy 
    @Name nvarchar(256), 
    @Comment nvarchar(max)=null 
AS 

    declare @nameExists nvarchar(256) 
    declare @id int 

    select @nameExists= [Name] from M2_Core_SecurityPolicy where [Name][email protected] 
    if (not @nameExists is null) 
    begin 
     raiserror (N'Name is already in use: %s', 
     11, 
     1, 
     @Name) 
    end 
    else 
    begin 

     INSERT INTO M2_Core_SecurityPolicy 
      ([Name],Comment) 
      values 
      (@Name,@Comment) 

     IF @@ROWCOUNT > 0 
     BEGIN 
      SET @id=SCOPE_IDENTITY() 
      SELECT @id as ID,[Timestamp] FROM M2_Core_SecurityPolicy WHERE [email protected] 
     END 

    end 

go 

和映射改变,这样它拿起 “新” 领域:

Model - Mapping details

3

一般来说,这是因为objectstatemanager中实体的时间戳不再匹配数据库中的内容。

调用 coreContext.Refresh(RefreshOptions.StoreWins(或.ClientWins取决于你想要的),实体);

调用保存之前同步的实体和DB。

对于一个好的文章,解释乐观并发看到 http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/457f2196-dd21-4188-8185-2561b954c54bhttp://msdn.microsoft.com/en-us/library/bb738618.aspx

+0

感谢Amasuriel。我已经更新了我的问题以包含我的新发现(更新2)。由于我正在抽象出EF,我会发现很难使用Context.Refresh,因为我需要发送需要刷新的确切对象。另外,我想避免这种情况。数据库没有任何活动。这是“从记忆中”发生的。 – 2011-06-13 09:07:25