编辑模型 - 复杂类型未正确更新

时间:2012-09-28 15:37:47

标签: asp.net-mvc-3 entity-framework-4 model-binding

我有这两个对象 - 杂志和作者(M-M关系):

public partial class MAGAZINE
    {
        public MAGAZINE()
        {
            this.AUTHORs = new HashSet<AUTHOR>();
        }

        public long REF_ID { get; set; }
        public string NOTES { get; set; }
        public string TITLE { get; set; }

        public virtual REFERENCE REFERENCE { get; set; }
        public virtual ICollection<AUTHOR> AUTHORs { get; set; }
    }

public partial class AUTHOR
{
    public AUTHOR()
    {  
         this.MAGAZINEs = new HashSet<MAGAZINE>();
    }

            public long AUTHOR_ID { get; set; }
            public string FULL_NAME { get; set; }

            public virtual ICollection<MAGAZINE> MAGAZINEs { get; set; }
        }
}

我的问题是,我无法更新针对杂志的作者数量,例如如果我有1位作者名为“Smith,P。”已经存储在杂志中,我可以添加另一个名为“Jones,D。”,但在回到编辑控制器之​​后,作者的数量仍显示1 - 即“Smith,P.H”。

请注意,我没有成功地将作者的数量绑定到父实体(杂志),它使用自定义模型绑定器来检索作者并绑定到杂志(我认为),但它仍然没有似乎正确更新。

我更新模型的代码很简单 - 并显示前后的变量值:

public ActionResult Edit(long id)
    {
        MAGAZINE magazine = db.MAGAZINEs.Find(id);
        return View(magazine);
    }

以下是预编辑/更新的变量 -

enter image description here

[HttpPost]
public ActionResult Edit(MAGAZINE magazine)
   {
       if (ModelState.IsValid)
       {
           db.Entry(magazine).State = EntityState.Modified;
           db.SaveChanges();
           return RedirectToAction("Index");
       }

       return View(magazine);
   }

...以下是添加新作者后的变量...

我怀疑作者实体正在展示,编辑后它没有被任何杂志约束,我猜这就是为什么它没有被更新回杂志实体 - 但它是令人困惑的,因为我有效处理同一个杂志实体 - 我想这可能与作者的自定义模型绑定器有关。

有人可以帮忙解决这个问题吗?

为了完整性 - 我也包含了我的AuthorModelBinder类 -

public class AuthorModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (values != null)
            {
                // We have specified asterisk (*) as a token delimiter. So
                // the ids will be separated by *. For example "2*3*5"
                var ids = values.AttemptedValue.Split('*');

                List<int> validIds = new List<int>();
                foreach (string id in ids)
                {
                    int successInt;
                    if (int.TryParse(id, out successInt))
                    {
                        validIds.Add(successInt);
                    }
                    else
                    {
                        //Make a new author
                        AUTHOR author = new AUTHOR();
                        author.FULL_NAME = id.Replace("\'", "").Trim();
                        using (RefmanEntities db = new RefmanEntities())
                        {
                            db.AUTHORs.Add(author);
                            db.SaveChanges();
                            validIds.Add((int)author.AUTHOR_ID);
                        }
                    }
                }

                 //Now that we have the selected ids we could fetch the corresponding
                 //authors from our datasource
                var authors = AuthorController.GetAllAuthors().Where(x => validIds.Contains((int)x.Key)).Select(x => new AUTHOR
                {
                    AUTHOR_ID = x.Key,
                    FULL_NAME = x.Value
                }).ToList();
                return authors;
            }
            return Enumerable.Empty<AUTHOR>();
        }
    }

enter image description here

2 个答案:

答案 0 :(得分:2)

此行db.Entry(magazine).State = EntityState.Modified;仅告知EF杂志实体已更改。它没有说关系。如果您调用Attach对象图中的所有实体都附加在Unchanged状态,您必须分别处理它们中的每一个。在多对多关系you must also handle relation itself(以及改变关系状态in DbContext API is not possible)的情况下甚至更糟。

我花了很多时间来处理problem and design in disconnected app。有三种一般方法:

  • 您将向实体发送其他信息,以查找已更改的内容和已删除的内容(是的,您还需要跟踪已删除的项目或关系)。然后,您将手动设置对象图中每个实体和关系的状态。
  • 您将只使用目前拥有的数据,而不是将它们附加到上下文中,您将加载当前杂志和您需要的每个作者,并在这些加载的实体上重建这些更改。
  • 您根本不会这样做,而是使用轻量级AJAX调用来添加或删除每个作者。我发现许多复杂的用户界面都很常见。

答案 1 :(得分:2)

当我使用MVC / Nhibernate开发我的博客并且实体为PostTag时,我遇到了类似的情况。

我也有像这样的编辑动作,

public ActionResult Edit(Post post)
{
  if (ModelState.IsValid)
  {
       repo.EditPost(post);
       ...
  }
  ...
}

但与您不同的是,我为Post而不是Tag创建了自定义模型绑定器。在自定义PostModelBinder中,我做的几乎与你在那里做的一样(但我没有像你在Tag那样创建新的Author。基本上我创建了一个新的Post实例,从POSTed表单填充它的所有属性,并从数据库中获取id的所有Tag。请注意,我只从数据库中获取Tag而不是Post

我可能会建议您为ModelBinder创建Magazine并查看。此外,最好使用存储库模式,而不是直接从控制器进行调用。

更新:

以下是Post模型装订器的完整源代码

namespace PrideParrot.Web.Controllers.ModelBinders
{
  [ValidateInput(false)]
  public class PostBinder : IModelBinder
  {
    private IRepository repo;

    public PostBinder(IRepository repo)
    {
      this.repo = repo;
    }

    #region IModelBinder Members

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      HttpRequestBase request = controllerContext.HttpContext.Request;

      // retrieving the posted values.
      string oper = request.Form.Get("oper"),
               idStr = request.Form.Get("Id"),
               heading = request.Form.Get("Heading"),
               description = request.Form.Get("Description"),
               tagsStr = request.Form.Get("Tags"),
               postTypeIdStr = request.Form.Get("PostType"),
               postedDateStr = request.Form.Get("PostedDate"),
               isPublishedStr = request.Form.Get("Published"),
               fileName = request.Form.Get("FileName"),
               serialNoStr = request.Form.Get("SerialNo"),
               metaTags = request.Form.Get("MetaTags"),
               metaDescription = request.Form.Get("MetaDescription"),
               themeIdStr = request.Form.Get("Theme");

      // initializing to default values.
      int id = 0, serialNo = 0;
      DateTime postedDate = DateTime.UtcNow;
      DateTime? modifiedDate = DateTime.UtcNow;
      postedDate.AddMilliseconds(-postedDate.Millisecond);
      modifiedDate.Value.AddMilliseconds(-modifiedDate.Value.Millisecond);

      /*if operation is not specified throw exception. 
        operation should be either'add' or 'edit'*/
      if (string.IsNullOrEmpty(oper))
        throw new Exception("Operation not specified");

      // if there is no 'id' in edit operation add error to model.
      if (string.IsNullOrEmpty(idStr) || idStr.Equals("_empty"))
      {
        if (oper.Equals("edit"))
          bindingContext.ModelState.AddModelError("Id", "Id is empty");
      }
      else
        id = int.Parse(idStr);

      // check if heading is not empty.
      if (string.IsNullOrEmpty(heading))
        bindingContext.ModelState.AddModelError("Heading", "Heading: Field is required");
      else if (heading.Length > 500)
        bindingContext.ModelState.AddModelError("HeadingLength", "Heading: Length should not be greater than 500 characters");

      // check if description is not empty.
      if (string.IsNullOrEmpty(description))
        bindingContext.ModelState.AddModelError("Description", "Description: Field is required");

      // check if tags is not empty.
      if (string.IsNullOrEmpty(metaTags))
        bindingContext.ModelState.AddModelError("Tags", "Tags: Field is required");
      else if (metaTags.Length > 500)
        bindingContext.ModelState.AddModelError("TagsLength", "Tags: Length should not be greater than 500 characters");

      // check if metadescription is not empty.
      if (string.IsNullOrEmpty(metaTags))
        bindingContext.ModelState.AddModelError("MetaDescription", "Meta Description: Field is required");
      else if (metaTags.Length > 500)
        bindingContext.ModelState.AddModelError("MetaDescription", "Meta Description: Length should not be greater than 500 characters");

      // check if file name is not empty.
      if (string.IsNullOrEmpty(fileName))
        bindingContext.ModelState.AddModelError("FileName", "File Name: Field is required");
      else if (fileName.Length > 50)
        bindingContext.ModelState.AddModelError("FileNameLength", "FileName: Length should not be greater than 50 characters");

      bool isPublished = !string.IsNullOrEmpty(isPublishedStr) ? Convert.ToBoolean(isPublishedStr.ToString()) : false;

      //** TAGS
      var tags = new List<PostTag>();
      var tagIds = tagsStr.Split(',');
      foreach (var tagId in tagIds)
      {
        tags.Add(repo.PostTag(int.Parse(tagId)));
      }
      if(tags.Count == 0)
        bindingContext.ModelState.AddModelError("Tags", "Tags: The Post should have atleast one tag");

      // retrieving the post type from repository.
      int postTypeId = !string.IsNullOrEmpty(postTypeIdStr) ? int.Parse(postTypeIdStr) : 0;
      var postType = repo.PostType(postTypeId);
      if (postType == null)
        bindingContext.ModelState.AddModelError("PostType", "Post Type is null");

      Theme theme = null;
      if (!string.IsNullOrEmpty(themeIdStr))
        theme = repo.Theme(int.Parse(themeIdStr));

      // serial no
      if (oper.Equals("edit"))
      {
        if (string.IsNullOrEmpty(serialNoStr))
          bindingContext.ModelState.AddModelError("SerialNo", "Serial No is empty");
        else
          serialNo = int.Parse(serialNoStr);
      }
      else
      {
        serialNo = repo.TotalPosts(false) + 1;
      }

      // check if commented date is not empty in edit.
      if (string.IsNullOrEmpty(postedDateStr))
      {
        if (oper.Equals("edit"))
          bindingContext.ModelState.AddModelError("PostedDate", "Posted Date is empty");
      }
      else
        postedDate = Convert.ToDateTime(postedDateStr.ToString());

      // CREATE NEW POST INSTANCE
      return new Post
      {
        Id = id,
        Heading = heading,
        Description = description,
        MetaTags = metaTags,
        MetaDescription = metaDescription,
        Tags = tags,
        PostType = postType,
        PostedDate = postedDate,
        ModifiedDate = oper.Equals("edit") ? modifiedDate : null,
        Published = isPublished,
        FileName = fileName,
        SerialNo = serialNo,
        Theme = theme
      };
    }

    #endregion
  }
}