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

时间:2012-11-06 17:25:03

标签: entity-framework

我有下面列出的个人资料模型。此模型通过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)作为_context.Entry<T>(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);
        }
    }
} 

所以这就是问题所在。让我们说客户端的配置文件是Name属性是John,Addresses属性包含1个地址。如果客户端将Name更改为Steve并修改1地址,那么当ApplyStateChanges被调用时,每个获取的DbEntityEntries都标记有正确的EntityState。当客户端向状态为1(已添加)的Addresses集合添加新地址时,会出现问题。当调用ApplyStateChanges调用context.ChangeTracker.Entries<IObjectState>()时,它不会将新添加的地址作为由ChangeTracker更改的条目返回。

我非常肯定代码_context.Entry<T>(entity)应该是_context.Entry<T>(entity).State = EntityState.Modified,但无论我尝试过什么,我都似乎无法绕过这个例外。

我正在使用带有.NET 4.0的EntityFramework 5.0,这意味着EntityFramework版本为4.4。我错过了什么?我已经阅读了我觉得每篇文章都能找到的关于这个主题的内容,看起来它应该有效。我从Julie Lerman的PluralSight视频Entity Framework in the Enterprise获得了状态更改跟踪代码。

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

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

这篇文章很老,但也可以帮助其他人:

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

_context.Entry<T>(entity);

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

您应该使用“.Add(entity)”或“.Attach(entity)”而不是。唯一的区别是州:

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

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

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

_context.ApplyStateChanges();

这意味着您无需关注之前申请的州,只要跟踪它们(不处于分离状态)。

  

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

如果您之后没有执行ApplyStateChanges(),则更新实体及其整个图形将标记为,从而产生意外结果(当然要添加其图元素。请检查此{{ 3}}与整个解释)。

  

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

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