ASP.NET MVC,实体框架,修改一对多和多对多的关系

Building on my previous article从实体框架查看了向多对多和多对多关系添加数据,这是一个看如何在ASP.NET MVC应用程序中修改该数据。

重新签署,我正在将此站点从ASP.NET Web表单迁移到MVC平台。随着服务器端方法的变化,我还应用实体框架来帮助我的数据访问层。上一篇今天福彩字谜总汇看内容管理系统中的功能,以将今天福彩字谜总汇添加到网站,包括应用今天福彩字谜总汇类型(一对多关系)和类别标签(多对多)。实体图如下:

 

在数据库中,今天福彩字谜总汇和类别之间存在桥接表(articlecategories)。这只包含IntryID和CateginidID作为外键。在Articlecation中创建了一个复合主键,它包括表中的两个字段。实体框架需要一个独特的约束来工作很好,这是有道理的。这意味着我不能有两个条目,其中相同的今天福彩字谜总汇与相同的类别相关联。如果未到位这样的约束,则EF将使表只读取。

修改现有今天福彩字谜总汇条目的工作流程是简单的 - 我将呈现一个类别列表,并且一旦选择其中一个,我将获得该类别中出现的今天福彩字谜总汇标题列表。选择其中一个将以可编辑的形式呈现今天福彩字谜总汇本身,以及用于提交更改的按钮。依赖于依赖,或级联选择列表立即单击我的Ajax交换机,因此我将使用jQuery来执行此操作。因此,editarticle View包含对jQuery的引用,以及功能中的精选插件 本文:

  

<script type="text/javascript" src="../../scripts/jquery-1.3.2.js"></script>
<script type="text/javascript" src="../../scripts/jquery.selectboxes.min.js"></script>

.........

<h2>
    Edit Article</h2>
  <div>
    <%=Html.DropDownList(
      "CategoryID", 
      new SelectList(
        (IEnumerable<Category>)ViewData["categories"],
        "CategoryID", 
        "CategoryName"
        ),
        string.Empty)%>
  </div>
  <div>
    <select name="ArticleID" id="ArticleID">
    </select>
  </div>
  <div id="articleform">
  </div>

有两个选择列表 - 通过HTMLHELPER扩展方法构建的一个选择,以及HTML中的空值(StartID)。还有一个名为armitform的空的div,它将通过ajax通过编辑形式填充。首次请求视图时,控制器填充第一个选择列表。 Controller操作不采用参数,只需从存储库获取数据以列出类别:

[ValidateInput(false)]
public ActionResult EditArticle()
{
  ViewData["类别"] = repository.GetCategoryList();
  return View();
}

和所谓的存储库方法:

public IQueryable<Category> GetCategoryList()
{
  return (de.CategorySet.OrderBy(c => c.CategoryName)).AsQueryable();
}

到目前为止一切都很容易,结果运行页面是这样的(如您所期望的那样):

我们需要一些AJAX的帮助时,选择其中一个类别来获取今天福彩字谜总汇列表,并将其显示在第二选择列表。我们还需要在服务器端上的一些代码来响应Ajax请求,以便涉及控制器上的另一个动作,以及存储库中的数据访问方法。控制器操作将返回JSON,以便我们可以在jQuery中轻松使用它:

public JsonResult GetArticlesByCategory(int categoryid)
{
  return Json(repository.GetArticlesByCategory(categoryid));
}

并且存储库方法如下:

public IQueryable<ArticleTitle> GetArticlesByCategory(int id)
{
  return (de.ArticleSet.Where(a => a.Categories
                                     .Any(c => c.CategoryID == id))
    .OrderByDescending(a => a.DateCreated)
    .Select(a => new ArticleTitle
                   {
                     ID = a.ArticleID,
                     Head = a.Headline,
                   }))
    .AsQueryable();
}

如果您已经阅读了我的迁移到MVC和EF的以前的Ramblings,您已经知道 我创造了一个名为Articletitle的小班,它只是包含两个属性 - 物品标题及其ID。这意味着我可以列出今天福彩字谜总汇,而无需带回所有文本内容,日期创建等等等我不需要显示。此轻型类非常适合第二个选择列表。所以我有一种方法可以获取项目,以及将结果转换为JSON的操作。现在有一些jquery将两者合在一起:

<script type="text/javascript">
  $(document).ready(function() {
  $("#CategoryID").change(function() {
  $.ajaxSetup({ cache: false });
      $.getJSON("/Admin/GetArticlesByCategory/" + $("#CategoryID").val(), null, function(data) {
        $("#ArticleID").removeOption(/./).addOption("", "", false);
        for (var i = 0; i < data.length; i++) {
          var val = data[i].ID;
          var text = data[i].Head;
          $("#ArticleID").addOption(val, text, false);
        }
      });
    });
    $("#ArticleID").change(function() {
    $("#articleform").load("/Admin/GetArticleForEdit/" + $("#ArticleID").val());
    });
  });
</script>

这进入了<head>editarticle视图的区域。它将事件处理程序应用于onchange选单列表的onchange事件,该列表将Ajax请求触发到控制器操作的详细信息。在此之前,将设置AjaxSetup选项以防止IE缓存选择列表中的结果。从控制器操作获得了JSON时,SelectBoxes插入清除以前请求填充的任何数据的今天福彩字谜总汇选择列表,然后将空字符串添加为默认选项,然后才能迭代JSON并填充物品选择列表。最后,它将事件处理程序添加到onfichange Select列表的onchange事件中,该事件将调用的结果加载到另一个控制器操作 - GetArticleForedit,它将今天福彩字谜总汇的ID作为参数:

public ActionResult GetArticleForEdit(int id)
{
  var article = repository.GetArticle(id);
  var selectedvalue = article.ArticleTypes.ArticleTypeID;
  ViewData["ArticleTypes"] = new SelectList(
                                         repository.GetArticleTypes(), 
                                         "ArticleTypeID", 
                                         "ArticleTypeName", 
                                         selectedvalue
                                         );
  ViewData["类别"] = repository.GetCategoryList();
  ViewData["Article"] = article;
  return View("EditArticlePartial");
}

该操作将某些数据添加到ViewDatdictionery - 一个选择列表,其中包含SelectListItems的SelectLSite,用于选择LSIT的今天福彩字谜总汇类型,以及当前今天福彩字谜总汇的ArticletypeID作为所选项目(TEH参数列表中的最后一个argumanet),一个类别列表和细节要编辑的今天福彩字谜总汇,并将其返回到部分视图,EditArticlePartial,然后使用数据来提供填充的编辑表单的HTML。 Repository.getArticle()方法值得快速查看:

public Article GetArticle(int id)
{
  return (de.ArticleSet
    .Include("ArticleTypes")
    .Include("类别")
    .Where(a => a.ArticleID == id))
    .First();
}

通过使用include()扩展方法,将今天福彩字谜总汇对象与其集合和其类别集合返回。这可确保填充和加载集合。参数中的字符串include()方法是该今天福彩字谜总汇开头的实体图中的导航属性。我们需要集合,以便我们可以映射已选中的复选框,并选择显示编辑表单时返回列出的选定项目:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

    <% var article = (Article)ViewData["Article"]; %>
    <h2>Edit Article</h2>
    <form action="EditArticle" method="post">
    <table>
    <tr>
      <td>Headline</td>
      <td><%= Html.TextBox("Headline", article.Headline, new { style = "width:500px"})%></td>
    </tr>
    <tr>
      <td>Abstract</td>
      <td><%= Html.TextArea("Abstract", article.Abstract, new { cols = "80", rows = "5" })%></td>
    </tr>
    <tr>
      <td>MainText</td>
      <td><%= Html.TextArea("MainText", article.Maintext, new { cols = "80", rows = "10" })%></td>
    </tr>   
    <tr>
      <td>Article Type</td>
      <td>
        <%= Html.DropDownList("ArticleTypeID", (IEnumerable<SelectListItem>)ViewData["ArticleTypes"])%>  
    </td>
    </tr> 
    <tr>
      <td>Article Categories</td>
      <td><% foreach (var item in (IEnumerable<Category>)ViewData["类别"]){ %>
      <input type="checkbox" name="CategoryID" value="<%=item.CategoryID %>" 
           <% foreach(var c in article.Categories)
           {
             if(c.CategoryID == item.CategoryID)
             { %>
               检查一下="检查一下"
           <%} 
           }%>/>
           <%= item.CategoryName %><br />
           <% } %>
      </td>
    </tr>     
    <tr>
      <td><input type="hidden" name="ArticleID" value="<%=article.ArticleID %>" /></td>
      <td><input type="submit" name="action" id="action" value="Submit" /></td>
    </tr>
    </table>
    </form>


鉴于MVC框架内缺少多个复选框的帮助者,我已经采取了经典的ASP代码风格。它非常简单:当它写入浏览器的每个选项元素时,它的值OS与通过Via ViewData传递的今天福彩字谜总汇的类别集合中的CategoryID值相比。如果找到匹配项, 检查一下="checked" 将选项添加为属性。我在样本中将代码间隔开来,但在原来的一行中写入所有。因此,渲染的HTML将保留由于将代码格式化为更可读的形式而发生的划线。就像旧的ColdFusion页面一样,如果您在哪里查看了来源,您将看到嵌入服务器端代码的大块白色空间。真正的答案是为复选表单编写自己的html.helper。

您将在图像中显示的示例中注意到我的今天福彩字谜总汇包含HTML标记。如果我点击提交时,我会得到一个ysod(黄色屏幕死亡)告诉我潜在的危险价值是发布的。因此,我可以通过归因于控制器操作,以便使用validateInput更新今天福彩字谜总汇(false):

[ValidateInput(false)]
[AcceptVerbs("POST")]
public ActionResult EditArticle(Article article)
{
  var articleTypeId = Request.Form["ArticleTypeID"];
  var categoryId = Request["CategoryID"].Split(',');
  repository.EditArticle(article, articleTypeId, categoryId);
  return Content("Updated");
}

查看代码的最后一部分是实际的存储库.Editarticle()方法持续数据库中更改的值:

public void EditArticle(Article article, string articleTypeId, string[] categoryId)
{
  var id = 0;
  Article art = de.ArticleSet
                  .Include("ArticleTypes")
                  .Include("类别")
                  .Where(a => a.ArticleID == article.ArticleID)
                  .First();

  var count = art.Categories.Count;
  for (var i = 0; i < count; i++)
  {
    art.Categories.Remove(art.Categories.ElementAt(i));
    count--;
  }
  foreach (var c in categoryId)
  {
    id = int.Parse(c);
    Category category = de.CategorySet.Where(ct => ct.CategoryID == id).First();
    art.Categories.Add(category);
  }

  art.Headline = article.Headline;
  art.Abstract = article.Abstract;
  art.Maintext = article.Maintext;
  art.DateAmended = DateTime.Now;
  art.ArticleTypesReference.EntityKey = new EntityKey(
                                             "DotnettingEntities.ArticleTypeSet", 
                                             "ArticleTypeID", 
                                             int.Parse(articleTypeId)
                                             );

  de.SaveChanges();
}

代码的第一部分获取要更新的今天福彩字谜总汇。更新今天福彩字谜总汇与其类别之间存在的多种关系是下一步。如果我使用存储过程,我将包括一些SQL,它只需从桥接表中删除现有行,并将其替换为作为更新今天福彩字谜总汇的一部分发布的任何内容。无论类别是否有任何更改,都会执行。实体框架似乎比这更聪明。代码确实确实删除了今天福彩字谜总汇的集合中的类别,并标记为删除。然后代码添加了从表单中发布的任何类别。但是,如果使用SQL Profiler来检查,您将看到删除的SQL命令并将项目插入艺术品桥接表中,只有本文与今天福彩字谜总汇相关的类别的任何更改。然而,为每个类别对象执行SQL命令,这些对象被获取的BEFroe被添加到集合。

其余的代码主要是自我开发的。可以更新可在表单中编辑的项目以及Dateamed属性。最后,通过设置潜在更改的articletype的键的值来更新今天福彩字谜总汇和articlyype之间的关系。更新这样的一对多关系的替代方法是查询具有发布的ArticletypeID的Articlyype对象,然后将其应用于今天福彩字谜总汇的Articletype集合::

int id = int.Parse(articleTypeId);
ArticleType at = de.ArticleTypeSet.Where(a => a.ArticleTypeID == id).First();
article.ArticleTypes = at;

但是,执行此操作会导致对数据库执行的SQL查询。设置EntityKey值不。

结果是它都有工作。但我对更新多对多关系的方式100%令人满意。我会发现一些时间做一些更多的调查 - 除非有人提出一种更清洁的方式来管理这个过程的这个过程。