简体   繁体   English

乐观并发

[英]Optimistic Concurrency

I have an Entity Framework Project with several linked entities. 我有一个包含多个链接实体的实体框架项目。 Since it is utilized by multiple users at once I've set up a RowVersion-Field for entities which are likely to be edited by several users at once. 由于它被多个用户同时使用,我已经为可能被多个用户同时编辑的实体设置了RowVersion-Field。 Unfortunately I now get an OptimisticConecurrencyException every time I try to save a new entity, which is linked to an already existing entity. 不幸的是,每次我尝试保存一个新实体时,我现在都会得到一个OptimisticConecurrencyException ,该实体链接到一个已经存在的实体。

Store update, insert, or delete statement affected an unexpected number of rows (0). 存储更新,插入或删除语句会影响意外的行数(0)。 Entities may have been modified or deleted since entities were loaded. 自实体加载后,实体可能已被修改或删除。 See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions. 有关理解和处理乐观并发异常的信息,请参阅http://go.microsoft.com/fwlink/?LinkId=472540

The problem is now that this error doesn't really give any pointers as to where the error really lies. 现在的问题是,这个错误并没有真正给出关于错误所在位置的任何指示。 It could either be the underlying model that is modified in the meantime, there could be a validation error on the new model or something else. 它可能是在此期间被修改的基础模型,新模型或其他东西可能存在验证错误。

The code I use to add the new entity is as follows: 我用来添加新实体的代码如下:

using (ctx = new DbContext())
{
    try
    {
        ctx.Samples.Add(model);
        ctx.SaveChanges();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        LogManager.HandleException(ex.InnerException);
    }
}

model is the model i want to add to the database model是我想要添加到数据库的模型

Edit: As seen above i modified the code to ignore the update of an underlying model. 编辑:如上所示,我修改了代码以忽略底层模型的更新。 Furthermore i have verified through: 此外,我通过以下方式验证

ctx.Database.log = s => Debug.Write(s);

That only an insert statement is sent to the database and not an additional update statement. 只有一个insert语句被发送到数据库而不是另一个更新语句。

INSERT [dbo].[Samples]([IDSample], [ModificationDate], [IDUser])
VALUES (@0, @1, @2) 
SELECT [RowVersion]
FROM [dbo].[Samples]
WHERE @@ROWCOUNT > 0 AND [IDSample] = @0 AND [ModificationDate] = @1

I would understand the exception if i would update an entity and the rowversion column wouldn't match, but in this case it's a completely new entity. 如果我更新实体并且rowversion列不匹配,我会理解异常,但在这种情况下,它是一个全新的实体。 Is there a way to see if one of the properties is malformed? 有没有办法看看其中一个属性是否格式错误?

Edit2: EDIT2:

Instead of just trimming the milliseconds i now used DateTime.Today instead of DateTime.Now which works. 我现在使用DateTime.Today代替DateTime.Now而不是仅仅修剪毫秒。 Seemingly there is some problem with datetime2(4) on ModificationDate. 看似在ModificationDate上的datetime2(4)存在一些问题。 I already made sure that ModificationDate is truncated to 4 milliseconds so there should be no parse error. 我已经确保将ModificationDate截断为4毫秒,因此不应该有解析错误。

Edit3: EDIT3:

After switching back to DateTime.Now and trimming the milliseconds it stopped working and the entities are not longer inserted into the database. 切换回DateTime.Now并修剪它停止工作的毫秒后,实体不再插入数据库。 Could this be caused by the fact that the sql server has problems matching the entities based on millisecond values. 这可能是由于sql server在基于毫秒值匹配实体时出现问题。 I executed the EF generated SQL as seen above with some fictional values and it went through although on some occasions the query didn't return a rowversion-value. 我执行了EF生成的SQL,如上所示,带有一些虚构的值,虽然在某些情况下查询没有返回rowversion值,但它仍然通过了。 In terms of the entity framework, the client would interpret this as a return value of 0 lines and therefore call an concurrency-exception. 就实体框架而言,客户端会将此解释为0行的返回值,因此调用并发异常。 (It should also be of note that the ModificationDate together with the IDSample is the primary key of the entity.) (还应注意,ModificationDate和IDSample一起是实体的主键。)

Edit4: Edit4:

I'm now using DateTime.Today and then add the needed precision, which works for me. 我现在正在使用DateTime.Today,然后添加所需的精度,这对我有用。 This can be flagged as solved. 这可以标记为已解决。 (Altough i would have expected that EF can take care of datetime-format-conversion by itself :/) (Altough我原本预计EF可以自己处理日期时间格式转换:/)

The question I have is where are/were you adding the DateTime? 我的问题是你在哪里添加DateTime? You are creating too many steps to hammer out this problem. 您正在创建太多步骤来解决此问题。 Creating a datetime, modifying it, etc. 创建日期时间,修改日期时间等。

If you're entity is inheriting from a base class with mapped properties do your concurrency add/update in the DbContext override of SaveChanges(). 如果您的实体是从具有映射属性的基类继承,请在DbContext覆盖SaveChanges()中进行并发添加/更新。

Here's an example: (written without optimized syntax) 这是一个例子:(没有优化语法编写)

public abstract class EntityBase
{
   public int Id {get; set;}
   public DateTime CreationDate {get; set;}
   public DateTime? ModifyDate {get; set;}
   public string VersionHash {get; set;}
}
public static class EntityBaseExtensions
{
    public static void MyBaseEntityMapping<T>(this EntityTypeConfiguration<T> configuration) where T : EntityBase
    {
        configuration.HasKey(x => x.Id);
        configuration.Property(x => x.CreationDate)
                                 .IsRequired();
        configuration.Property(x => x.ModifyDate)
                                 .IsOptional();
        configuration.Property(x => x.VersionHash).IsConcurrencyToken();
    } 
}
public class MyEntity : EntityBase
{
   public string MyProperty {get; set;}
}
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
    public MyEntityMapping()
    {
       this.MyBaseEntityMapping();
       Property(x=>x.MyProperty).IsRequired();
    }
}

public class MyContext : DbContext
{
    ....
    public override int SaveChanges()
    {
        this.ChangeTracker.DetectChanges(); //this forces EF to compare changes to originals including references and one to many relationships, I'm in the habit of doing this.

        var context = ((IObjectContextAdapter)this).ObjectContext; //grab the underlying context
        var ostateEntries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added); // grab the entity entries (add/remove, queried) in the current context

        var stateEntries = ostateEntries.Where(x => x.IsRelationship == false && x.Entity is EntityBase); // don't care about relationships, but has to inherit from EntityBase

        var time = DateTime.Now; //getting a date for our auditing dates

        foreach (var entry in stateEntries)
        {
            var entity = entry.Entity as EntityBase;
            if (entity != null) //redundant, but resharper still yells at you :)
            {
                if (entry.State == EntityState.Added) //could also look at Id field > 0, but this is safe enough
                {
                    entity.CreationDate = time;
                }
                entity.ModifyDate = time;
                entity.VersionHash = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); //this an example of a simple random configuration of letters/numbers..  since the query on sql server is primarily using the primary key index, you can use whatever you want without worrying about query execution.. just don't query on the version itself!
            }
        }
        return base.SaveChanges();
    }
    ....
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM