我有下面列出的个人资料模型。此模型通过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获得了状态更改跟踪代码。
任何帮助我指向正确方向的人都将不胜感激。
答案 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方法,您必须有一个重复的导航实体。