.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N180819802
出刊日期: 2018/8/22
考量到未來資料存取程式碼可能會有變動的需求,你可能會選擇使用Repository Pattern來為應用程式加入開發上的彈性。Repository包含描述資料操作的介面(interface),以及實作此介面的物件,封裝資料層(data layer)的程式碼,包含操作資料的邏輯,並將它們對應到一個實體模型(Entity Model)。在這篇文章中,將簡介如何在ASP.NET Core MVC專案之中,加入Repository Pattern,設計資料查詢的網頁。
建立ASP.NET Core MVC應用程式
讓我們從Visual Studio 2017開發環境中新建一個ASP.NET Core MVCWeb Application範本網站開始。啟動Visual Studio 2017開發環境。從Visual Studio開發工具「File」-「New」-「Project」項目,在「New Project」對話盒中,選取左方「Installed」清單 -「Visual C#」程式語言,從「.NET Core」分類中,選取「ASP.NET Core Web Application」。請參考下圖所示,設定專案名稱以及專案存放路徑,然後按下「OK」鍵。

圖 1:新建一個ASP.NET Core Web Application網站。
在「New ASP.NET Core Web Application」對話盒中,確認左上方的清單選取「.NET Core」,右上方的清單ASP.NET Core版本為「ASP.NET Core 2.1」,選取下方的「Web Application (Model-View-Controller)」樣版專案,清除勾選下方的「Enable Docker Support」核取方塊,確定右方的「Authentication」項目設定為「No Authentication」,然後按下「OK」按鈕建立專案,請參考下圖所示:

圖 2:選取「Web Application (Model-View-Controller)」樣版專案。
建立與使用模型(Model)
在本文中,將建立Employee物件,描述員工ID、Name、Age屬性。模型一般建議放在「Models」資料夾。從「Solution Explorer」視窗 - 專案名稱上方按滑鼠右鍵,從快捷選單選擇「Add」- 「New Folder」選項,將新建立的資料夾命名為「Models」。
從「Solution Explorer」視窗 -「Models」資料夾上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「Class」項目,從「Add New Item」對話盒中,選取「Visual C#」-「ASP.NET Core」-「Code」分類下的,「Class」項目,將「Name」設定為「Employee」,最後按下「Add」按鈕,然後加入以下程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReposDemo.Models {
public class Employee {
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
設計DbContext類別
在「Models」資料夾中,加入一個「MyDbContext」類別,使其繼承自「DbContext」類別,並在類別中定義一個名為「Employees」,型別為「DbSet< Employee >」的屬性,並且使用相依性插入(Depenency Injection)在建構函式中插入服務「DbContextOptions< MyDbContext >」:
using Microsoft.EntityFrameworkCore;
namespace ReposDemo.Models {
public class MyDbContext : DbContext {
public MyDbContext( DbContextOptions<MyDbContext> option )
: base( option ) { }
public DbSet<Employee> Employees { get; set; }
}
}
定義Respository介面
Repository Pattern中需要定義一個介面定義資料存取操作。在「Models」資料夾中,加入一個「IEmployeeRepository」介面,加入以下程式碼,定義一個「GetAll」方法,用來取回員工清單:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReposDemo.Models {
public interface IEmployeeRepository {
IEnumerable<Employee> GetAll( );
}
}
實作Respository介面
下一步讓我們建立類別來實作Respository介面,「Models」資料夾中,加入一個「EmployeeRepository」類別,使其實作「IEmployeeRepository」介面,並加入以下程式碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReposDemo.Models {
public class EmployeeRepository : IEmployeeRepository {
private MyDbContext context;
public EmployeeRepository( MyDbContext ctx ) {
context = ctx;
}
public IEnumerable<Employee> GetAll( ) {
return context.Employees;
}
}
}
「EmployeeRepository」類別將透過建構函式注入(Constructor Injection)取得「MyDbContext」物件,這樣在控制器的程式碼中只需要依賴「IEmployeeRepository」介面,而不用管DbContext由何處何來。
設計控制器
EmployeeController
從Visual Studio 2017開發工具 -「Solution Explorer」視窗 - 專案名稱上方按滑鼠右鍵,從快捷選單選擇「Add」- 「New Folder」選項,將新建立的資料夾命名為「Controllers」。
從「Controllers」資料夾上方,按滑鼠右鍵,從快捷選單選擇「Add」- 「Controller」項目。在「Add Scaffold」對話盒中選取「MVC Controller Empty」項目,然後按下「Add 」按鈕。
在「Add Empty MVC Controller」對話盒,將控制器名稱(Controller name)設定為「EmployeeController」,然後按下「Add」按鈕,請參考下圖所示:

圖 3:新增「EmployeeController」控制器。
修改「EmployeeController」控制器程式碼如下,「Index」方法將透過建構函式注入,從Repository介面來取得Employee物件集合:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ReposDemo.Models;
namespace ReposDemo.Controllers {
public class EmployeeController : Controller {
private IEmployeeRepository repo;
public EmployeeController( IEmployeeRepository r ) {
repo = r;
}
public IActionResult Index( ) {
return View( repo.GetAll( ) );
}
}
}
設定連接字串
修改「appsettings.json」檔案,設定連接字串的資料庫伺服器為「(localdb)\\MSSQLLocalDB」;資料庫為「EmployeeDB」,並使用Windows驗證連接到資料庫:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=EmployeeDb;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
設計檢視
建立「Index」檢視。將游標停留在控制器程式設計畫面「Index」方法之中,按滑鼠右鍵,從快捷選單選取「Add View」:

圖 4:建立Index檢視。
在「Add View」對話盒中,設定:
1. View name:「Index」。
2. Template:「List」。
3. Model class:「Employee」。
4. 勾選「Reference script libiaries」與「Use a layout page」項目。
然後按下「Add」按鈕。Visual Studio 2017便會在「Views\Home」資料夾下,新增一個「Index.cshtml」檔案,請參考下圖所示:

圖 5:建立Index檢視。
修改「Index.cshtml」檔案的內容如下:
@model IEnumerable<ReposDemo.Models.Employee>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<p>
總筆數:@Model.Count( )
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor( model => model.ID )
</th>
<th>
@Html.DisplayNameFor( model => model.Name )
</th>
<th>
@Html.DisplayNameFor( model => model.Age )
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach ( var item in Model ) {
<tr>
<td>
@Html.DisplayFor( modelItem => item.ID )
</td>
<td>
@Html.DisplayFor( modelItem => item.Name )
</td>
<td>
@Html.DisplayFor( modelItem => item.Age )
</td>
<td>
</td>
</tr>
}
</tbody>
</table>
在Startup設定與使用服務
修改專案中的「Startup.cs」檔案,在「ConfigureServices」方法加入以下程式,設定資料庫連接字串,將從「appsettings.json」檔案檔案中的「DefaultConnection」而來,並且叫用「IServiceCollection」實體的「AddDbContext」方法,註冊「MyDbContext」物件。接著再叫用「AddTransient」方法,註冊Repository服務:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ReposDemo.Models;
namespace ReposDemo {
public class Startup {
public Startup( IConfiguration configuration ) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services ) {
services.Configure<CookiePolicyOptions>( options => {
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
} );
services.AddMvc( ).SetCompatibilityVersion( CompatibilityVersion.Version_2_1 );
string cnstr = Configuration["ConnectionStrings:DefaultConnection"];
services.AddDbContext<MyDbContext>( options => options.UseSqlServer( cnstr ) );
services.AddTransient<IEmployeeRepository , EmployeeRepository>( );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app , IHostingEnvironment env ) {
if ( env.IsDevelopment( ) ) {
app.UseDeveloperExceptionPage( );
} else {
app.UseExceptionHandler( "/Home/Error" );
app.UseHsts( );
}
app.UseHttpsRedirection( );
app.UseStaticFiles( );
app.UseCookiePolicy( );
app.UseMvc( routes => {
routes.MapRoute(
name: "default" ,
template: "{controller=Employee}/{action=Index}/{id?}" );
} );
}
}
}
建立資料庫
選取Visual Studio 開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。開啟「Developer Command Prompt for VS2017」視窗,先利用「cd」指令切換到專案檔案所在的資料夾:
cd 專案檔案所在的資料夾
接著在提示字元中輸入以下指令:
dotnet ef migrations add initial
執行結果,請參考下圖所示:

圖 6:使用Migration。
接著Visual Studio會在專案中建立一個「Migrations」資料夾,裏頭包含多個C#檔案。選取Visual Studio 開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。然後在提示字元中輸入指令,以更新資料庫:
dotnet ef database update
執行結果,請參考下圖所示:

圖 7:更新資料庫。
測試與執行
選取Visual Studio 開發工具「Build」-「Build Solution」項目編譯目前的專案,確認程式碼能正確編譯。在Visual Studio開發工具,按F5執行網站首頁(請注意:埠號可能會依據實際上的操作而有所不同,請修改為實際的埠號),然後在瀏覽器輸入以下網址(URL)
http://localhost:44398/Employees
執行結果參考如下:

圖 8:資料查詢。
IEnumerable或IQueryable介面
使用Visual Studio 除錯模式執行網站(F5)時,預設會將Entity Framework Core產生的SQL命令輸出在Visual Studio 「Output」視窗。目前Repository 「GetAll」方法回傳的是「IEnumerable」介面:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReposDemo.Models {
public interface IEmployeeRepository {
IEnumerable<Employee> GetAll( );
}
}
若修改控制器程式碼,在叫用「GetAll」方法後,利用「Take」方法,加上篩選條件,將前兩筆資料回傳:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ReposDemo.Models;
namespace ReposDemo.Controllers {
public class EmployeeController : Controller {
private IEmployeeRepository repo;
public EmployeeController( IEmployeeRepository r ) {
repo = r;
}
public IActionResult Index( ) {
return View( repo.GetAll( ).Take(2) );
}
}
}
再次按「F5」以除錯模式執行程式碼,你將會發現,雖然只要求取回兩筆資料,但Entity Framework Core產生出來的SQL查詢子句,還是會將所有資料取回來,也就是說篩選資料的動作是在你的程式中做,而不是在資料庫端做掉,你還是會將所有資料表中的資料取回來:

圖 9:檢視Entity Framework Core產生出來的SQL查詢子句。
對於小型資料表,取回所有資料可能問題不大,但對於大型資料表,這將造成效能上的問題。要修訂這個問題,可以改用「IQueryable」介面。修改Repository介面程式碼,使「GetAll」方法回傳「IQueryable」介面:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReposDemo.Models {
public interface IEmployeeRepository {
IQueryable<Employee> GetAll( );
}
}
修改Repository類別,使「GetAll」方法回傳「IQueryable」介面:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ReposDemo.Models {
public class EmployeeRepository : IEmployeeRepository {
private MyDbContext context;
public EmployeeRepository( MyDbContext ctx ) {
context = ctx;
}
public IQueryable<Employee> GetAll( ) {
return context.Employees;
}
}
}
再次按「F5」以除錯模式執行程式碼,你將會發現,Entity Framework Core產生出來的SQL查詢子句,將會包含篩選資料的語法,也就是說篩選資料的動作是在資料庫端做掉,而不是在你的程式中處理:

圖 10:檢視Entity Framework Core產生出來的SQL查詢子句。
如果認真檢視「Output」視窗,你將會發現SQL命令執行兩次:

圖 11:檢視Entity Framework Core產生出來的SQL查詢子句。
這是因為「IQueryable」介面每回需要資料時,都會下達查詢,要求資料,因此在檢視中的這一行程式碼:
<p>
總筆數:@Model.Count( )
</p>
與這一行程式碼,都分別下達一次SQL命令:
@foreach ( var item in Model ) {
}
若要避免這個問題,你可以改回原來回傳「IEnumberable」介面的設計方式。或者,在控制器明確叫用「ToList」或「ToArray」方法,明確地將「IQueryable」介面轉型成「IEnumberable」,例如以下程式碼,在「ToList」這行程式碼執行時,便會馬上執行SQL查詢一次:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ReposDemo.Models;
namespace ReposDemo.Controllers {
public class EmployeeController : Controller {
private IEmployeeRepository repo;
public EmployeeController( IEmployeeRepository r ) {
repo = r;
}
public IActionResult Index( ) {
return View( repo.GetAll( ).Take( 2 ).ToList() );
}
}
}
如此檢視中的程式碼,便可針對記憶體中的集合物件來進行筆數計算與列舉動作,不需要再下SQL命令從資料庫取回資料。