【问题标题】:EntityKey and ApplyPropertyChanges()EntityKey 和 ApplyPropertyChanges()
【发布时间】:2015-06-26 21:05:16
【问题描述】:

我需要设置一个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'。

这两个 EntityKey 是相等的。为什么第二个代码块会失败?我该如何解决?

【问题讨论】:

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


    【解决方案1】:

    您的第二个代码块失败的原因是因为 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 列作为主键的东西。调用该方法唯一有趣的部分是您需要告诉它如何通过委托作为扩展方法的第二个参数来找到关键属性:

    [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 表示扩展方法。那很漂亮! =) 如果表使用多列 PK,该方法将如何更改?
    • 您需要重载采用 KeyValuePair 的 IEnumerable 来指定键的扩展方法,然后在 EntityKey 上调用相应的构造函数重载。为了保持 stype 安全并避免在 KeyValuePair 中传递字符串,您可以传递一个“key-finding”委托数组以及要放入键中的值。
    • 基于这个很棒的答案,我对其进行了一些改进,并从中制作了自己的小方法,我也将其发布为答案。它适用于任何类型的实体对象,并且不使用委托。
    【解决方案2】:
    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);
        }
    }
    

    【讨论】:

    • 谢谢,我一直在寻找这样的东西,而您提供了需求! +1
    • 我不得不在“entity.GetType().name”上添加一个“ToPlural()”字符串扩展方法,因为我的实体集被代码生成器复数了。除此之外,您的代码运行良好。
    【解决方案3】:

    尝试使用下面的代码,让我知道它是否适合你。

    [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
            }
        }
     }       
    

    【讨论】:

      【解决方案4】:

      改进 Steve Willcock 的出色实现,这是我的建议。

      它比原始示例更多地使用反射(.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();
      }
      

      【讨论】:

        【解决方案5】:

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

        在我的表单上添加它(虽然我已经删除了它......)这一事实已经解决了我的问题。 (用style="visibility:hidden",为了不看到他...)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-25
          • 1970-01-01
          相关资源
          最近更新 更多