教學課程:瞭解進階案例 - 使用 EF Core ASP.NET MVC
在上一個教學課程中,您已實作每個階層的數據表繼承。 本教學課程介紹數個值得注意的主題,當您進階使用 Entity Framework Core 開發 ASP.NET Core Web 應用程式時,這些主題將很有用。
在本教學課程中,您會:
- 執行原始 SQL 查詢
- 執行查詢以傳回實體
- 執行查詢以傳回其他類型的結果
- 執行更新查詢
- 檢查 SQL 查詢
- 建立抽象層
- 了解自動變更偵測
- 瞭解 EF Core 原始程式碼和開發計劃
- 瞭解如何使用動態 LINQ 來簡化程式碼
先決條件
執行原始 SQL 查詢
使用 Entity Framework 的優點之一是,它避免將程式代碼與儲存數據的特定方法緊密結合。 其方式是為您產生 SQL 查詢和命令,這也可讓您不必自行撰寫它們。 但當您需要執行手動建立的特定 SQL 查詢時,會有例外狀況。 在這些案例中,Entity Framework Code First API 包含方法,可讓您將 SQL 命令直接傳遞至資料庫。 您在 EF Core 1.0 中有下列選項:
針對傳回實體類型的查詢,請使用
DbSet.FromSql
方法。 傳回的對象必須是DbSet
物件所預期的型別,除非您 關閉追蹤,否則資料庫內容會自動追蹤這些物件。針對非查詢命令使用
Database.ExecuteSqlCommand
。
如果您需要執行傳回非實體類型的查詢,您可以使用 ADO.NET 搭配 EF 所提供的資料庫連線。 即使使用此方法來擷取實體類型,傳回的數據也不會由資料庫內容追蹤。
如同在 Web 應用程式中執行 SQL 命令時一樣,您必須採取預防措施來保護您的網站免受 SQL 插入式攻擊。 其中一個作法是使用參數化查詢,以確保網頁提交的字串無法解譯為 SQL 命令。 在本教學課程中,您會在將使用者輸入整合到查詢時使用參數化查詢。
調用查詢以返回實體
DbSet<TEntity>
類別提供一個方法,可用來執行傳回類型 TEntity
實體的查詢。 若要查看運作方式,您會在 Department 控制器中的 Details
方法中變更程式碼。
在 DepartmentsController.cs
中,在 Details
方法中,將取得部門的程式碼替換為 FromSql
方法呼叫,如下列醒目顯示的程式碼所示:
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync();
if (department == null)
{
return NotFound();
}
return View(department);
}
若要確認新程式代碼正常運作,請選取 [部門] 標籤頁,然後選取 [其中一個部門的詳細數據]。
呼叫一個查詢以返回其他類型的数据或結果。
稍早,您已為 [關於] 頁面建立學生統計數據方格,其中顯示每個註冊日期的學生數目。 您從 Students 實體集取得資料(_context.Students
),並使用 LINQ 將結果投影到 EnrollmentDateGroup
檢視模型物件清單中。 假設您想要撰寫 SQL 本身,而不是使用 LINQ。 若要這樣做,您必須執行 SQL 查詢,以傳回不包含實體物件的其他內容。 在 EF Core 1.0 中,其中一種方法是撰寫 ADO.NET 程式代碼,並從 EF 取得資料庫連線。
在 HomeController.cs
中,以下列程式代碼取代 About
方法:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
新增 using 語句:
using System.Data.Common;
執行應用程式並移至 [關於] 頁面。 顯示與先前相同的數據。
執行更新查詢
假設 Contoso 大學系統管理員想要在資料庫中執行全域變更,例如變更每個課程的點數。 如果大學有大量的課程,那麼將其全部擷取為實體並個別變更會沒有效率。 在本節中,您將實作一個網頁,讓用戶能夠指定要變更所有課程點數數目的因數,而您將執行 SQL UPDATE 語句來進行變更。 網頁看起來會像下圖所示:
在 CoursesController.cs
中,新增 HttpGet 和 HttpPost 的 UpdateCourseCredits 方法:
public IActionResult UpdateCourseCredits()
{
return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}
當控制器處理 HttpGet 要求時,ViewData["RowsAffected"]
中不會傳回任何內容,而檢視會顯示空白文字方塊和送出按鈕,如上圖所示。
點擊 [Update] 按鈕時,會呼叫 HttpPost 方法,而乘數為在文字框中輸入的值。 然後,程式代碼會執行更新課程的 SQL,並將受影響的數據列數目傳回至 ViewData
中的檢視。 當檢視取得 RowsAffected
值時,它會顯示更新的數據列數目。
在 [方案總管]中,在 [Views/Courses] 資料夾上按一下滑鼠右鍵,然後選擇 [新增 > 新項目]。
在 [[新增專案] 對話框中,按兩下左窗格中 [已安裝] 底下的 [ASP.NET Core],按兩下 [檢視 ]Razor ,並將新檢視命名為 UpdateCourseCredits.cshtml
。
在 Views/Courses/UpdateCourseCredits.cshtml
中,將範本程式代碼取代為下列程式代碼:
@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>
@if (ViewData["RowsAffected"] == null)
{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default" />
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
選取 [Courses] 索引卷標,然後將 “/UpdateCourseCredits” 新增至瀏覽器網址列中 URL 的結尾,以執行 UpdateCourseCredits
方法(例如:http://localhost:5813/Courses/UpdateCourseCredits
)。 在文字盒中輸入數位:
點選 「更新」。 您會看到已受影響的資料列數目:
受到影響的數據列
按 返回清單,以查看已修訂學分的課程清單。
請注意,生產程式代碼可確保更新一律會產生有效的數據。 此處顯示的簡化程式代碼可能會將點數相乘,結果可能大於5的數字。 (Credits
屬性具有 [Range(0, 5)]
屬性。)更新查詢會運作,但無效的數據可能會導致系統其他部分出現意外結果,因為系統假定點數為5或更少。
如需原始 SQL 查詢的詳細資訊,請參閱 原始 SQL 查詢。
檢查 SQL 查詢
有時候,能夠查看傳送至資料庫的實際 SQL 查詢會很有幫助。 ASP.NET Core 的內建記錄功能會自動由 EF Core 用來寫入包含查詢和更新 SQL 的記錄。 在本節中,您會看到一些 SQL 記錄範例。
開啟 StudentsController.cs
,並在 Details
方法中設定 if (student == null)
語句上的斷點。
以偵錯模式執行應用程式,然後移至學生的詳細數據頁面。
請轉到 輸出 視窗顯示偵錯輸出,然後您會看到查詢:
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]
您會注意到此處可能會讓您吃驚的事情:SQL 會從 Person 數據表選取最多 2 個數據列(TOP(2)
)。
SingleOrDefaultAsync
方法無法在伺服器上解析為一列。 原因如下:
- 如果查詢會傳回多個數據列,則方法會傳回 null。
- 若要判斷查詢是否會傳回多個數據列,EF 必須檢查它是否至少傳回 2。
請注意,您不需要使用偵錯模式並在斷點停止,即可在 [輸出] 視窗中取得記錄輸出。 這隻是在您想要查看輸出時停止記錄的便利方式。 如果您沒有這麼做,日誌記錄會繼續進行,而且您必須捲動回去以找到您感興趣的部分。
建立抽象層
許多開發人員撰寫程式代碼,以實作儲存庫和單一工作模式作為與 Entity Framework 協同運行的代碼包裝函式。 這些模式旨在建立數據存取層與應用程式商業規則層之間的抽象層。 實作這些模式有助於隔離應用程式與數據存放區中的變更,並有助於自動化單元測試或測試驅動開發 (TDD)。 不過,撰寫其他程式代碼來實作這些模式,不一定是使用EF的應用程式的最佳選擇,原因有數個:
EF 內容類別本身會隔離您的程式代碼與數據存放區特定的程式代碼。
EF 上下文類別可以作為您使用 EF 進行資料庫更新時的工作單元類別。
EF 包含不需要撰寫存放庫程式碼即可實作 TDD 的功能。
如需如何實作存放庫和工作單位模式的詳細資訊,請參閱本教學課程系列 Entity Framework 5 版本。
Entity Framework Core 會實作可用於測試的記憶體內部資料庫提供者。 如需詳細資訊,請參閱 使用 InMemory 測試。
自動變動偵測
Entity Framework 會比較實體目前的值與原始值,以判斷實體變更的方式(因此需要傳送哪些更新至資料庫)。 原始值會在查詢或附加實體時儲存。 造成自動變更偵測的一些方法如下:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Entries
如果您要追蹤大量的實體,而且您在迴圈中多次呼叫其中一種方法,您可能會使用 ChangeTracker.AutoDetectChangesEnabled
屬性暫時關閉自動變更偵測來獲得顯著的效能改善。 例如:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
EF Core 原始程式碼和開發計劃
Entity Framework Core 來源位於 https://github.com/dotnet/efcore。 EF Core 存放庫包含夜間組建、問題追蹤、功能規格、設計會議筆記,以及 未來開發的藍圖。 您可以提交或尋找問題,並參與貢獻。
雖然原始程式碼是開放的,但 Entity Framework Core 作為 Microsoft 產品,仍然獲得完整支援。 Microsoft Entity Framework 小組會控制接受哪些貢獻,並測試所有程式代碼變更,以確保每個版本的品質。
從現有資料庫進行反向工程
若要從現有資料庫中反向生成數據模型,包括實體類別,請使用 scaffold-dbcontext 命令。 請參閱 入門指南。
使用動態 LINQ 簡化程式碼
本系列中的 第三個教學課程 示範如何在 switch
語句中硬式編碼數據行名稱來撰寫 LINQ 程序代碼。 有兩個欄位可供選擇時,這樣運作良好,但如果您有許多欄位,程式碼可能會變得冗長。 若要解決此問題,您可以使用 EF.Property
方法,將屬性的名稱指定為字串。 若要試用此方法,請將 StudentsController
中的 Index
方法取代為下列程序代碼。
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
pageNumber ?? 1, pageSize));
}
致謝
湯姆·迪克斯特拉和里克·安德森(推特 @RickAndMSFT) 撰寫了本教學課程。 Rowan Miller、Diego Vega 和 Entity Framework 小組的其他成員協助進行程式代碼檢閱,並協助偵錯我們在撰寫教學課程程式代碼時所引發的問題。 John Parente 和 Paul Goldman 致力於更新 ASP.NET Core 2.2 的教學課程。
解決常見錯誤
另一個進程所使用的 ContosoUniversity.dll
錯誤資訊:
無法開啟 '...bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll',因為它正由另一個進程使用。
解決方案:
在 IIS Express 中停止網站。 移至 Windows 系統匣,尋找 IIS Express,並以滑鼠右鍵單擊其圖示,選取 Contoso University 網站,然後單擊 [停止網站]。
在 Up 和 Down 方法中,移轉無需代碼的支架設計
可能的原因:
EF CLI 命令不會自動關閉並儲存程式代碼檔案。 如果您在執行 migrations add
命令時尚未儲存變更,EF 就不會找到您的變更。
解決方案:
執行 migrations remove
命令、儲存程式代碼變更,然後重新執行 migrations add
命令。
執行資料庫更新時發生錯誤
在具有現有數據的資料庫中進行架構變更時,可能會收到其他錯誤。 如果您收到無法解析的移轉錯誤,您可以變更連接字串中的資料庫名稱或刪除資料庫。 使用新的資料庫時,沒有任何數據可移轉,而且 update-database 命令更有可能在沒有錯誤的情況下完成。
最簡單的方法是在 appsettings.json
中重新命名資料庫。 下次執行 database update
時,將會建立新的資料庫。
若要刪除 SSOX 中的資料庫,請在資料庫上按下滑鼠右鍵,按兩下 [刪除],然後在 [刪除資料庫] 對話框中,選取 [關閉現有的連線],然後按下 [確定 ]。
若要使用 CLI 刪除資料庫,請執行 database drop
CLI 命令:
dotnet ef database drop
尋找 SQL Server 實例時發生錯誤
錯誤資訊:
建立 SQL Server 的連線時發生網路相關或實例特定錯誤。 找不到伺服器或無法存取。 確認實例名稱正確,且 SQL Server 已設定為允許遠端連線。 (提供者: SQL 網路介面, 錯誤: 26 - 尋找指定的伺服器/實例時發生錯誤)
解決方案:
檢查連接字串。 如果您已手動刪除資料庫檔案,請變更建構字串中的資料庫名稱,以從新的資料庫開始。
取得程序代碼
其他資源
如需 EF Core的詳細資訊,請參閱 Entity Framework Core 檔。 另外還有一本書:Entity Framework Core in Action。
如需如何部署 Web 應用程式的資訊,請參閱 主機和部署 ASP.NET Core。
如需與 ASP.NET Core MVC 相關的其他主題相關信息,例如驗證和授權,請參閱 ASP.NET Core概觀。
如需建立可靠、安全、高效能、可測試且可調整 ASP.NET Core 應用程式的指引,請參閱 Enterprise Web 應用程式模式。 提供可實作模式的完整生產品質範例 Web 應用程式。
後續步驟
在本教學課程中,您會:
- 執行原始 SQL 查詢
- 呼叫一個查詢以返回實體。
- 呼叫查詢以傳回其他類型的
- 被稱為更新查詢
- 已檢查的 SQL 查詢
- 建立抽象層
- 了解自動變更偵測
- 瞭解 EF Core 原始程式碼和開發計劃
- 瞭解如何使用動態 LINQ 來簡化程式碼
這會完成在 ASP.NET Core MVC 應用程式中使用 Entity Framework Core 的這一系列教學課程。 此系列使用新的資料庫;另一個方法是從現有的資料庫 反向工程模型。