Asp.net mvc 3 在模型上编辑-复杂类型未正确更新

Asp.net mvc 3 在模型上编辑-复杂类型未正确更新,asp.net-mvc-3,entity-framework-4,model-binding,Asp.net Mvc 3,Entity Framework 4,Model Binding,我有两个对象-杂志和作者(M-M关系): 以下是预编辑/更新的变量- …以下是添加新作者后的变量 我开始怀疑作者实体的表现,编辑后,它没有绑定到任何杂志,我猜这就是为什么它没有被更新回杂志实体-但这是令人困惑的,因为我正在有效地处理相同的杂志实体-我猜这可能与作者的自定义模型活页夹有关 有人能帮上忙吗 为了完整性-我也加入了AuthorModelBinder类- public class AuthorModelBinder : IModelBinder { public

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

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

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

我开始怀疑作者实体的表现,编辑后,它没有绑定到任何杂志,我猜这就是为什么它没有被更新回杂志实体-但这是令人困惑的,因为我正在有效地处理相同的杂志实体-我猜这可能与作者的自定义模型活页夹有关

有人能帮上忙吗

为了完整性-我也加入了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>();
        }
    }
公共类AuthorModelBinder:IModelBinder
{
公共对象绑定模型(ControllerContext ControllerContext,ModelBindingContext bindingContext)
{
var values=bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
如果(值!=null)
{
//我们已经指定星号(*)作为标记分隔符
//ID将以*分隔。例如“2*3*5”
var id=values.AttemptedValue.Split('*');
List validIds=新列表();
foreach(id中的字符串id)
{
int序列;
if(int.TryParse(id,out successInt))
{
有效添加(成功);
}
其他的
{
//改名
作者=新作者();
author.FULL\u NAME=id.Replace(“\”,“).Trim();
使用(RefmanEntities db=new RefmanEntities())
{
db.AUTHORs.Add(author);
db.SaveChanges();
validIds.Add((int)author.author_ID);
}
}
}
//既然我们有了选定的ID,我们就可以获取相应的ID
//来自我们数据源的作者
var authors=AuthorController.GetAllAuthors()。其中(x=>validds.Contains((int)x.Key))。选择(x=>newauthor
{
作者ID=x.密钥,
全名=x.值
}).ToList();
返回作者;
}
返回可枚举的.Empty();
}
}

此行
db.Entry(杂志).State=EntityState.Modified仅告知EF刀库实体已更改。它没有提到关系。如果调用
Attach
,对象图中的所有实体都将以
Unchanged
状态附着,并且必须分别处理它们。在多对多关系(以及关系状态的变化)的情况下,更糟糕的是

我花了很多时间在这上面。一般有三种方法:

  • 您将与您的实体一起发送附加信息,以查找已更改的内容和已删除的内容(是的,您还需要跟踪已删除的项目或关系)。然后,您将手动设置对象图中每个实体和关系的状态
  • 您将只使用当前拥有的数据,而不是将它们附加到上下文中,您将加载当前杂志和您需要的每个作者,并在这些加载的实体上重建这些更改
  • 您根本不会这样做,而是使用轻量级AJAX调用来添加或删除每个作者。我发现这在许多复杂的UI中很常见
此行
db.Entry(杂志).State=EntityState.Modified仅告知EF刀库实体已更改。它没有提到关系。如果调用
Attach
,对象图中的所有实体都将以
Unchanged
状态附着,并且必须分别处理它们。在多对多关系(以及关系状态的变化)的情况下,更糟糕的是

我花了很多时间在这上面。一般有三种方法:

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

当我使用MVC/Nhibernate开发我的博客时,我遇到了一个非常类似的场景,实体是
Post
Tag

我也有这样的编辑动作

public ActionResult Edit(Post post)
{
  if (ModelState.IsValid)
  {
       repo.EditPost(post);
       ...
  }
  ...
}
但与您不同的是,我为
Post
而不是
标签创建了一个自定义模型活页夹。在定制
PostModelBinder
中,我所做的与您在那里所做的几乎相同(但我不会像您为
Author
s所做的那样创建新的
Tag
s)。基本上,我创建了一个新的
Post
实例,从发布的表单中填充它的所有属性,并从数据库中获取ID的所有
标记。注意,我只从数据库中获取了
标记,而不是
Post

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

更新:

以下是
Post
model活页夹的完整源代码

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
  }
}
namespace PrideParrot.Web.Controllers.ModelBinders
{
[验证输入(错误)]
公共类PostBinder:IModel
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>();
        }
    }
public ActionResult Edit(Post post)
{
  if (ModelState.IsValid)
  {
       repo.EditPost(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
  }
}