在UPDATE上使用Entity Framework的OptimisticConcurrencyException会影响0行

时间:2011-06-07 10:32:08

标签: entity-framework-4

我有一个数据结构:

SecurityPolicy 1< --- * SecurityPolicyRule

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

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

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

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

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]=@Name 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]=@Name,
                [Comment]=@Comment
                where id=@id and [timestamp]=@timestamp
        IF @@ROWCOUNT>0
            SELECT [Timestamp] AS newTimeStamp FROM M2_Core_SecurityPolicy WHERE id=@id
    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]=@RoleName
    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]=@id

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

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

    end

    RETURN

go

我正在使用以下代码进行测试:

  1. 制定安全政策
  2. 将创建的安全策略规则添加到安全策略
  3. 添加安全政策
  4. 保存更新
  5. 将1添加到安全策略规则的等级
  6. 保存更新
  7. 测试如下:

    [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   用户代码未处理   Message =存储更新,插入或   删除语句影响了   意外的行数(0)。   实体可能已被修改或   自实体加载后删除。   刷新ObjectStateManager条目   来源= System.Data.Entity的
      堆栈跟踪:          在System.Data.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64)   rowsAffected,UpdateCommand source)          在System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager)   stateManager,IEntityAdapter适配器)          在System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager   entityCache)          在System.Data.Objects.ObjectContext.SaveChanges(SaveOptions   选项)          在System.Data.Objects.ObjectContext.SaveChanges()          在MIGTurbo2.Core.Data.CoreEntities.Save()   在   d:\ dev的\ migturbo2.0 \ MIGTurbo2.Core \数据\ Core.Context.cs:线   92          在MIGTurbo2.Repositories.CoreUnitOfWork.Save()   在   d:\ dev的\ migturbo2.0 \ MIGTurbo2.Repositories \ CoreUnitOfWork.cs:行   26          在MIGTurbo2.Core.Tests.IntegrationTests.SecurityPolicyRepositoryTest.AddWithSecurityPolicyRuleChangeRankTest()   在   d:\ dev的\ migturbo2.0 \ MIGTurbo2.Core.Tests \ IntegrationTests \ SecurityPolicyRepositoryTest.cs:行   524 InnerException:

    果然,没有数据发生变化。即。第一次更新时[Rank]仍为1(因此,INSERT)。但是,通过SQL事件探查器和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,@Timestamp=@t
    

    工作正常。 EF内部发生阻止呼叫的内容

    更新2

    通过突破代码,我发现发生以下情况:

    1. 创建项目(显然,时间戳为空)
    2. 添加项目(时间戳仍为空)
    3. 保存更改(这会发出INSERT)
    4. 然后,[时间戳]字段不会从数据库
    5. 更新
    6. 因此,后续UPDATE失败,因为[Timestamp] IS NULL
    7. 那么为什么[Timestamp]字段不会被更新?

2 个答案:

答案 0 :(得分:3)

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

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

在调用保存之前同步实体和数据库。

对于解释乐观并发的好帖子,请参阅 http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/457f2196-dd21-4188-8185-2561b954c54b 要么 http://msdn.microsoft.com/en-us/library/bb738618.aspx

答案 1 :(得分:1)

似乎我可能误解了Julie Lerman的书,或者她如何实施她的存储过程需要稍作修改。

我更改了模型和存储过程,以便SProcs返回时间戳,模型会选择它。因此,这意味着[Timestamp]字段不会为空。

所以INSERT SProc现在看起来像:

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]=@Name
    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 ID=@id
        END

    end

go

并更改映射,以便它选择“新”字段:

Model - Mapping details