0

我有三个类,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 对象(实际上可能表示相同的数据库记录)从各种不同的 DbContext 加载,然后添加到另一个 DbContext 中的对象树中。

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

调用 db.Chips.Attach 解决了检索到的对象之一的此问题,但第二次 Attach 调用失败,并出现异常“ObjectStateManager 中已存在具有相同键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象。”

在这里实现我想要实现的目标的最佳方法是什么?

4

2 回答 2

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 问题。

于 2013-04-23T19:14:36.930 回答
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,这将是一个问题。你必须以某种方式决定哪个是“正确的”。但这将是业务逻辑。您只需要将其中一个交给 EF,而且只有一个。在您的场景中,您使用哪一个并不重要,因为您只是在创建一个关系,并且对于这个 EF 只会关心键值。retrievedChips1retrievedChips2

旁注:而不是...ToList()[0]更自然的方式...First()(或者Single()在这种情况下,因为您正在查询密钥)。

于 2013-04-23T18:31:23.267 回答