乐观并发

时间:2015-05-15 08:52:03

标签: c# entity-framework entity-framework-6

我有一个包含多个链接实体的实体框架项目。由于它被多个用户同时使用,我已经为可能被多个用户同时编辑的实体设置了RowVersion-Field。不幸的是,每次我尝试保存一个新实体时,我现在得到一个OptimisticConecurrencyException,该实体链接到一个已经存在的实体。

  

存储更新,插入或删除语句会影响意外的行数(0)。自实体加载后,实体可能已被修改或删除。有关理解和处理乐观并发异常的信息,请参阅http://go.microsoft.com/fwlink/?LinkId=472540

现在的问题是,这个错误实际上没有给出关于错误真正所在位置的任何指示。它可能是在此期间被修改的基础模型,新模型或其他东西可能存在验证错误。

我用来添加新实体的代码如下:

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

model是我想要添加到数据库的模型

编辑:如上所示,我修改了代码以忽略底层模型的更新。此外,我已经通过验证:

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

只有一个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

如果我要更新实体并且rowversion列不匹配,我会理解异常,但在这种情况下,它是一个全新的实体。有没有办法看看其中一个属性是否格式错误?

EDIT2:

而不仅仅是修剪毫秒我现在使用DateTime.Today而不是DateTime.Now工作。看似在ModificationDate上的datetime2(4)存在一些问题。我已经确保将ModificationDate截断为4毫秒,因此不应该有任何解析错误。

EDIT3:

切换回DateTime.Now并修剪它停止工作的毫秒后,实体不再插入数据库。这可能是由于sql server在基于毫秒值匹配实体时出现问题。我执行了EF生成的SQL,如上所示,带有一些虚构的值,虽然在某些情况下查询没有返回rowversion值,但它仍然通过了。就实体框架而言,客户端会将此解释为0行的返回值,因此调用并发异常。 (还应注意,ModificationDate和IDSample一起是实体的主键。)

Edit4:

我现在正在使用DateTime.Today,然后添加所需的精度,这对我有用。这可以标记为已解决。 (Altough我原本预计EF可以自己处理日期时间格式转换:/)

1 个答案:

答案 0 :(得分:1)

我遇到的问题是你在哪里添加DateTime?您正在创建太多步骤来解决此问题。创建日期时间,修改它等等。

如果您的实体是从具有映射属性的基类继承,请在DbContext覆盖SaveChanges()中进行并发添加/更新。

这是一个例子:(没有优化语法编写)

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();
    }
    ....
}