OptimisticConcurrencyException с Entity Framework на UPDATE влияет на 0 строк - PullRequest
1 голос
/ 07 июня 2011

У меня есть такая структура данных:

SecurityPolicy 1 <--- * SecurityPolicyRule </p>

Следовательно, SecurityPolicy может иметь 0, один или несколько SecurityPolicyRules.

Я использую книгу Entity Framework Джулии Лерман для реализации некоторой степени проверки параллелизма, поддержки 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. Сохраняет обновления

Тест приведен ниже:

[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.
Source = System.Data.Entity
StackTrace: в System.Data.Mapping.Update.Internal.UpdateTranslator.ValidateRowsActed (Int64 rowAActed, UpdateCommand source) в System.Data.Mapping.Update.Internal.UpdateTranslator.Update (IEntityStateManager stateManager, адаптер IEntityAdapter) в System.Data.EntityClient.EntityAdapter.Update (IEntityStateManager entityCache) в System.Data.Objects.ObjectContext.SaveChanges (SystemOata.baseOhange).) в MIGTurbo2.Core.Data.CoreEntities.Save () в D: \ dev \ migturbo2.0 \ MIGTurbo2.Core \ Data \ Core.Context.cs: строка 92 в MIGTurbo2.Repositories.CoreUnitOfWork.Save () в D:\ dev \ migturbo2.0 \ MIGTurbo2.Repositories \ CoreUnitOfWork.cs: строка 26 в MIGTurbo2.Core.Tests.IntegrationTests.SecurityPolicyRepositoryTest..cs: ​​строка 524 InnerException:

И, конечно же, данные не изменились.то есть.[Ранг] по-прежнему 1 из первого обновления (следовательно, ВСТАВКА).Однако, запустив его через SQL Profiler и Ayende EF Profiler, в базу данных даже не производятся ОБНОВЛЕНИЯ.Значит, актуальность метки времени / обращения строк определенно ... не имеет значения?

Что может быть причиной этого?Я не хочу обновлять БД при каждом сохранении!

Обновление 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. Поле [Timestamp] затем НЕ ОБНОВЛЯЕТСЯ из БД
  5. Следовательно, последующее ОБНОВЛЕНИЕ завершается ошибкой, так как [Timestamp] IS NULL

Так почему быполе [Timestamp] не обновляется?

Ответы [ 2 ]

3 голосов
/ 10 июня 2011

Вообще говоря, это потому, что временная метка объекта в 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 голос
/ 13 июня 2011

Похоже, что там я мог неправильно понять книгу Джули Лерман, или требуется небольшое изменение в том, как она реализует свои хранимые процедуры.

Я изменил модель и хранимые процедуры так, что 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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...