我有三个类,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无法跟踪具有相同键的多个对象键“。
实现我想要实现的目标的最佳途径是什么?
答案 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
),那么retrievedChips1
和retrievedChips2
会出现问题具有(键值除外)不同的属性值。你必须以某种方式决定哪个是“正确的”。但那将是商业逻辑。你只需将其中一个移交给EF而只需一个。在你的场景中,你使用哪一个并不重要,因为你只是创建一个关系,而这个EF只关心关键值。
旁注:代替...ToList()[0]
,更自然的方式是...First()
(或Single()
,因为您正在查询密钥。