附加从多个源检索的对象

时间:2013-04-23 17:26:17

标签: c# entity-framework

我有三个类,Fish(分别包含Chips和MushyPeas类型的两个属性),MushyPeas(包含Chips类型的属性)和Chips(具有Name属性)。

我正在运行以下假设代码:

int chipsId;
using (var db = new FishContext())
{
    var creationChips = new Chips() { Name = "A portion of chips" };
    db.Chips.Add(creationChips);
    db.SaveChanges();
    chipsId = creationChips.ChipsId;
}

Chips retrievedChips1;
using (var db = new FishContext())
{
    retrievedChips1 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0];
}

Chips retrievedChips2;
using (var db = new FishContext())
{
    retrievedChips2 = db.Chips.Where(x => x.ChipsId == chipsId).ToList()[0];
}

using (var db = new FishContext())
{
    db.Chips.Attach(retrievedChips1);
    db.Chips.Attach(retrievedChips2);

    var mushyPeas = new MushyPeas() { Chips = retrievedChips2 };

    var fish = new Fish() { Chips = retrievedChips1, MushyPeas = mushyPeas };
    db.Fish.Add(fish);
    db.ChangeTracker.DetectChanges();
    db.SaveChanges();
}

这是为了模拟我的真实应用程序中的情况,其中EF对象(实际上可能代表相同的数据库记录)从各种不同的DbContexts加载,然后添加到另一个DbContext中的对象树。

如果我没有调用两个db.Chips.Attach行,那么当Fish对象保存到数据库并分配新ID时会创建全新的Chips实体。

调用db.Chips.Attach为其中一个检索到的obejct解决了这个问题,但是第二个Attach调用失败,异常“ObjectStateManager中已存在具有相同键的对象.ObjectStateManager无法跟踪具有相同键的多个对象键“。

实现我想要实现的目标的最佳途径是什么?

2 个答案:

答案 0 :(得分:1)

作为一个头发花白的EF兽医,我得出的结论是,在许多情况下最好避免使用Attach

异常"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key"通常会产生误导,因为您尝试附加的对象实际上并未附加到数据上下文。附加对象时会发生什么,它会以递归方式附加它引用的任何实体。因此,如果您将实体附加到数据上下文,然后附加另一个引用先前隐式附加的实体的实体,您将收到此错误。解决方案非常简单:

using (var db = new FishContext())
{
    var chips1 = db.Chips.Find(retrievedChips1.Id);
    var chips2 = db.Chips.Find(retrievedChips2.Id);

    var mushyPeas = new MushyPeas() { Chips = chips2 };

    var fish = new Fish() { Chips = chips1, MushyPeas = mushyPeas };
    db.Fish.Add(fish);
    db.ChangeTracker.DetectChanges();
    db.SaveChanges();
}

这保证了两个实体都将附加到数据上下文,而不会出现任何类型的ObjectStateManager问题。

答案 1 :(得分:0)

您可以查询Local集合以检查是否已经附加了具有相同密钥的实体,如果是,则使用附加的实体:

using (var db = new FishContext())
{
    var attachedChips1 = db.Chips.Local
        .SingleOrDefault(c => c.ChipsId == retrievedChips1.ChipsId);
    if (attachedChips1 == null)
    {
        db.Chips.Attach(retrievedChips1);
        attachedChips1 = retrievedChips1;
    }

    var attachedChips2 = db.Chips.Local
        .SingleOrDefault(c => c.ChipsId == retrievedChips2.ChipsId);
    if (attachedChips2 == null)
    {
        db.Chips.Attach(retrievedChips2);
        attachedChips2 = retrievedChips2;
    }

    var mushyPeas = new MushyPeas() { Chips = attachedChips2 };

    var fish = new Fish() { Chips = attachedChips1, MushyPeas = mushyPeas };

    //...
}

(在这个简单的例子中,第一次检查没有意义,因为新的上下文是空的,没有附加任何东西。但是你明白了......)

但是,如果您还想更新相关实体(例如在附加后将状态设置为Modified),那么retrievedChips1retrievedChips2会出现问题具有(键值除外)不同的属性值。你必须以某种方式决定哪个是“正确的”。但那将是商业逻辑。你只需将其中一个移交给EF而只需一个。在你的场景中,你使用哪一个并不重要,因为你只是创建一个关系,而这个EF只关心关键值。

旁注:代替...ToList()[0],更自然的方式是...First()(或Single(),因为您正在查询密钥。