EntityKey和ApplyPropertyChanges()

时间:2009-05-22 14:50:04

标签: c# .net asp.net-mvc entity-framework

我需要设置一个EntityObject的EntityKey。我知道它的类型和它的id值。我不想不必要地查询数据库。

这有效......

//
// POST: /Department/Edit/5

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Guid id, Department Model)
{
    Model.EntityKey = (from Department d in db.Department
                       where d.Id == id
                       select d).FirstOrDefault().EntityKey;
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model);
    db.SaveChanges();
    return RedirectToAction("Index");
}

这失败了......

//
// POST: /Department/Edit/5

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Guid id, Department Model)
{
    String EntitySetName = db.DefaultContainerName + "." + Model.GetType().Name;
    Model.EntityKey = new System.Data.EntityKey(EntitySetName, "Id", Model.Id);
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model);
    db.SaveChanges();
    return RedirectToAction("Index");
}

ApplyPropertyChanges()行因此异常而失败:

  

ObjectStateManager没有   包含带有的ObjectStateEntry   引用类型的对象   'Sample.Models.Department'。

两个EntityKeys是平等的。为什么第二块代码失败?我该如何解决?

5 个答案:

答案 0 :(得分:9)

你的第二个代码块失败的原因是因为EF无法在ObjectStateManager中找到对象 - 即当它从数据库中提取对象时它将它们放在状态管理器中以便它可以跟踪它们 - 这类似于Identity Map模式。尽管有一个EntityKey,但您的对象不在状态管理器中,因此EF无法保留更改。您可以通过将对象置于状态管理器中来解决这个问题,但是您对它有点狡猾。

这有效:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Guid id, Department model)
{
  var entitySetName = db.DefaultContainerName + "." + model.GetType().Name;
  var entityKey = new System.Data.EntityKey(entitySetName, "Id", model.Id);

  db.Attach(new Department{Id = id, EntityKey = entityKey});
  db.AcceptAllChanges();

  db.ApplyPropertyChanges(entitySetName, model);
  db.SaveChanges();
}

......但它不是很干净。基本上,这是仅使用实体键附加“空”对象,接受所有更改,然后使用实际的实际更新值调用ApplyPropertyChanges。

这是在扩展方法中包含的相同内容 - 这应该适用于对主键使用单个db列的任何内容。调用该方法唯一有趣的部分是你需要告诉它如何通过委托找到key属性作为扩展方法的第二个参数:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Guid id, Department model)
{
  db.ApplyDetachedPropertyChanges(model, x => x.Id);
  db.SaveChanges();
}

和扩展方法:

public static class EfExtensions
{
  public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate)
  where T : EntityObject
  {
    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name;
    var id = getIdDelegate(entity);
    var entityKey = new EntityKey(entitySetName, "Id", id);

    db.Attach(new Department {Id = id, EntityKey = entityKey});
    db.AcceptAllChanges();

    db.ApplyPropertyChanges(entitySetName, entity);
  }
}

由于扩展方法正在调用AcceptAllChanges,如果您同时对多个实体进行更新,则需要小心调用此方法 - 如果您不小心,可能很容易“丢失”更新。因此,这种方法仅适用于简单的更新场景 - 例如很多MVC动作方法:)

答案 1 :(得分:4)

public static class EfExtensions
{
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate)
    where T : EntityObject
    {
        var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name;

        T newEntity = Activator.CreateInstance<T>();
        newEntity.EntityKey = db.CreateEntityKey(entitySetName, entity);

        Type t = typeof(T);
        foreach(EntityKeyMember keyMember in newEntity.EntityKey.EntityKeyValues) {
            PropertyInfo p = t.GetProperty(keyMember.Key);
            p.SetValue(newEntity, keyMember.Value, null);
        }

        db.Attach(newEntity);
        //db.AcceptAllChanges();

        db.ApplyPropertyChanges(entitySetName, entity);
    }
}

答案 2 :(得分:2)

尝试使用以下代码,并告诉我它是否适合您。

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Guid id, Department Model)
{
    using (var context =  new EntityContext())
    {
        try
        {
            Object entity = null;
            IEnumerable<KeyValuePair<string, object>> entityKeyValues =
                new KeyValuePair<string, object>[] {
                    new KeyValuePair<string, object>("DepartmentID", id) };

            // Create the  key for a specific SalesOrderHeader object. 
            EntityKey key = new EntityKey("EntityContext.Deparment",   
                                                                   entityKeyValues);

            // Get the object from the context or the persisted store by its key.
            if (context.TryGetObjectByKey(key, out entity))
            {
               context.ApplyPropertyChanges(key.EntitySetName, Model);
               context.SaveChanges();
            }
            else
            {
               // log message if we need
               //"An object with this key could not be found." 
            }                
        }
        catch (EntitySqlException ex)
        {
           // log message
        }
    }
 }       

答案 3 :(得分:2)

改进史蒂夫威尔科克的伟大实施,这是我的建议。

它比原始示例更多地使用Reflection(.NET的一部分),以节省一些代码。它还自动支持任何类型的实体类,而不仅仅是“部门”。

此外,它摆脱了过时的ApplyPropertyChanges方法,并使用了新的ApplyCurrentValues方法。

方法

该方法基本上只使用反射来动态获取“Id”属性的值,并进行设置。这节省了代表的所有麻烦。

public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity) where T : EntityObject
{
    PropertyInfo idProperty = typeof(T).GetProperty("Id");

    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name;
    var id = idProperty.GetValue(entity, null);
    var entityKey = new EntityKey(entitySetName, "Id", id);

    Type type = entity.GetType();
    EntityObject obj = (EntityObject)Activator.CreateInstance(type);

    idProperty.SetValue(obj, id, null);
    obj.EntityKey = entityKey;

    db.Attach(obj);
    db.AcceptAllChanges();

    db.ApplyCurrentValues(entitySetName, entity);
}

<强>用法

使用它也很简单。

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Guid id, Department Model)
{
    db.ApplyDetachedPropertyChanges(Model);
    db.SaveChanges();
}

答案 4 :(得分:1)

我有同样的问题,而它正在其他页面上工作。我不知道这条评论是否有用......但我终于发现,在我的对象(相当于你的部门)中,我的“Id”没有出现在表格中......

在我的表单上添加它(当我删除了它...)的事实解决了我的问题。 (使用style =“visibility:hidden”,为了不见他......)