MVC3+EF4.1學習系列(五)----- EF查找導航屬性的幾種方式

經過上一篇的學習 咱們把demo的各類關係終於搭建裏起來 以及處理好了如何映射到數據庫等問題 可是 只是搭建好了關係 問題還遠沒有解決sql

這篇就來寫如何查找導航屬性 和查找導航屬性的幾種方式 已經跟蹤生成的SQL來檢測是否滿意 經過這節學習 來明白何時用哪一個~~數據庫

一.三種加載mvc

1.延遲加載app

這是原文中的圖 你們能夠去看下  我模仿上面的作了個測試  出現了  已有打開的與此 Command 相關聯的 DataReader,必須首先將它關閉。asp.net

個人解決辦法是    var departments = db.Departments.ToList();    現讀取出來 而後再遍歷. 而不加ToList()  真正執行SQL語句在 foreach的時候性能

而後再說下 這樣寫之後 SQL語句的執行學習

1.上來先查詢出全部的Department測試

複製代碼

SELECT 
[Extent1].[DepartmentID] AS [DepartmentID],
[Extent1].[Name] AS [Name],
[Extent1].[Budget] AS [Budget],
[Extent1].[StartDate] AS [StartDate],
[Extent1].[InstructorID] AS [InstructorID]
FROM [dbo].[Department] AS [Extent1]

複製代碼

2.再執行到內層foreach時  這個會執行屢次  每次@EntityKeyValue1 等於 迭代到此次的 DepartmentIDui

複製代碼

exec sp_executesql N'SELECT 
[Extent1].[CourseID] AS [CourseID],
[Extent1].[Title] AS [Title],
[Extent1].[Credits] AS [Credits],
[Extent1].[DepartmentID] AS [DepartmentID]
FROM [dbo].[Course] AS [Extent1]
WHERE [Extent1].[DepartmentID] = @EntityKeyValue1
',N'@EntityKeyValue1 int',@EntityKeyValue1=1

複製代碼

也就是說 咱們有多少條Department 就要執行多少次上面的方法   固然 這裏使用的是exec sp_executesql   利用sp_executesql,可以重用執行計劃,這就大大提供了執行性能spa

2.貪婪加載

在執行到第一個foreach 時  就執行了SQL語句 這是EF幫咱們生成的

複製代碼

SELECT 
[Project1].[DepartmentID] AS [DepartmentID],
[Project1].[Name] AS [Name],
[Project1].[Budget] AS [Budget],
[Project1].[StartDate] AS [StartDate],
[Project1].[InstructorID] AS [InstructorID],
[Project1].[C1] AS [C1],
[Project1].[CourseID] AS [CourseID],
[Project1].[Title] AS [Title],
[Project1].[Credits] AS [Credits],
[Project1].[DepartmentID1] AS [DepartmentID1]
FROM ( SELECT
   
[Extent1].[DepartmentID] AS [DepartmentID],
   
[Extent1].[Name] AS [Name],
   
[Extent1].[Budget] AS [Budget],
   
[Extent1].[StartDate] AS [StartDate],
   
[Extent1].[InstructorID] AS [InstructorID],
   
[Extent2].[CourseID] AS [CourseID],
   
[Extent2].[Title] AS [Title],
   
[Extent2].[Credits] AS [Credits],
   
[Extent2].[DepartmentID] AS [DepartmentID1],
   
CASE WHEN ([Extent2].[CourseID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
   
FROM  [dbo].[Department] AS [Extent1]
   
LEFT OUTER JOIN [dbo].[Course] AS [Extent2] ON [Extent1].[DepartmentID] = [Extent2].[DepartmentID]
)  
AS [Project1]
ORDER BY [Project1].[DepartmentID] ASC, [Project1].[C1] ASC

複製代碼

3.顯示加載

先看圖

這個我測試後 效果是和第一個同樣的 並無看出什麼好處? 期待高手指點下 

英文好的也能夠看下原文 

4.關閉延遲加載

若是咱們想啓用延遲加載 能夠經過這兩種方式

1.去掉屬性裏的virtual

2.context.Configuration.LazyLoadingEnabled = false;


二.實戰開始 建立教師頁

先上實現後的效果圖

從圖中 咱們能夠看出這個要處理的關係

1對1的 教師和辦公地點

1對多的 教師教的課程

普通的多對多的

多對多的(關係表裏有數據的)  課程和學生  查看選擇課程的學生和學分

1.建立viewmodel

有時 咱們的頁面 顯示的不是一個實體類的內容  這個時候咱們能夠建立一個ViewModel 來展現界面

複製代碼

using System;
using System.Collections.Generic;
using ContosoUniversity.Models;

namespace ContosoUniversity.ViewModels
{
   
public class InstructorIndexData
   {
       
public IEnumerable<Instructor> Instructors { get; set; }
       
public IEnumerable<Course> Courses { get; set; }
       
public IEnumerable<Enrollment> Enrollments { get; set; }
   }
}

複製代碼

2.建立控制器添加Index

複製代碼

public ActionResult Index(Int32? id, Int32? courseID)
{
   var viewModel
= new InstructorIndexData();
   viewModel.Instructors
= db.Instructors
       .Include(i
=> i.OfficeAssignment)
       .Include(i
=> i.Courses.Select(c => c.Department))
       .OrderBy(i
=> i.LastName);

   
if (id != null)
   {
       ViewBag.InstructorID
= id.Value;
       viewModel.Courses
= viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses;
   }

   
if (courseID != null)
   {
       ViewBag.CourseID
= courseID.Value;
       viewModel.Enrollments
= viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments;
   }

   
return View(viewModel);
}

複製代碼

先看進來訪問的這一塊

  viewModel.Instructors = db.Instructors
       .Include(i
=> i.OfficeAssignment)
       .Include(i
=> i.Courses.Select(c => c.Department))
       .OrderBy(i
=> i.LastName);

從最上面的圖中 咱們能夠看到  要顯示有教師信息 辦公地址 和所教課程

因而 咱們使用貪婪加載出辦公地址和課程  可是 原文教程裏 還Select(c => c.Department) 把院系也一塊兒加載了進來  我認爲這是不必的

因而 我把代碼修改成

 db.Instructors
               .Include(i
=> i.OfficeAssignment)
               .Include(i
=> i.Courses)
               .OrderBy(i
=> i.LastName);

去掉了對院系的貪婪加載

看下生成的SQL語句

複製代碼

SELECT 
[Project1].[InstructorID1] AS [InstructorID],
[Project1].[InstructorID] AS [InstructorID1],
[Project1].[LastName] AS [LastName],
[Project1].[FirstName] AS [FirstName],
[Project1].[HireDate] AS [HireDate],
[Project1].[InstructorID2] AS [InstructorID2],
[Project1].[Location] AS [Location],
[Project1].[C1] AS [C1],
[Project1].[CourseID] AS [CourseID],
[Project1].[Title] AS [Title],
[Project1].[Credits] AS [Credits],
[Project1].[DepartmentID] AS [DepartmentID]
FROM ( SELECT
   
[Extent1].[InstructorID] AS [InstructorID],
   
[Extent1].[LastName] AS [LastName],
   
[Extent1].[FirstName] AS [FirstName],
   
[Extent1].[HireDate] AS [HireDate],
   
[Extent2].[InstructorID] AS [InstructorID1],
   
[Extent3].[InstructorID] AS [InstructorID2],
   
[Extent3].[Location] AS [Location],
   
[Join3].[CourseID1] AS [CourseID],
   
[Join3].[Title] AS [Title],
   
[Join3].[Credits] AS [Credits],
   
[Join3].[DepartmentID] AS [DepartmentID],
   
CASE WHEN ([Join3].[CourseID2] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
   
FROM    [dbo].[Instructor] AS [Extent1]
   
LEFT OUTER JOIN [dbo].[OfficeAssignment] AS [Extent2] ON [Extent1].[InstructorID] = [Extent2].[InstructorID]
   
LEFT OUTER JOIN [dbo].[OfficeAssignment] AS [Extent3] ON [Extent2].[InstructorID] = [Extent3].[InstructorID]
   
LEFT OUTER JOIN  (SELECT [Extent4].[CourseID] AS [CourseID2], [Extent4].[InstructorID] AS [InstructorID], [Extent5].[CourseID] AS [CourseID1], [Extent5].[Title] AS [Title], [Extent5].[Credits] AS [Credits], [Extent5].[DepartmentID] AS [DepartmentID]
       
FROM  [dbo].[CourseInstructor] AS [Extent4]
       
INNER JOIN [dbo].[Course] AS [Extent5] ON [Extent5].[CourseID] = [Extent4].[CourseID] ) AS [Join3] ON [Extent1].[InstructorID] = [Join3].[InstructorID]
)  
AS [Project1]
ORDER BY [Project1].[LastName] ASC, [Project1].[InstructorID1] ASC, [Project1].[InstructorID] ASC, [Project1].[InstructorID2] ASC, [Project1].[C1] ASC

複製代碼

繼續分析

  if (id != null)
   {
       ViewBag.InstructorID
= id.Value;
       viewModel.Courses
= viewModel.Instructors.Where(i => i.InstructorID == id.Value).Single().Courses;
   }

若是點擊教師 則可查看該教師教的課程  這個id 就是教師ID 一下子會在視圖展現這個 這個就是根據教師查看課程 

接着是點擊課程 查看所選的學生和分數

    if (courseId != null)
           {
               viewModel.Enrollments
= viewModel.Courses.Where(i => i.CourseID == courseId.Value).Single().Enrollments;
           }

這裏還給出裏另外一種方法

複製代碼

    if (courseID != null)
   {
       ViewBag.CourseID
= courseID.Value;

       var selectedCourse
= viewModel.Courses.Where(x => x.CourseID == courseID).Single();
       db.Entry(selectedCourse).Collection(x
=> x.Enrollments).Load();
       
foreach (Enrollment enrollment in selectedCourse.Enrollments)
       {
           db.Entry(enrollment).Reference(x
=> x.Student).Load();
       }
                       
       viewModel.Enrollments
= viewModel.Courses.Where(x => x.CourseID == courseID).Single().Enrollments;
   }

複製代碼

最後上視圖

複製代碼

@model ContosoUniversity.ViewModels.InstructorIndexData

@{
   ViewBag.Title = "Instructors";
}

<h2>Instructors</h2>

<p>
   @Html.ActionLink("Create New", "Create")
</p>
<table>
   
<tr>
       
<th></th>
       
<th>Last Name</th>
       
<th>First Name</th>
       
<th>Hire Date</th>
       
<th>Office</th>
       
<th>Courses</th>
   
</tr>
   @foreach (var item in Model.Instructors)
   {
       string selectedRow = "";
       if (item.InstructorID == ViewBag.PersonID)
       {
           selectedRow = "selectedrow";
       }
       
<tr class="@selectedRow" valign="top">
           
<td>
               @Html.ActionLink("Select", "Index", new { id = item.InstructorID }) |
               @Html.ActionLink("Edit", "Edit", new { id = item.InstructorID }) |
               @Html.ActionLink("Details", "Details", new { id = item.InstructorID }) |
               @Html.ActionLink("Delete", "Delete", new { id = item.InstructorID })
           
</td>
           
<td>
               @item.LastName
           
</td>
           
<td>
               @item.FirstMidName
           
</td>
           
<td>
               @String.Format("{0:d}", item.HireDate)
           
</td>
           
<td>
               @if (item.OfficeAssignment != null)
               {
                   @item.OfficeAssignment.Location  
               }
           
</td>
           
<td>
               @{
                   foreach (var course in item.Courses)
                   {
                       @course.CourseID @:
&nbsp; @course.Title <br />
                   }
               }
           
</td>
       
</tr>
   }
</table>

@if (Model.Courses != null)
{
   
<h3>Courses Taught by Selected Instructor</h3>
<table>
   
<tr>
       
<th></th>
       
<th>ID</th>
       
<th>Title</th>
       
<th>Department</th>
   
</tr>

   @foreach (var item in Model.Courses)
   {
       string selectedRow = "";
       if (item.CourseID == ViewBag.CourseID)
       {
           selectedRow = "selectedrow";
       }
   
<tr class="@selectedRow">
       
<td>
           @Html.ActionLink("Select", "Index", new { courseID = item.CourseID })
       
</td>
       
<td>
           @item.CourseID
       
</td>
       
<td>
           @item.Title
       
</td>
       
<td>
           @item.Department.Name
       
</td>
   
</tr>
   }

</table>
}

@if (Model.Enrollments != null)
{
   
<h3>
       Students Enrolled in Selected Course
</h3>
   
<table>
       
<tr>
           
<th>Name</th>
           
<th>Grade</th>
       
</tr>
       @foreach (var item in Model.Enrollments)
       {
           
<tr>
               
<td>
                   @item.Student.FullName
               
</td>
               
<td>
                   @item.Grade
               
</td>
           
</tr>
       }
   
</table>
}

複製代碼

三.上節的一個問題與疑問的提出

再上節的創建關係中 有一個這樣的問題  一對多的關係中 是否應該爲導航屬性 再專門創建一個ID

好比咱們可 課程與院系  一個院系能夠有多個課程  一個課程只能屬於一個院系 那咱們是否應該在課程類裏 加入院系ID呢

複製代碼

    /// <summary>
   
/// 課程類
   
/// </summary>
   public class Course
   {
       
/// <summary>
       
/// 課程ID
       
/// </summary>
       [DatabaseGenerated(DatabaseGeneratedOption.None)]
       [Display(Name
= "Number")]
       
public int CourseID { get; set; }
       
/// <summary>
       
/// 課程名稱
       
/// </summary>
       [Required(ErrorMessage = "Title is required.")]
       [MaxLength(
50)]
       
public string Title { get; set; }
       
/// <summary>
       
/// 學分
       
/// </summary>
       [Required(ErrorMessage = "Number of credits is required.")]
       [Range(
0, 5, ErrorMessage = "Number of credits must be between 0 and 5.")]
       
public int Credits { get; set; }

       [Display(Name
= "Department")]
       
public int DepartmentID { get; set; }


       
/// <summary>
       
/// 關係表導航屬性  一個課程容許被屢次報名等級
       
/// </summary>
       public virtual ICollection<Enrollment> Enrollments { get; set; }
       
public virtual Department Department { get; set; }
       
public virtual ICollection<Instructor> Instructors { get; set; }


   }

複製代碼

這裏面加了 院系ID  我之前一直以爲沒有必要加這個 今天在作這個導航屬性查找時 發現一個問題 作個小實驗

好比我想獲得其中一個課程的ID 若是有院系ID 屬性 能夠這麼寫

   var courses = db.Courses.ToList();
     
int i = courses[0].DepartmentID;

若是沒 能夠這麼寫

 int i = courses[0].Department.DepartmentID;

首先 這個都沒有用貪婪加載 默認的延遲加載 若是你使用上面的 則不會往數據庫裏去執行一條根據課程ID查找院系的SQL語句

但你使用下面的 則會往數據庫裏發送一條查找語句

這點 EF作的是並很差的 在NH裏 兩種方法 都不會發送  由於在下面那裏使用了代理 而EF沒有

我想問的是 是我哪操做的不對麼? 形成了這個緣由? 請高手解答下

四.總結

關係的加載就結束了 其實寫關係加載的園子中有很多好文章了 我這裏寫的少了些~~

不過關係的操做尚未結束

相關文章
相關標籤/搜索