在没有跟踪的情况下更新时保留 Entity Framework 中的导航属性

Preserving navigation properties in Entity Framework when updating without tracking

我正在按照 Repository 模式开发一个程序,该模式涉及使用 Entity Framework 进行数据库访问,但我遇到了一个问题。当我尝试在同一上下文中两次操作同一变量时,我遇到了这个异常:

System.InvalidOperationException: Attaching an entity of type
'Syn.Commons.Data.Tests.EntityFramework.Helper.DbModel.DBBox' failed because 
another entity of the same type already has the same primary key value.
This can happen when using the 'Attach' method or [...]

我知道这是因为实体在同一上下文中被多次使用。但是,当我尝试使用 Detach() 方法时,它会丢失所有导航属性(即,所有这些 变成 null edit : 重置为最后已知值), as stated here(分离对象部分):

 In an independent association, the relationship information is not maintained for a detached object.

如果我进行查询,我会使用 AsNoTracking(),但这是不可能的,因为我没有使用它们。这是 Update() 方法:

public bool Update(T entity)
{
    // Some code to get dbEnt

    _context.Set<TDbType>().Attach(dbEnt);

    // The following gives an error when manipulating the entity for the second time
    //_context.Entry<TDbType>(dbEnt).State = EntityState.Modified;

    _context.SaveChanges();

    // The following makes the entity "lose" all of its navigation properties
    //DetachEntry(_context.Entry(dbEnt));

    return true;
}

这两种解决方案似乎相互冲突。有没有办法在保留其导航属性的同时不跟踪实体(没有显式查询)?


编辑: 评论中的人是对的。虽然没有坏处,但我不需要Attach()。因此,该方法现在看起来像这样:

public bool Update(T entity)
{
    // Some code to get dbEnt
    _context.Entry<TDbType>(dbEnt).State = EntityState.Modified;
    _context.SaveChanges();
    return true;
}

我找到了问题的答案。

Entity Framework 从分离的实体更新时不管理关系(有关详细信息,请参阅here and 的答案),因此必须这样做手动,这有点难过,因为添加时默认管理关系。

我找到了一个库,它应该自动 link 分离实体的更新关系(这正是我想要的),但我似乎无法让它工作。叫GraphDiff,如果你想看的话(那边还有很多,你也可以搜索)。

感谢评论中的每一个人。


编辑: 经过努力,我成功地解决了我的问题。这是一对多关系的伪代码。假设您可以访问当前实体(我将其称为 entity),并且该实体中有一个包含 "many" 部分的列表:

var previous = Find_Previous_Entity();

// Find list differences from the database entity (you may need a comparer)
var added = entity.OneToManyList.Except( previous.OneToManyList );  // ToList() optional but very recommended
var deleted = previous.OneToManyList.Except( entity.OneToManyList );  // Same as above

// Make the appropriate changes
foreach( var item in deleted )
{
    item.TheForeignKey = null;  // Foreign key related with the entity
    myContext.Entry(item).State = EntityState.Modified;
}
foreach( var item in added )
{
    item.TheForeignKey = entity.ThePrimaryKey;
    myContext.Entry(item).State = (item.Id == 0) ? EntityState.Added : EntityState.Modified;
}

应该是这样。现在,关于多对多关系,它变得有点棘手。我所做的是将新实体的列表恢复到它们的原始值(数据库中的那个),Attach() 实体到上下文,以便 Entity Framework 可以跟踪对关系所做的更改,并且然后更改它们:

var previous = Find_Previous_Entity();

// Find list differences from the database entity (you may need a comparer)
var added = entity.ManyToManyList.Except( previous.ManyToManyList );  // ToList() optional but very recommended
var deleted = previous.ManyToManyList.Except( entity.ManyToManyList );  // Same as above

// Restore the current list to the value held in the database
entity.ManyToManyList.Clear();
foreach( var item in previous.ManyToManyList )
{
    // You may want to insert the line below to avoid attaching both the previous entity and the current one to the context, it gets messy then
    item.OtherManyToManyList.Remove(previous);
    entity.ManyToManyList.Add(item);
}
// The new entity now has a copy of the previous list

//Attach the entity with the previous list
myContext.Set<ClassOfEntity>().Attach(entity);
// Entity Framework will now acknowledge the changes made in the list and automatically create the appropriate relationships

foreach( var item in deleted )
{
    entity.ManyToManyList.Remove(item);
    myContext.Entry(item).State = EntityState.Modified;
}
foreach( var item in added )
{
    entity.ManyToManyList.Add(item);
    myContext.Entry(item).State = (item.Id == 0) ? EntityState.Added : EntityState.Modified;
}

多对多部分并​​不漂亮,但很管用。您可能已经注意到,如果您有许多多对多 (hehe) 关系,您将需要使用 previous 的所有列表更新当前 entity,然后调用 Attach() 最后使用列表。

如果没有"attaching the entity to the context with the previous list"还有其他方法可以做到,请在评论中告诉我,将不胜感激。不管怎样,希望对大家有用。