【问题标题】:Update relationships on edit POST with model bind from MVC controller使用 MVC 控制器的模型绑定更新编辑 POST 的关系
【发布时间】:2015-12-01 19:35:07
【问题描述】:

使用具有多对多关系的实体框架,我很困惑如何从绑定模型的 ASP.NET MVC 控制器更新该关系。

例如一个博客:帖子有很多标签,标签有很多帖子。

帖子控制器编辑操作无法延迟加载 tags 实体:

public class PostsController : Controller
{
    [Route("posts/edit/{id}")]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit([Bind(Include = "Id,Title,Tags")] Post post)
    {
        // Post tags is null
        Post.tags.ToList();
    }
}

上面,HTTP Post 将属性绑定到模型;但是,Post.tags 关系为空。

我无法查询.Include(p =&gt; p.Tags) 或Attach() 使用此[Bind()] 检索相关标签实体的帖子。

在视图方面,我正在使用标记器并传递表单数据 - 我没有使用 MVC 列表组件。因此,问题不在于绑定视图表单数据 - 问题在于 .tags 属性不是延迟加载实体。

这种关系是有效的——从 Razor cshtml 视图中,我能够遍历集合并查看子标签。

从帖子视图中,我可以查看标签(这有效)

@foreach (var tag in Model.Tags) {
}

从标签视图中,我可以查看帖子(这可行)

@foreach (var post in Model.Posts) {
}

同样,在从 Posts 控制器创建操作时,我可以创建新的标签实体并保持它们的关系。

public class PostsController : Controller
{
    [Route("posts/create")]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
    {
        string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();

        post.Tags = new HashSet<Tag>();

        Tag tag = new Tag();
        post.Tags.Add(tag);

        if (ModelState.IsValid)
        {
            db.posts.Add(post);
            await db.SaveChangesAsync();
            return RedirectToAction("Admin");
        }

        return View(post);
    }

这些关系在任何地方都有效,除了这个编辑 HTTP Post。作为参考,它们被定义为:

public class Post
{
    public virtual ICollection<Tag> Tags { get; set; }
}

public class Tag
{
    public virtual ICollection<Post> Posts { get; set; }
}

只有在编辑操作的 HTTP Post 上我不能访问帖子的标签。

如何加载相关标签来更改该集合?

【问题讨论】:

  • 当您在表单中POST a collection 时,您需要使用for 循环而不是foreach 来获取绑定以识别模型属性。
  • 如果tags在POST方法中为null,那是因为你查看的不正确。显示它。

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


【解决方案1】:

您的问题与实体框架无关。当您从表单发布具有集合属性的模型/视图模型时,基本上是一个问题,集合属性变为空。

您可以使用 EditorTemplates 解决此问题。

看起来您正在视图中使用 Entity 框架生成的实体类。通常这不是一个好主意,因为现在您的 UI 层与 EF 实体紧密耦合。如果明天您出于任何原因想要将数据访问代码实现表单 EF 更改为其他内容怎么办?

所以让我们创建一些在 UI 层中使用的视图模型。 Viewmodels 是简单的 POCO 类。视图模型将具有视图绝对需要的属性。不要只是复制所有实体类属性并粘贴到您的视图模型中。

public class PostViewModel
{
    public int Id { set; get; }
    public string Title { set; get; }
    public List<PostTagViewModel> Tags { set; get; }
}

public class PostTagViewModel
{
    public int Id { set; get; }
    public string TagName { set; get; }
    public bool IsSelected { set; get; }
}

现在在您的 GET 操作中,您将创建 PostViewModel 类的对象,初始化 Tags 集合并发送到视图。

public ActionResult Create()
{
    var v =new PostViewModel();
    v.Tags = GetTags();
    return View(v);
}
private List<PostTagViewModel> GetTags()
{
    var db = new YourDbContext();
    return db.Tags.Select(x=> new PostTagViewModel { Id=x.Id, TagName=x.Name})
         .ToList();

} 

现在,让我们创建一个编辑器模板。转到~/Views/YourControllerName 目录并创建一个名为EditorTemplates 的子目录。在那里创建一个名为 PostTagViewModel.cshtml 的新视图。

将以下代码添加到新文件中。

@model YourNamespaceHere.PostTagViewModel
<div>
    @Model.TagName
    @Html.CheckBoxFor(s=>s.IsSelected)
    @Html.HiddenFor(s=>s.Id)      
</div>

现在,在我们强类型化到 PostViewModel 的主视图 (create.cshtml) 中,我们将调用 Html.EditorFor 辅助方法来使用编辑器模板。

@model YourNamespaceHere.PostViewModel
@using (Html.BeginForm())
{
    <label>Post title</label>
    @Html.TextBoxFor(s=>s.Title)

    <h3>Select tags</h3>
    @Html.EditorFor(s=>s.Tags)

    <input type="submit"/>
}

现在在您的 HttpPost 操作方法中,您可以检查标签集合的发布模型。

[HttpPost]
public ActionResult Create(PostViewModel model)
{
    if (ModelState.IsValid)
    {
       // Put a break point here and inspect model.
        foreach (var tag in model.Tags)
        {
            if (tag.IsSelected)
            {
                // Tag was checked from UI.Save it
            }
        }
        // to do : Save Post,Tags and then redirect.
    }
    model.Tags = GetTags(); //reload tags again 
    return View(model);
}

因此,由于我们的 HttpPost 操作的参数是 PostViewModel 的对象,我们需要将其映射到您的 Entity 类以使用实体框架保存它。

var post= new Post { Title = model.Title };
foreach(var t in model.Tags)
{
   if(t.IsSelected)
   {
      var t = dbContext.Tags.FirstOrDefault(s=>s.Id==t.Id);
      if(t!=null)
      { 
        post.Tags.Add(t);
      }
   }
}
dbContext.Posts.Add(post);
await dbContext.SaveChangesAsync();

【讨论】:

    【解决方案2】:

    您的创建方法缺少要插入标签的帖子的 Id 参数。 您必须在方法的开头声明 Post Id。

    [Route("posts/create")]
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Create([Bind(Include = "Title,Content")] Post post)
    {
            Post nPost= db.Posts.FirstOrDefault(x => x.Id == post.Id);
            nPost.Id= post.Id;
            nPost.Title= post.Title;
            string[] tagNames = this.Request.Form["TagNames"].Split(',').Select(tag => tag.Trim()).ToArray();
            foreach(string item in tagNames)
            {
              //First check if you got tag in Tags table
              Tag tg= db.Tags.FirstOrDefault(x => x.Name.ToLower() == item.ToLower().Trim());
              // If no row than create one.
              if (tg== null)
              {
                 tg= new Tag();
                 tg.Name= item;
                 db.Tags.Add(tg);
                 await db.SaveChanges();
               }
              // Now adding those tags to Post Tags.
               if (nPost.Tags.FirstOrDefault(x => x.Id == tg.Id) == null)
               {
                  nPost.Tags.Add(etk);
                  await db.SaveChanges();
               }
            }
            if (ModelState.IsValid)
            {
              await db.SaveChangesAsync();
              return RedirectToAction("Admin");
            }
        return View(post);
    }
    

    就是这样。

    【讨论】:

      猜你喜欢
      • 2014-02-07
      • 1970-01-01
      • 2021-12-06
      • 1970-01-01
      • 2011-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多