使用具有多对多关系的Entity Framework,我很困惑如何从绑定模型的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 => p.Tags)
查询帖子Attach()
或[Bind()]
以检索相关标记实体。
在视图方面,我使用了一个tokenizer并传递了formdata - 我没有使用MVC列表组件。所以,问题是没有绑定视图formdata - 问题是.tags
属性不是延迟加载实体。
这种关系是有用的 - 从Razor cshtml视图我能够遍历集合并查看子标签。
在Post View中,我可以查看标签(这可行)
@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发布编辑操作时,我无法访问帖子的标签。
如何加载相关标签来改变该集合?
答案 0 :(得分:0)
您的问题与Entity框架无关。当您从表单发布带有collection属性的model / viewmodel时,它基本上是一个问题,collection属性变为null。
您可以使用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操作方法中,您可以检查Tags集合的已发布模型。
[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();
答案 1 :(得分:0)
您的create方法缺少要插入标记的帖子的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);
}
就是这样。