实体框架条目更改跟踪问题

Posted

技术标签:

【中文标题】实体框架条目更改跟踪问题【英文标题】:Entity Framework Entry Change Tracking Issues 【发布时间】:2012-10-26 17:01:05 【问题描述】:

我有下面列出的 Profile 模型。此模型通过 ajax 调用发送到客户端,然后通过另一个 ajax 调用更新并发送回服务器。

当客户端进行更改时,他们负责设置他们正在使用的模型的 ObjectState。例如,如果他们正在更改 Name 属性,他们会将 Name 属性设置为新值,并将 ObjectState 属性设置为 2(已修改),并向地址添加新地址,他们会将新地址的 ObjectState 设置为 1 (添加)。这一切都很好,ApiController 正确接收新的 Profile 对象。

public enum ObjectState

    Unchanged = 0,
    Added = 1,
    Modified = 2,
    Deleted = 3


public interface IObjectState

    ObjectState ObjectState  get; set; 


static class EntityStateHelper

    public static EntityState ConvertState(ObjectState objectState)
    
        switch (objectState)
        
            case ObjectState.Added:
                return EntityState.Added;
            case ObjectState.Deleted:
                return EntityState.Deleted;
            case ObjectState.Modified:
                return EntityState.Modified;
            default:
                return EntityState.Unchanged;
        
    


// Models
public class Profile : IObjectState

    public int Id  get; set; 
    public string Name  get; set; 
    public DateTime? BirthDay  get; set; 

    // Navigation Properties
    public ICollection<Address> Addresses  get; set; 

    public ObjectState ObjectState  get; set; 


public class Address : IObjectState

    public int Id  get; set; 
    public string Street1  get; set; 
    public string Street2  get; set; 
    public string City  get; set; 
    public string State  get; set; 
    public string Zip  get; set; 
    public ObjectState ObjectState  get; set; 

在我的 ProfileRepository 内部,我实际上已经分离了 Insert 和 Update,在 Profile 对象的情况下,客户端没有删除配置文件的服务调用,所以他们对配置文件所能做的实际上就是修改它。在我的更新方法中,我选择使用_context.Entry&lt;T&gt;(entity)_context.Entry&lt;T&gt;(entity).State = EntityState.Modified 相对,因为这会引发异常,说明ObjectStateManager 中已经存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象

public void Update(T entity)

    _context.Entry<T>(entity);
    _context.ApplyStateChanges();

首先让我说,我在 Update 方法中实际上没有 ApplyStateChanges,但对于这个示例,它将显示正在发生的事情。以下是 ApplyStateChanges 方法的代码。

public static class DbContextEntensions

    public static void ApplyStateChanges(this DbContext context)
    
        foreach (var trackableEntry in context.ChangeTracker.Entries<IObjectState>())
        
            IObjectState state = trackableEntry.Entity;
            trackableEntry.State = EntityStateHelper.ConvertState(state.ObjectState);
        
    
 

这就是问题所在。假设客户端的 Profile 是 Name 属性是 John,Addresses 属性包含 1 个地址。如果客户端将 Name 更改为 Steve 并修改 1 Address,那么当 ApplyStateChanges 被调用时,每个 DbEntityEntries 都会被标记为正确的 EntityState。当客户端将新地址添加到状态为 1(已添加)的地址集合时,就会出现问题。当 ApplyStateChanges 被调用 context.ChangeTracker.Entries&lt;IObjectState&gt;() 时,它不会将新添加的地址作为已被 ChangeTracker 更改的条目返回。

我很确定代码 _context.Entry&lt;T&gt;(entity) 应该是 _context.Entry&lt;T&gt;(entity).State = EntityState.Modified 但无论我尝试了什么,我似乎都无法绕过这个异常。

我将 EntityFramework 5.0 与 .NET 4.0 一起使用,这意味着 EntityFramework 版本是 4.4。我错过了什么?我已经阅读了我能找到的关于这个主题的每一篇文章,这看起来应该有效。我从 Julie Lerman 的 PluralSight 视频 Entity Framework in the Enterprise 中获得了这个状态变化跟踪代码。

任何帮助我指出正确的方向将不胜感激。

【问题讨论】:

【参考方案1】:

我建议在 Update 方法中使用 .Add(entity) 添加实体,这会将新地址标记为已添加并修复其导航属性。然后,当您调用 ApplyStateChanges 时,它会更正状态。

【讨论】:

【参考方案2】:

这篇文章很旧,但它可能对其他人有所帮助:

问题出在更新方法上。我假设您在断开连接模式下工作。 以下方法:

_context.Entry<T>(entity);

从上下文中获取实体。 如果它不在其中,它将使用“Detached”EntityState 添加它(及其导航属性作为地址),**不会将它添加到“context.ChangeTracker.Entries()”**。

您应该改用“.Add(entity)”或“.Attach(entity)”。唯一的区别是状态:

.Add 将使用 EntityState 添加客户及其地址:“已添加”

.Attach 将使用 EntityState 添加客户及其地址:“未更改”

当您在下面的行中更改它们的状态(再次!)时,两者都将在您的代码中工作:

_context.ApplyStateChanges();

这意味着你不关心你之前应用的状态,只要它们被跟踪(不是处于分离状态)。

我很确定代码 _context.Entry(entity) 应该是 _context.Entry(entity).State = EntityState.Modified 但无论我尝试了什么,我似乎都无法绕过这个异常。

如果你之后不执行 ApplyStateChanges(),它更新实体和它的整个图被标记为 dirty 产生意想不到的结果(当然就像添加它的图元素一样。检查这个@987654321 @ 有完整的解释)。

ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象。

这意味着您的数据库/测试中已经有相同的对象(或者更确切地说是它的导航属性之一)。 这与您的“真正”问题无关,更有可能是副作用。签入数据库的 Seed 方法,您必须有一个重复的导航实体。

【讨论】:

以上是关于实体框架条目更改跟踪问题的主要内容,如果未能解决你的问题,请参考以下文章

为啥实体框架 System.Data.Entity.Core.Objects.RelationshipEntry 错误? (用于更改跟踪)

撤消实体框架实体中的更改

实体框架 ChangeTracker 流并保存到查询

在 DbContext 中看不到实体框架数据库更改

使用实体框架恢复数据库中的更改

数据库对象跟踪