不进行跟踪更新时在Entity Framework中保留导航属性

时间:2018-07-30 13:14:03

标签: c# entity-framework navigation-properties

我正在按照存储库模式开发一个程序,该程序涉及使用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;
}

1 个答案:

答案 0 :(得分:1)

我找到了问题的答案。

实体框架从分离的实体更新时不管理关系(有关更多详细信息,请参见herethis的答案),因此必须完成手动,这有点令人遗憾,因为在添加时默认情况下会管理关系。

我找到了一个应该用来自动链接已分离实体上的更新关系的库(这正是我想要的),但是我似乎无法使其正常工作。它称为GraphDiff,以备您查看(那里还有更多,也可以搜索它们)。

感谢所有人在评论中。


编辑:,在完成之后,我已经设法手动解决了我的问题。这是一对多关系的伪代码。假设您有权访问当前实体(我将称为entity),并且该实体中有一个包含“许多”部分的列表:

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()实体恢复为上下文,以便实体框架可以跟踪对关系所做的更改,然后进行更改:

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)关系,则需要使用entity的所有列表更新当前的previous,然后调用{{1} },最后使用列表。

如果您还有其他方法可以做到,而不必“将实体附加到上一个列表的上下文中”,请在评论中告诉我,我们将不胜感激。无论如何,我希望它对您中的任何人都有用。