MVC3+EF4.1學習系列(六)-----導航屬性數據更新的處理

經過上一篇的學習 咱們已經知道怎麼查詢關係 這篇就來講說怎麼導航屬性數據更新時的處理 以及EF又會爲咱們生成哪些SQL~javascript

老規矩 先看下今天的圖html

添加和修改頁面基本就是這樣java

這節的內容相對簡單~~jquery

主要就是講  一對一 一對多 多對多時的增刪改 以及MVC的一些小東西web

一. 一對多的的處理算法

看第一張圖   院系和課程是一對多的關係  sql

1.添加數據庫

一對多的添加很是簡單  遇到一對多的狀況  咱們通常考慮dropdownlist來展現 只要把這個展現出來就容易了json

mvc綁定dropdownlist 通常是 控制器用 viewstate存一個SelectList  或者viewbag  而後視圖綁定 上代碼併發

複製代碼

        public ActionResult Create()
       {
           PopulateDepartmentsDropDownList();

           
return View();
       }

       
//
       
// POST: /Course/Create

       [HttpPost]
       
public ActionResult Create(Course course)
       {
           
try
           {
               
// TODO: Add insert logic here
               if (ModelState.IsValid)
               {
                   db.Courses.Add(course);
                   db.SaveChanges();
                 
// PopulateDepartmentsDropDownList(course.DepartmentID);
               }
               
return RedirectToAction("Index");
           }
           
catch
           {
               ModelState.AddModelError(
"", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");

               
return View();
           }
         

       }

       
/// <summary>
       
/// 處理下拉列表
       
/// </summary>
       
/// <param name="selected">選擇的項</param>
       private void PopulateDepartmentsDropDownList(object selected = null)
       {
           var departmentsQuery
= from d in db.Departments
                                  orderby d.Name
                                  select d;
           ViewBag.DepartmentID
= new SelectList(departmentsQuery, "DepartmentID", "Name", selected);
       }

複製代碼

能夠看到 很是簡單下面是視圖的

複製代碼

@model ContosoUniversity.Models.Course

@{
   ViewBag.Title = "Create";
   Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Create</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
   @Html.ValidationSummary(true)
   
<fieldset>
       
<legend>Course</legend>
       
<div class="editor-label">
           @Html.LabelFor(model => model.CourseID)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.CourseID)
           @Html.ValidationMessageFor(model => model.CourseID)
       
</div>


       
<div class="editor-label">
           @Html.LabelFor(model => model.Title)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.Title)
           @Html.ValidationMessageFor(model => model.Title)
       
</div>

       
<div class="editor-label">
           @Html.LabelFor(model => model.Credits)
       
</div>
       
<div class="editor-field">
           @Html.EditorFor(model => model.Credits)
           @Html.ValidationMessageFor(model => model.Credits)
       
</div>

     
<div class="editor-label">
           @Html.LabelFor(model => model.DepartmentID, "Department")
       
</div>
       
<div class="editor-field">
         @Html.DropDownList("DepartmentID","請選擇")
           @Html.ValidationMessageFor(model => model.DepartmentID)
       
</div>

       
<p>
           
<input type="submit" value="Create" />
       
</p>
   
</fieldset>
}

<div>
   @Html.ActionLink("Back to List", "Index")
</div>

複製代碼

視圖的核心其實就這句

Html.DropDownList("DepartmentID","請選擇")

DepartmentID 對應 你的viewbag  就能讓他們對應上去了 就這麼簡單神奇~~  因爲生成的html  name 爲 DepartmentID 當你保存的時候 經過ModelBinder  
會自動對應到實體類上 直接保存便可
生成的SQL插入語句也簡單  並且是參數化的~

exec sp_executesql N'insert [dbo].[Course]([CourseID], [Title], [Credits], [DepartmentID])
values (@0, @1, @2, @3)
',N'@0 int,@1 nvarchar(50),@2 int,@3 int',@0=4444,@1=N'試試插入',@2=4,@3=1

2.修改

這個沒啥好說的了~~ 基本和添加同樣

複製代碼

  public ActionResult Edit(int id)
       {
           Course course
= db.Courses.Find(id);
           PopulateDepartmentsDropDownList(course.DepartmentID);
           
return View(course);

       }

       
//
       
// POST: /Course/Edit/5

       [HttpPost]
       
public ActionResult Edit(Course course)
       {
           
try
           {
               
if (ModelState.IsValid)
               {
                   db.Entry(course).State
= EntityState.Modified;
                   db.SaveChanges();
                   
return RedirectToAction("Index");
               }
           }
           
catch (DataException)
           {
               
//Log the error (add a variable name after DataException)
               ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
           }
           PopulateDepartmentsDropDownList(course.DepartmentID);
           
return View(course);

       }

複製代碼

生成的SQL語句以下

exec sp_executesql N'update [dbo].[Course]
set [Title] = @0, [Credits] = @1, [DepartmentID] = @2
where ([CourseID] = @3)
',N'@0 nvarchar(50),@1 int,@2 int,@3 int',@0=N'試試插入',@1=4,@2=2,@3=4444

這有個問題  就是把每一個字段都作了更新 而咱們實際上只更新了一個  若是能按需更新改多好 dudu給了咱們好的思路 能夠參考下 -----文章鏈接

3.刪除

這個簡單....沒啥好說的了  具體參考第二篇  簡單的增刪改查~~

二.一對一和多對多

一對多比較簡單 由於涉及的都是一張表裏的東西 因此沒什麼難的   而一對一,多對多 則是處理 多張表的狀況 因此這裏重點說下這部分~~

先看第二個圖 分析下關係   這是修改老師的信息  跟他關聯的有一對一的地址  和多對多的課程  也就是說  當咱們插入一條記錄時 

應該插入三張表  教師表  辦公地址表  和 教師課程關係表   那讓咱們來印證下吧

1.添加

首先 咱們要先把添加頁面顯示出來 先來講下 下面那個複選框選擇課程   控制器很簡單 就是讀取出來全部課程存放到viewbag裏便可

public ActionResult Create()
       {
           ViewBag.SelectCourse
= db.Courses.ToList();
           
           
return View();
       }

視圖的顯示 有兩種方法  一種是原文提供的 
添加一個viewmodel  專門存放課程 以及是否選中  而後經過以下代碼實現三列換行

複製代碼

<div class="editor-field">
   
<table style="width: 100%">
       
<tr>
           @{
               int cnt = 0;
               List
<ContosoUniversity.ViewModels.AssignedCourseData> courses = ViewBag.Courses;

               foreach (var course in courses) {
                   if (cnt++ % 3 == 0) {
                       @:  
</tr> <tr>
                   }
                   @:
<td>
                       
<input type="checkbox"
                              name
="selectedCourses"
                              value
="@course.CourseID"
                              @(Html.Raw(course.Assigned ? "checked
=\"checked\"" : "")) />
                       @course.CourseID @:
&nbsp; @course.Title
                   @:
</td>
               }
               @:
</tr>
           }
   
</table>
</div>

複製代碼

我的以爲麻煩了些  這裏我說下個人方法

複製代碼

   <div class="editor-field">
       @{
            System.Text.StringBuilder sb = new System.Text.StringBuilder("
<table style='width:100%'><tr>");
            List
<ContosoUniversity.Models.Course> list= ViewBag.SelectCourse as List<ContosoUniversity.Models.Course>;
           for (int i = 0; i
< list.Count; i++)
           {
               if (i!
=0&&i % 3 == 0)
               {
                   sb.Append("</tr
><tr>");
               }
               sb.Append("
<td><input type='checkbox' value='"+list[i].CourseID+"' name='selectedCourses'/>"+list[i].Title+"</td>");
           }
           sb.Append("
</tr></table> ");
           }
       @MvcHtmlString.Create(sb.ToString())    
       
</div>

複製代碼

這裏要說的就是 MvcHtmlString.Create   若是不使用這個 直接輸入  則會把<tr><td> 這些也直接輸入出來 而不是轉換成HTML   記住,若是想生成想要的結果 請使用MvcHtmlString.Create


好了 添加頁面有了 如今就是添加了 

複製代碼

[HttpPost]
       
public ActionResult Create(Instructor Model, string[] selectedCourses)
       {
           
try
           {
               
// TODO: Add insert logic here
               if (ModelState.IsValid)
               {

                   Model.Courses
= new List<Course>();

                   
foreach (var item in db.Courses.ToList())
                   {
                       
if (selectedCourses.Contains(item.CourseID.ToString()))
                       {
                           Model.Courses.Add(item);
                       }
                   }
                   db.Instructors.Add(Model);
                   db.SaveChanges();
                 
               }
               
return RedirectToAction("Index");
               
           }
           
catch
           {
               
return View();
           }
       }

複製代碼

這裏提下 第二個參數 就是咱們選中的checkbox 的value 的集合   這個參數的名字 與 checkbox名字的name同樣

而後咱們遍歷全部的課程  把符合條件的加入進來 我選了一個  讓咱們看下EF爲咱們生成的SQL

複製代碼

exec sp_executesql N'insert [dbo].[Instructor]([LastName], [FirstName], [HireDate])
values (@0, @1, @2)
select [InstructorID]
from [dbo].[Instructor]
where @@ROWCOUNT > 0 and [InstructorID] = scope_identity()
',N'@0 nvarchar(50),@1 nvarchar(50),@2 datetime',@0=N'W',@1=N'LF',@2='02  3 2011 12:00:00:000AM'



exec sp_executesql N'insert [dbo].[CourseInstructor]([CourseID], [InstructorID])
values (@0, @1)
',N'@0 int,@1 int',@0=2042,@1=10




exec sp_executesql N'insert [dbo].[OfficeAssignment]([InstructorID], [Location])
values (@0, @1)
',N'@0 int,@1 nvarchar(50)',@0=10,@1=N'天朝'

複製代碼

看 確實是生成了三條SQL語句 符合咱們的要求 而咱們的代碼 卻寫了不多  就輕鬆完成了添加~~

2.修改

修改要比添加麻煩   首先顯示視圖時  要顯示哪些被選中了 還有就是修改時  要在關係表裏刪除原來的 課程教師關係 還要添加新的進去

如何讓EF幫咱們完成這一對一 多對多的複雜的 三張表之間的關係處理呢

仍是先從視圖開始 先解決讓之前被選中的顯示出來

        public ActionResult Edit(int id)
       {
           ViewBag.SelectCourse
= db.Courses.ToList();
           Instructor model
= db.Instructors.Include(i => i.Courses).Include(i => i.OfficeAssignment).Where(i => i.InstructorID == id).SingleOrDefault();
           
return View(model);
       }

先把要修改的這條加載出來返回給視圖   用貪婪加載 把地點和課程都加載出來

複製代碼

      <div class="editor-field">
       @{
            System.Text.StringBuilder sb = new System.Text.StringBuilder("
<table style='width:100%'><tr>");
            List
<ContosoUniversity.Models.Course> list= ViewBag.SelectCourse as List<ContosoUniversity.Models.Course>;
           for (int i = 0; i
< list.Count; i++)
           {
               if (i!
=0&&i % 3 == 0)
               {
                   sb.Append("</tr
><tr>");
               }

               string IsSelect = string.Empty;
               if (Model.Courses.Contains(list[i]))
               {
                   IsSelect = "checked='checked'";
               }
               sb.Append("
<td><input type='checkbox' value='"+list[i].CourseID+"'  "+IsSelect+"    name='selectedCourses'/>"+list[i].Title+"</td>");
           }
           sb.Append("
</tr></table> ");
           }
       @MvcHtmlString.Create(sb.ToString())    
       
</div>

複製代碼

在視圖上加上判斷 遍歷到的這個課程 是否在選中的課程裏 若是在 則加上選中屬性

 string IsSelect = string.Empty;
               if (Model.Courses.Contains(list[i]))
               {
                   IsSelect = "checked='checked'";
               }

這樣就解決了視圖修改時的顯示問題了 下面是點擊修改時  讓咱們先大概想一想 有哪些操做

1.修改 教師表信息

2.修改 地址表信息

3. 刪除之前的課程關係表  和添加新的課程關係表記錄

這裏比較負責的是第三部  咱們來想一想怎麼作 如今 咱們能知道 咱們新選擇了哪些  和 之前選擇了哪些   好比 新選擇的是 {1,2,3}  此次選的是 {3,4,5} 那麼咱們應該

刪除{4,5}  添加{1,2} 便可 這其實就轉換爲一個簡單的算法題了~~ 原文給出了一種方法  我本身也寫了一種  你們能夠比較下看~先上原文的

複製代碼

private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructorToUpdate)
{
   
if (selectedCourses == null)
   {
       instructorToUpdate.Courses
= new List<Course>();
       
return;
   }

   var selectedCoursesHS
= new HashSet<string>(selectedCourses);
   var instructorCourses
= new HashSet<int>
       (instructorToUpdate.Courses.Select(c
=> c.CourseID));
   
foreach (var course in db.Courses)
   {
       
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
       {
           
if (!instructorCourses.Contains(course.CourseID))
           {
               instructorToUpdate.Courses.Add(course);
           }
       }
       
else
       {
           
if (instructorCourses.Contains(course.CourseID))
           {
               instructorToUpdate.Courses.Remove(course);
           }
       }
   }
}

複製代碼

原文利用HashSet 不能存重複項 實現  不過判斷邏輯多了些 但效率應該高  hashset 散列算法 

下面是個人

複製代碼

     private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructor)
       {
           
if (selectedCourses == null)
           {
               instructor.Courses
= new List<Course>();
               
return;
           }
           
//思路 比較之前選擇的和如今選擇的  先取出他們的差集   那麼這部分就是能夠刪除的項  和添加的部分
           string[] beforeSelect=instructor.Courses.Select(i => i.CourseID.ToString()).ToArray();//獲得之前選擇的
           beforeSelect.Except(selectedCourses).ToList().ForEach(n => instructor.Courses.Remove(db.Courses.Find(Convert.ToInt32(n))));
           selectedCourses.Except(beforeSelect).ToList().ForEach(n
=> instructor.Courses.Add(db.Courses.Find(Convert.ToInt32(n))));
       }

複製代碼

這裏 參數selectedCourses 爲如今選中的項   利用linq 內置的方法 Except取出差集  而後 ForEach遍歷移除和添加   一句話搞定  可是效率估計不會過高~~

下面是完整的修改方法

複製代碼

 [HttpPost]
       
public ActionResult Edit(int id, FormCollection collection, string[] selectedCourses)
       {
           
try
           {
               
// TODO: Add update logic here
               var instructor = db.Instructors.Include(i => i.Courses).Where(i => i.InstructorID == id).SingleOrDefault();

               UpdateModel(instructor,
"", null, new string[] {"Courses"});
               UpdateInstructorCourses(selectedCourses, instructor);
               
if (string.IsNullOrWhiteSpace(instructor.OfficeAssignment.Location))
               {
                   instructor.OfficeAssignment
= null;
               }
               db.Entry(instructor).State
= EntityState.Modified;
               db.SaveChanges();
               
return RedirectToAction("Index");
           }
           
catch
           {
               
return View();
           }
       }

       
private void UpdateInstructorCourses(string[] selectedCourses, Instructor instructor)
       {
           
if (selectedCourses == null)
           {
               instructor.Courses
= new List<Course>();
               
return;
           }
           
//思路 比較之前選擇的和如今選擇的  先取出他們的差集   那麼這部分就是能夠刪除的項  和添加的部分
           string[] beforeSelect=instructor.Courses.Select(i => i.CourseID.ToString()).ToArray();//獲得之前選擇的
           beforeSelect.Except(selectedCourses).ToList().ForEach(n => instructor.Courses.Remove(db.Courses.Find(Convert.ToInt32(n))));
           selectedCourses.Except(beforeSelect).ToList().ForEach(n
=> instructor.Courses.Add(db.Courses.Find(Convert.ToInt32(n))));
       }

複製代碼

好了 讓咱們來看下執行的SQL

複製代碼

exec sp_executesql N'update [dbo].[Instructor]
set [LastName] = @0, [FirstName] = @1, [HireDate] = @2
where ([InstructorID] = @3)
',N'@0 nvarchar(50),@1 nvarchar(50),@2 datetime,@3 int',@0=N'W',@1=N'LF',@2='02  3 2011 12:00:00:000AM',@3=10



exec sp_executesql N'update [dbo].[OfficeAssignment]
set [Location] = @0
where ([InstructorID] = @1)
',N'@0 nvarchar(50),@1 int',@0=N'',@1=10



exec sp_executesql N'delete [dbo].[CourseInstructor]
where (([CourseID] = @0) and ([InstructorID] = @1))
',N'@0 int,@1 int',@0=2042,@1=10
exec sp_executesql N'insert [dbo].[CourseInstructor]([CourseID], [InstructorID])
values (@0, @1)
',N'@0 int,@1 int',@0=0,@1=10

複製代碼

看 和咱們想要的效果同樣~~

3.刪除

簡單的說下刪除, 當咱們刪除一條數據時 直接看生成的SQL語句  預計應該是 要刪3個表

exec sp_executesql N'insert [dbo].[CourseInstructor]([CourseID], [InstructorID])
values (@0, @1)
',N'@0 int,@1 int',@0=0,@1=10

exec sp_executesql N'delete [dbo].[OfficeAssignment]
where ([InstructorID] = @0)
',N'@0 int',@0=10

結果是刪除了 2個表的  一對一的刪除了  但是多對多的爲何沒刪除 這實際上是一個簡單的數據庫知識了~~

EF爲咱們建立數據庫時  這個刪除規則是層疊  就是級聯刪除  因此這個關係表的就會被直接刪除了

三.總結

導航屬性的更新操做等 結束了   如今EF的已經基本操做已經結束了  下一節講EF處理併發的策略

相關文章
相關標籤/搜索