1

我有下面列出的 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 对象,客户端没有删除配置文件的服务调用,所以他们对配置文件所能做的就是修改它。在我的 Update 方法中,我选择使用_context.Entry<T>(entity)asapposed_context.Entry<T>(entity).State = EntityState.Modified导致抛出异常,指出ObjectStateManager 中已经存在具有相同键的对象。ObjectStateManager 无法使用相同的 key 跟踪多个对象

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 个地址。如果客户端将名称更改为 Steve 并修改 1 地址,那么当 ApplyStateChanges 被调用时,每个 DbEntityEntries 都会被标记为正确的 EntityState。当客户端将新地址添加到状态为 1(已添加)的地址集合时,就会出现问题。当调用 ApplyStateChanges 时,context.ChangeTracker.Entries<IObjectState>()对它的调用不会将新添加的地址作为已被 ChangeTracker 更改的条目返回。

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

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

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

4

2 回答 2

0

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

于 2013-10-29T17:18:37.567 回答
0

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

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

_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(),则其更新实体及其整个图形将被标记为,从而产生意想不到的结果(当然就像添加其图形元素一样。请检查此答案与整个解释)。

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

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

于 2015-12-08T10:10:49.667 回答