【问题标题】:Entity framework attach: object with same key exists实体框架附加:存在具有相同键的对象
【发布时间】:2014-03-18 03:49:29
【问题描述】:

我正在构建一个 Windows 窗体应用程序,并且我使用多个 DBContext 实例(每个业务层调用大多一个)。

在处理问题几天后(在插入新实体时,他们引用的实体被添加为新实体,而不是使用现有实体),我发现问题是我必须附加现有实体根据上下文。

大约 2 小时后一切正常,然后我在附加时出错:上下文中存在具有相同键的实体。

我在附加之前尝试过测试(每种实体类型的类似方法):

    private void attachIfNeeded(POCO.Program myObject, myContext context)
    {
    if (!context.Set<POCO.Program>().Local.Any(e => e.ID == myObject.ID))
        {
            context.programs.Attach(myObject);
            return true;
        }
        else
        {
            myObject = context.Set<POCO.Program>().Local.Single(e => e.ID == myObject.ID);
            return false;
        }
}

但测试返回false,但附加时仍然失败。

所以基本上,如果我不附加,它将添加一个新实体,而不是使用现有的(和预期的)实体。如果我附上,有一个我无法弄清楚的错误。

我环顾四周(现在整天都在这样做),我实际上(认为我)知道问题出在哪里:

我尝试添加的实体有多个关系,其他实体可以通过多个路径到达。这会导致问题吗?

请帮助解决这个问题,那里的解决方案对我来说真的没有意义,也没有奏效。

我真的很接近我将尝试抓住附加语句并完成它的地步。但我会讨厌这样做。

这是我的实体(不是全部,但这应该足够了):

 public class Word
{
    [Key]
    public int ID {get;set;}

    [Required]
    public string word { get; set; }


    public WordCategories category { get; set; }

    public Word parent {get;set;}

    public List<Unit> units { get; set; }

    public Program program { get; set; }

    public List<Lesson> lessons { get; set; }

    public Word()
    {
        units = new List<Unit>();
        lessons = new List<Lesson>();
    }
}
public class Unit
{
    [Key ]
    public int ID { get; set; }

    [Required]
    public string name { get; set; }

    public string description { get; set; }

    public List<Lesson> lessons { get; set; }

    public Program program {get;set;}

    public List<Word> words { get; set; }

    public Unit()
    {
        lessons=new List<Lesson>();
        words = new List<Word>();
    }

}

这里是我调用 attach 方法的地方。第一次附加时抛出错误:

public int addWords(List<POCO.Word > words,int programID, int unitID,int lessonID)
     {
         CourseHelperDBContext context = getcontext();

         int result;



        foreach(POCO.Word a in words)
        {
            foreach (POCO.Unit b in a.units)
                attachIfNeeded(b, context);
            foreach(POCO.Lesson c in a.lessons )
                attachIfNeeded(c,context);
            attachIfNeeded(a.program,context);
            if (a.parent != null)
                attachIfNeeded(a.parent,context);
        }

         context.words.AddRange(words);
         result = context.SaveChanges();
         return result;

     }

我不敢相信我有这么多问题。我只想存储这些实体,添加一些(我还没有到要更改它们的地步)并保存它。

到目前为止,我已经想通了:

  1. 有些词是新的,有些是存在的,有些是改变的(主要是父属性);
  2. 所有单元都存在,程序和课程也存在(所以我需要附上它们);
  3. 对象图包含多个实体路径,其中一些存在,一些是新的;
  4. 我为每个请求都使用了一个新的上下文。当我一直使用相同的东西时,我遇到了其他问题。我找到了指向这种模式的解决方案,我认为这没问题,因为这就是你在 ASP MVC 项目中要做的事情。

所有这些都可能导致问题,但我不知道哪些以及如何解决这些问题。

我想我可以通过一次添加一个单词来完成这项工作,并且每次都提取程序、课程和单元……但这意味着要多次往返 DB。这不可能。

【问题讨论】:

  • 我在附加到 POCO.Lesson 之前更改了测试 localLesson = context.lessons.Find(myObject.ID);它返回true,所以它不附加。没有更多的例外,但我回到它创建一个新的单元、课程和程序,而不是使用现有的。我接近尝试将附件包含在 try catch 中的绝望方法,这可能行不通(它不会附加,因此会创建新实体)。
  • 这太令人沮丧了,这不可能这么难。
  • 如果使用通用的存储库模式会容易得多。
  • 这里的问题 - 可能是一个问题,是我添加的一些单词是新的,一些存在,还有一些被更改(它们的父级更改)。所有单元都存在,课程和计划也是如此。所以我需要附加现有实体以避免创建其他实体,并且(我认为这是一个问题)对象图包含机器人新实体和现有实体的路径。
  • 通用存储库模式看起来不错,但如果我无法弄清楚实体框架的这种简单用法,我不会转向更复杂的模式。

标签: c# .net entity


【解决方案1】:

一段时间后回到这个问题,在这种情况下的问题是我需要检索我的关系中存在的实体。

解决方案既不是附加(因为如果实体已经附加,它会失败)也不是添加(因为它已经存在于数据库中)。

我应该做的是检索与我添加的实体相关的每个实体。

这有助于: Entity Framework creating new entity with relationship to existing entity, results in attempt to create new copy of the existing entity

【讨论】:

    【解决方案2】:

    附加实体后,尝试将实体状态设置为已修改。

    context.programs.Attach(myObject);
    context.Entry(myObject).State = EntityState.Modified;
    

    我认为您的测试逻辑有误。

    如果数据库中不存在实体,您应该添加而不是附加。如果在真正应该添加时找不到实体,则您的代码正在附加。

    添加新实体的代码(创建/插入)

    context.Set<T>.Add(entity);
    

    附加实体的代码(更新)

    context.Set<T>.Attach(entity);
    context.Entry(entity).State = EntityState.Modified;
    

    如果您的代码在第一次附加时失败,那将是 attachIfNeeded(b,context); ?我认为您没有向我们展示此代码。

    【讨论】:

    • 我无法附加,它会抛出具有相同键的对象存在。
    • 这可能是真的,但它在第一个单元上失败了,我正在添加单词。所有单位都已经存在。这可能是因为我试图附加的单位与其他单词有关系,附加单位会尝试附加单词吗?
    • AttachIfNeeded 是其中的第一个代码,我将为其添加存根。
    • 您提供的 AttachIfNeeded 方法采用 POCO.Program 对象,但错误发生在采用 POCO.Unit 对象的 AttachIfNeeded 方法上。我在这里错过了什么吗?
    • 不,我对所有实体类型都有一个 attachIFNeeded,错误发生在它被调用的第一个单元中。
    【解决方案3】:

    我分享我的经验,但有同样的例外。 首先,这是我的代码:

            public void UpdateBulk(IEnumerable<Position> pDokumentPosition, DbDal pCtx)
            {
                    foreach (Position vPos in pDokumentPosition)
                    {
                        vPos.LastDateChanged = DateTime.Now;
                        pCtx.Entry(vPos).State = EntityState.Modified;
                     }
                    pCtx.SaveChanges();
            }
    

    我在 EntityState.Modified 行上遇到了同样的异常。

    在我的情况下,问题在于,当将 vPos 状态设置为已修改时,所有相关对象(vPos.Document 和 vPos.Produkt)都会以未更改的状态加载到上下文中。 在 foreach 第一步中,它没有任何异常,只是在第二步中,因为例如。相关的文档实体已经加载到内存/上下文中(因此也是文档的关键属性)。

    我该如何解决? (也许不是最好的解决方案):

    我在每一步都用这行来分离相关的实体:

            if (vPos.Dokument != null)
            {
                pCtx.Entry(vPos.Dokument).State = EntityState.Detached;
            }
    
            if (vPos.Produkt!=null)
            {
                pCtx.Entry(vPos.Produkt).State = EntityState.Detached;
            }
    

    如果你有更好的解决方案,我很期待......

    【讨论】:

      【解决方案4】:

      你可以试试这个

      context.words.Add(words);
      result=context.SaveChanges();
      

      【讨论】:

      • 我之后会这样做,但如果我之前不附加,它将创建一个新单元(以及课程和程序),而不是使用现有的单元。如果我附加,它会返回错误“上下文中存在具有相同键的对象”。
      • 另外,words 是一个列表,所以它必须是 addRange(我正在这样做)。
      • 检查 stackoverflow.com/questions/19593299/…> 和 stackoverflow.com/questions/17544705/…> 和这里 stackoverflow.com/questions/6033638/…>
      • 其中一个建议始终使用相同的上下文(好吧,相同的上下文来拉取实体并保存它们,这对我来说一直都是如此)。这是我以前做的,但我遇到了完全不同类型的问题(它说实体已被处置)。
      • 这真是令人沮丧,从实现中来回移动我真的不明白它们之间的区别,从一个问题跑到另一个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 2012-01-21
      • 2011-11-05
      相关资源
      最近更新 更多