更新EF中已存在于对象状态管理器中的分离对象

时间:2014-02-12 14:12:12

标签: c# entity-framework ef-code-first repository

我希望这会很容易,但对于我的生活,我无法在SO或任何其他网站上找到具体答案。

我有一个基本的存储库/单元工作模式与Entity Framework Code First。除了某些更新案例外,一切顺利。问题是我有一组Entity Framework模型对象,所有对象都以EF返回的“Db”为前缀,但我将它们转换为普通的DataContract Model对象以传递给Web层以分离关注点。我有一个基本的转换接口,它只是从DataModel对象填充WebModel对象,逐字段逐字复制。

因此,如果从ID为1的EF检索DbUser对象,然后转换为User对象,然后将该BACK转换为DbUser对象,最终得到ID为1的DbUser,但它是一个DIFFERENT对象对于你开始使用的那个,虽然它们具有相同的主键字段,但实际的CLR对象本身是不同的。

以下作品

User user;
using (var work = new UnitOfWork())
{
    var repository = new UserDataRepository(work);
    user = repository.Get(1);
    repository.save();
}

var modelUser = DataConverter.Convert(user);
modelUser.Name = "new name";
user = BusinessConverter.Convert(modelUser); 

using (var work = new UnitOfWork())
{
    var repository = new UserDataRepository(work);
    repository.Update(user);
    repository.save();
}

因为他们正在使用两个不同的工作/上下文单元,所以第二个块在ObjectStateManager中没有任何内容可以比较,并且只能在Update()方法中附加分离的对象

然而,这不起作用

using (var work = new UnitOfWork())
{
    var repository = new UserDataRepository(work);
    user = repository.Get(1);
    repository.save();

    var modelUser = DataConverter.Convert(user);
    modelUser.Name = "new name";
    user = BusinessConverter.Convert(modelUser)

    repository.Update(user);
    repository.save();
}

注意:我在逻辑上知道这对转换没有多大意义,只是转换回来但是顺其自然,我已经大大简化了示例,使其更容易投入纸张,在我的实际代码中有一个原因这样做。

我得到通常的错误“objectstatemanager中已存在具有相同密钥的对象...”。我假设因为Get()将对象加载到EF中,然后更新看到对象已分离,然后尝试附加它并且它已经存在。

我的存储库中的My Update方法如下所示

public override bool UpdateItem(DbUser item)
        {
            if (Work.Context.Entry(item).State == EntityState.Detached)
                Work.Context.Users.Attach(item);

            Work.Context.Entry(item).State = EntityState.Modified;

            return Work.Context.Entry(item).GetValidationResult().IsValid;
        }

3 个答案:

答案 0 :(得分:1)

我将此扩展方法设置为DbContext以重新启动实体而没有问题尝试:

public static void ReAttach<T>(this DbContext context, T entity) where T : class
{
    var objContext = ((IObjectContextAdapter) context).ObjectContext;
    var objSet = objContext.CreateObjectSet<T>();
    var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entity);

    Object foundEntity;
    var exists = objContext.TryGetObjectByKey(entityKey, out foundEntity);
    // Detach it here to prevent side-effects
    if (exists)
    {
        objContext.Detach(foundEntity);
    }
    context.Set<T>().Attach(entity);
}

然后只需更新您的方法:

public override bool UpdateItem(DbUser item)
{
    Work.Context.ReAttach(item);    
    Work.Context.Entry(item).State = EntityState.Modified;    
    return Work.Context.Entry(item).GetValidationResult().IsValid;
}

答案 1 :(得分:0)

您可能会获得一个托管实体,并再次逐字地将新的DbUser属性映射到托管对象:

public override bool UpdateItem(DbUser item)
{
  using (var work = new UnitOfWork())
  {
    var repository = new UserDataRepository(work);
    DbUser managedUser = repository.Get(item.PK);

    //foreach DbUser property map the item to managedUser
    managedUser.field1 = item.field1;
    [..]

    repository.Update(managedUser);
    repository.Save();
  }
}

答案 2 :(得分:0)

如果您将上下文设置为AsNoTracking(),则会停止aspmvc跟踪内存中实体的更改(无论如何,这都是您想要的)。

_dbContext.Products.AsNoTracking().Find(id);

我建议你在http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application

了解更多相关信息

佳日