【问题标题】:Optimistic Concurrency乐观并发
【发布时间】:2015-05-15 08:52:03
【问题描述】:

我有一个包含多个链接实体的实体框架项目。由于它同时被多个用户使用,我为可能同时由多个用户编辑的实体设置了一个 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 [dbo].[Samples]([IDSample], [ModificationDate], [IDUser])
VALUES (@0, @1, @2) 
SELECT [RowVersion]
FROM [dbo].[Samples]
WHERE @@ROWCOUNT > 0 AND [IDSample] = @0 AND [ModificationDate] = @1

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

编辑2:

我现在不只是修剪毫秒,而是使用 DateTime.Today 而不是 DateTime.Now 。似乎在 ModificationDate 上的 datetime2(4) 存在一些问题。我已经确保 ModificationDate 被截断为 4 毫秒,所以应该没有解析错误。

编辑3:

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

编辑4:

我现在使用 DateTime.Today,然后添加所需的精度,这对我有用。这可以标记为已解决。 (尽管我希望 EF 可以自行处理日期时间格式转换:/)

【问题讨论】:

  • 我认为验证不会触发 DbUpdateConcurrencyException
  • 如果模型有一个字段被截断,例如错误的 datetime2 格式,因此数据库不写入任何行并返回 0,它将触发并发异常。这是我已经发现的错误之一。
  • 你的意思是它没有告诉你模型中的哪些字段与当前行不同?
  • 是的。我不知道究竟是什么,或者更好地说是模型上的哪些字段导致了错误。
  • “标记为已解决”在此处不存在。最好的方法是写下你自己对问题的答案,详细说明解决方案,并在你能够这样做时接受它(我认为你需要两天时间才能使用该选项)。

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


【解决方案1】:

我的问题是您在哪里/在哪里添加日期时间?您正在创建太多步骤来解决这个问题。创建日期时间、修改日期时间等。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-18
    • 1970-01-01
    • 1970-01-01
    • 2015-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多