重新附加分离的实体会引发异常

时间:2016-03-14 12:19:14

标签: c# .net entity-framework

我正在为我的项目使用Entity Framework 6(使用生成的模型和DbContext)。我在我的程序中使用的架构是从应用程序中抽象出数据访问层。这意味着我的实体将主要处于分离状态。

似乎重新附加任何先前连接的实体似乎是不可能的(或者我没有正确地做到这一点)。

我已经单独测试了一个案例并且能够缩小问题范围。以下是步骤。

  1. 从上下文中获取实体并处理上下文。这会分离实体。

    public async Task<ArchiveEntry> GetEntry(string api, string id)
    {
        // Omitted some code for simplicity
        ArchiveEntry entry;
    
        using (var db = new TwinTailDb())
        {
            entry = await db.ArchiveEntries.Where(a => a.Id == id).SingleOrDefaultAsync();
        }
    
        return entry;
    }
    
  2. 保存实体,无论其是否已更改。

    public async Task Save()
    {
        // Omitted some code for simplicity
        using (var db = new TwinTailDb())
        {
            db.ArchiveEntries.AddOrUpdate(a => a.Id, this);
            await db.SaveChangesAsync();
        }
    }
    
  3. 最后,重新附加分离的实体。

    public async Task RevertChanges()
    {
        using (var db = new TwinTailDb())
        {
            if (db.Entry(this).State == EntityState.Detached && Id != 0)
            {
                db.ArchiveEntries.Attach(this);
                //await db.Entry(this).ReloadAsync(); // Commented since this is irrelevant
            }
        }
    }
    
  4. 然后我运行此函数以使用上面的代码。

    public async Task Test()
    {
        ArchiveEntry entry = await ArchiveService.GetEntry(null, "7");
        await entry.Save();
        await entry.RevertChanges();
    }
    
  5. 然后它抛出了这个错误:

      

    附加类型为&#39; TwinTail.Entities.ArchiveEntry&#39;的实体失败,因为同一类型的另一个实体已具有相同的主键值。使用&#39;附加&#39;方法或将实体的状态设置为“未更改”#39;或者&#39;修改&#39;如果图中的任何实体具有冲突的键值。这可能是因为某些实体是新的并且尚未收到数据库生成的键值。在这种情况下,请使用&#39;添加&#39;方法或“添加”#39;实体状态跟踪图形,然后将非新实体的状态设置为“未更改”。或者&#39;修改&#39;酌情。

    这是一个重点。如果我跳过第2步,则不会抛出异常。

    我的猜测是实体被修改并保存在不同的上下文中。如果跳过第2步,则实体保持不变,因此重新连接它不会造成问题(只是猜测)。但是,此实体已处于分离状态,因此这应该是无关紧要的。

    另一点,ChangeTracker在这些步骤中不包含任何内容。此外,如果我对分离的实体执行任何上下文操作,它会抛出一个异常,说它应该首先附加。我还注意到内部_entitywrapper仍然引用旧的上下文。

    所以,最后,问题就在这里。如何正确地重新附加实体以及为什么会发生此异常。

    我在一个不同的问题(How to revert changes on detached entities)中问了类似的问题,但我觉得我需要发布一个新问题,因为这更通用。

2 个答案:

答案 0 :(得分:1)

  

我在我的程序中使用的架构是数据访问层是从应用程序中抽象出来的。

看起来您正在$$类本身上实现这些方法。这不是一个抽象的数据访问层,将实体传递给许多这样的短期上下文会让你陷入困境。

您应该将该代码放入与实体分离的类中,而不是为实体类提供自己的方法来管理持久性问题,并确保一旦实体附加到上下文,您将继续使用相同的上下文直到它被处置了。处理完上下文后,如果要对实体执行其他操作,则应在尝试使用它执行任何(附加)操作之前从新上下文中检索它。

答案 1 :(得分:0)

我使用的是AsNoTracking(),所以对我来说异常是奇怪的。以下代码解决了我的代码中的问题。我认为我可以在try块中使用代码,而仅使用catch块,但是我从未测试过该概念。

带有PrimaryKey的实体基类

public class EntityBase<K>
{
    [NotMapped]
    public virtual K PrimaryKey {get;}
}

更新功能

public static void Update<T,K>(DbContext context, T t) where T:EntityBase<K>
{
    DbSet<T> set = context.Set<T>();
    if(set==null) return;
    if(context.Entry<T>(t).State == EntityState.Detached)
    {
         try
         {
             T attached = set.Attached(t);
             context.Entry<T>(attached).State = EntityState.Modified;
         }catch(Exception ex)
         {
             T found = set.Find(t.PrimaryKey);
             context.Entry(found).CurrentValues.SetValues(t);
         }
     }
     context.SaveChanges();
}

扩展功能

public static void Update<T,K>(this DbContext context, T t) where T:EntityBase<K> => Update<T,K>(context, t);