2012-09-28 104 views
1

我有这样的两个对象 - 杂志和作者(MM关系):编辑的模式 - 复杂的类型不正确更新

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

我的问题是,我似乎无法更新对一个作家的数量杂志例如如果我有一位作者叫“Smith,P.”我可以添加另一个名为“Jones,D.”的文档,但是在发回到编辑控制器后,作者的数量仍然显示为1 - 即“Smith,P.H”。

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

我的更新型号代码是直线前进 - 和之前和之后显示变量值:

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

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

我也曾经有过的编辑操作这样的事情,

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

但不像你,我已经创建了Post没有Tag自定义模型粘合剂。在自定义PostModelBinder我正在做与你在那里做的几乎相同的事情(但我没有创建新的Tag,就像你在为Author一样)。基本上,我创建了一个新的Post实例,其中包含来自POST表单的所有属性,并从数据库中获取所有Tag的ID。请注意,我只从数据库中取得Tag而不是Post

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

UPDATE:

这里是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 
    } 
} 
+0

答复!Hoorah,这是Mark的安慰,因为自写这篇文章后,我意识到我需要一个MagazineModelBinder类,所以我做了一个。但是当你做一个新的职位时你做什么?你是否删除现有的Post,并重新从头开始重新创建?在我的情况下,我通过ID检索杂志,并清除了作者,然后应用我的新作家集(即使之前也存在),但是,它开始创建新的作者,并且我在Authors表中获得重复项:你在你的联编程序中做了什么? – Vidar

+1

@Vidar我包含了PostBinderBinder的源代码,你可以看看那个 – VJAI

+0

如果你还有问题,请告诉我 – VJAI

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调用来添加或删除每个作者。我发现这在很多复杂的用户界面中都很常见。
+0

嗯,我的意思是这在最好的可能的方式 - “我希望你是错误的”,因为如果不是这样,我想想整个EF路线都是一个巨大的错误,它会导致我做这么多工作,我觉得它根本不值得! – Vidar

+0

以其他方式思考 - 如何在没有EF的情况下做到这一点?在您的操作中接收到数据时,您仍然需要检测已更改的内容,并在数据库中执行适当的SQL命令以添加新记录,删除旧记录并更新现有记录。 EF对此没有任何影响,除非您不需要手动编写这些SQL命令,但是您必须告诉EF它需要为每条记录执行哪项操作。 –

+0

我越来越讨厌的是,你看到关于MVC和EF4的每一个演示/教程 - 显示了更新简单类型的一个非常基本的例子 - 没有任何关于你会发现自己的常见情况。使用1-M或M-M关系。我对所有这些都很生气! – Vidar