在 ASP.NET MVC 應用程式中實作存放庫和工作單位模式, (9/10)

By Tom Dykstra

Contoso University 範例 Web 應用程式示範如何使用 Entity Framework 5 Code First 和 Visual Studio 2012 建立 ASP.NET MVC 4 應用程式。 如需教學課程系列的資訊,請參閱本系列的第一個教學課程

注意

如果您遇到無法解決的問題, 請下載已完成的章節 ,並嘗試重現您的問題。 一般而言,您可以將程式碼與已完成的程式碼進行比較,以找出問題的解決方案。 如需一些常見的錯誤以及如何解決這些問題,請參閱 錯誤和因應措施。

在上一個教學課程中,您已使用繼承來減少 和 Instructor 實體類別中的 Student 備援程式碼。 在本教學課程中,您將瞭解如何使用 CRUD 作業的存放庫和工作單元模式。 如同上一個教學課程,在此教學課程中,您將變更程式碼與您已建立的頁面搭配運作的方式,而不是建立新的頁面。

存放庫和工作單位模式

存放庫和工作單位模式旨在建立資料存取層與應用程式商務邏輯層之間的抽象層。 實作這些模式可協助隔離您的應用程式與資料存放區中的變更,並可促進自動化單元測試或測試驅動開發 (TDD)。

在本教學課程中,您將為每個實體類型實作存放庫類別。 Student針對實體類型,您將建立存放庫介面和存放庫類別。 當您在控制器中具現化存放庫時,您將使用 介面,讓控制器接受實作存放庫介面之任何物件的參考。 當控制器在 Web 服務器下執行時,它會收到可與 Entity Framework 搭配運作的存放庫。 當控制器在單元測試類別下執行時,它會收到存放庫,該存放庫可與儲存的資料搭配使用,讓您可以輕鬆地操作測試,例如記憶體內部集合。

稍後在本教學課程中,您將針對控制器中的 和 實體類型使用多個存放庫和 Department 工作類別 CourseCourse 單元。 工作類別的單位會藉由建立所有存放庫共用的單一資料庫內容類別別,來協調多個存放庫的工作。 如果您想要能夠執行自動化單元測試,您會以您針對存放庫所做的相同方式,建立和使用這些類別的 Student 介面。 不過,若要讓教學課程保持簡單,您將建立並使用這些類別,而不需要介面。

下圖顯示一種概念化控制器與內容類別別之間的關聯性的方法,相較于完全不使用存放庫或工作單位模式。

Repository_pattern_diagram

您不會在此教學課程系列中建立單元測試。 如需使用存放庫模式之 MVC 應用程式的 TDD 簡介,請參閱逐步解說 :搭配使用 TDD 搭配 ASP.NET MVC。 如需存放庫模式的詳細資訊,請參閱下列資源:

注意

有許多方式可以實作存放庫和工作單位模式。 您可以搭配或不使用工作類別的單位來使用存放庫類別。 您可以為所有實體類型實作單一存放庫,或針對每個類型實作一個存放庫。 如果您為每個類型實作一個,您可以使用不同的類別、泛型基類和衍生類別,或抽象基類和衍生類別。 您可以在存放庫中包含商務邏輯,或將其限制為數據存取邏輯。 您也可以使用 IDbSet 介面,而不是實體集的 DbSet 類型,將抽象層建置至資料庫內容類別別。 本教學課程所示實作抽象層的方法,是一個選項供您考慮,而不是所有案例和環境的建議。

建立學生存放庫課程

DAL 資料夾中,建立名為 IStudentRepository.cs 的 類別檔案,並以下列程式碼取代現有的程式碼:

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

namespace ContosoUniversity.DAL
{
    public interface IStudentRepository : IDisposable
    {
        IEnumerable<Student> GetStudents();
        Student GetStudentByID(int studentId);
        void InsertStudent(Student student);
        void DeleteStudent(int studentID);
        void UpdateStudent(Student student);
        void Save();
    }
}

此程式碼會宣告一組典型的 CRUD 方法,包括兩個讀取方法:一個傳回所有 Student 實體,另一個依識別碼尋找單 Student 一實體。

DAL 資料夾中,建立名為 StudentRepository.cs 檔案的 類別檔案。 將現有的程式碼取代為下列實作 IStudentRepository 介面的程式碼:

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

namespace ContosoUniversity.DAL
{
    public class StudentRepository : IStudentRepository, IDisposable
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public IEnumerable<Student> GetStudents()
        {
            return context.Students.ToList();
        }

        public Student GetStudentByID(int id)
        {
            return context.Students.Find(id);
        }

        public void InsertStudent(Student student)
        {
            context.Students.Add(student);
        }

        public void DeleteStudent(int studentID)
        {
            Student student = context.Students.Find(studentID);
            context.Students.Remove(student);
        }

        public void UpdateStudent(Student student)
        {
            context.Entry(student).State = EntityState.Modified;
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

資料庫內容是在類別變數中定義,而且建構函式預期呼叫物件傳入內容實例:

private SchoolContext context;

public StudentRepository(SchoolContext context)
{
    this.context = context;
}

您可以在存放庫中具現化新的內容,但是,如果您在一個控制器中使用多個存放庫,每個存放庫最後都會有個別的內容。 稍後您會在控制器中使用 Course 多個存放庫,並瞭解工作類別的單位如何確保所有存放庫都使用相同的內容。

存放庫會實作 IDisposable 並處置您稍早在控制器中所見的資料庫內容,以及其 CRUD 方法會以您稍早看到的相同方式呼叫資料庫內容。

將學生控制器變更為使用存放庫

StudentController.cs中,以下列程式碼取代類別中目前的程式碼。 所做的變更已醒目提示。

using System;
using System.Data;
using System.Linq;
using System.Web.Mvc;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;
using PagedList;

namespace ContosoUniversity.Controllers
{
   public class StudentController : Controller
   {
      private IStudentRepository studentRepository;

      public StudentController()
      {
         this.studentRepository = new StudentRepository(new SchoolContext());
      }

      public StudentController(IStudentRepository studentRepository)
      {
         this.studentRepository = studentRepository;
      }

      //
      // GET: /Student/

      public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
      {
         ViewBag.CurrentSort = sortOrder;
         ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
         ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

         if (searchString != null)
         {
            page = 1;
         }
         else
         {
            searchString = currentFilter;
         }
         ViewBag.CurrentFilter = searchString;

         var students = from s in studentRepository.GetStudents()
                        select s;
         if (!String.IsNullOrEmpty(searchString))
         {
            students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                                   || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
         }
         switch (sortOrder)
         {
            case "name_desc":
               students = students.OrderByDescending(s => s.LastName);
               break;
            case "Date":
               students = students.OrderBy(s => s.EnrollmentDate);
               break;
            case "date_desc":
               students = students.OrderByDescending(s => s.EnrollmentDate);
               break;
            default:  // Name ascending 
               students = students.OrderBy(s => s.LastName);
               break;
         }

         int pageSize = 3;
         int pageNumber = (page ?? 1);
         return View(students.ToPagedList(pageNumber, pageSize));
      }

      //
      // GET: /Student/Details/5

      public ViewResult Details(int id)
      {
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // GET: /Student/Create

      public ActionResult Create()
      {
         return View();
      }

      //
      // POST: /Student/Create

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
           Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.InsertStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

      //
      // GET: /Student/Edit/5

      public ActionResult Edit(int id)
      {
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // POST: /Student/Edit/5

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
         Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.UpdateStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

      //
      // GET: /Student/Delete/5

      public ActionResult Delete(bool? saveChangesError = false, int id = 0)
      {
         if (saveChangesError.GetValueOrDefault())
         {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
         }
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // POST: /Student/Delete/5

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Delete(int id)
      {
         try
         {
            Student student = studentRepository.GetStudentByID(id);
            studentRepository.DeleteStudent(id);
            studentRepository.Save();
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
         }
         return RedirectToAction("Index");
      }

      protected override void Dispose(bool disposing)
      {
         studentRepository.Dispose();
         base.Dispose(disposing);
      }
   }
}

控制器現在會針對實 IStudentRepository 作 介面的物件宣告類別變數,而不是內容類別別:

private IStudentRepository studentRepository;

預設 (無參數) 建構函式會建立新的內容實例,而選擇性建構函式可讓呼叫端傳入內容實例。

public StudentController()
{
    this.studentRepository = new StudentRepository(new SchoolContext());
}

public StudentController(IStudentRepository studentRepository)
{
    this.studentRepository = studentRepository;
}

(如果您使用 相依性插入或 DI,則不需要預設建構函式,因為 DI 軟體可確保一律會提供正確的存放庫物件。)

在 CRUD 方法中,現在會呼叫存放庫,而不是內容:

var students = from s in studentRepository.GetStudents()
               select s;
Student student = studentRepository.GetStudentByID(id);
studentRepository.InsertStudent(student);
studentRepository.Save();
studentRepository.UpdateStudent(student);
studentRepository.Save();
studentRepository.DeleteStudent(id);
studentRepository.Save();

Dispose而且方法現在會處置存放庫,而不是內容:

studentRepository.Dispose();

執行網站,然後按一下 [學生] 索引 標籤。

Students_Index_page

頁面看起來和運作方式相同,如同您變更程式碼以使用存放庫之前所做的一樣,其他 Student 頁面也會運作相同。 不過,控制器的 方法執行篩選和排序的方式 Index 有一個重要差異。 此方法的原始版本包含下列程式碼:

var students = from s in context.Students
               select s;
if (!String.IsNullOrEmpty(searchString))
{
    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                           || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

更新 Index 的方法包含下列程式碼:

var students = from s in studentRepository.GetStudents()
                select s;
if (!String.IsNullOrEmpty(searchString))
{
    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                        || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

只有醒目提示的程式碼已變更。

在原始版本的程式碼中, students 會輸入為 IQueryable 物件。 查詢不會傳送至資料庫,直到使用 之類的 ToList 方法將查詢轉換成集合,直到索引檢視存取學生模型為止。 Where上述原始程式碼中的 方法會 WHERE 成為 SQL 查詢中傳送至資料庫的 子句。 接著,這表示資料庫只會傳回選取的實體。 不過,由於變更 context.StudentsstudentRepository.GetStudents() 為 ,這個語句之後的 students 變數是 IEnumerable 包含資料庫中所有學生的集合。 套用 Where 方法的最終結果相同,但現在工作是在網頁伺服器上的記憶體中完成,而不是由資料庫完成。 對於傳回大量資料的查詢,這可能會沒有效率。

提示

IQueryable 與 IEnumerable

實作如這裡所示的存放庫之後,即使您在 [搜尋] 方塊中輸入某個專案,傳送至SQL Server查詢會傳回所有 Student 資料列,因為它不包含您的搜尋準則:

程式碼的螢幕擷取畫面,其中顯示已實作並醒目提示的新學生存放庫。

SELECT 
'0X0X' AS [C1], 
[Extent1].[PersonID] AS [PersonID], 
[Extent1].[LastName] AS [LastName], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[EnrollmentDate] AS [EnrollmentDate]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[Discriminator] = N'Student'

此查詢會傳回所有學生資料,因為存放庫執行查詢而不知道搜尋準則。 排序、套用搜尋準則,以及選取資料子集以進行分頁 (在此案例中只會顯示 3 個數據列的程式,稍後在集合上呼叫 方法時 ToPagedList ,會在記憶體中 IEnumerable 完成) 。

在您實作存放庫) 之前,在舊版的程式碼 (中,查詢不會傳送至資料庫, ToPagedList 直到您在物件上 IQueryable 呼叫搜尋準則之後才會傳送至資料庫。

顯示學生控制器程式碼的螢幕擷取畫面。程式碼的搜尋字串資料列和程式碼的 [分頁清單] 資料列會反白顯示。

在 物件上 IQueryable 呼叫 ToPagedList 時,傳送至SQL Server的查詢會指定搜尋字串,而且只會傳回符合搜尋準則的資料列,而且不需要在記憶體中完成篩選。

exec sp_executesql N'SELECT TOP (3) 
[Project1].[StudentID] AS [StudentID], 
[Project1].[LastName] AS [LastName], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[EnrollmentDate] AS [EnrollmentDate]
FROM ( SELECT [Project1].[StudentID] AS [StudentID], [Project1].[LastName] AS [LastName], [Project1].[FirstName] AS [FirstName], [Project1].[EnrollmentDate] AS [EnrollmentDate], row_number() OVER (ORDER BY [Project1].[LastName] ASC) AS [row_number]
FROM ( SELECT 
    [Extent1].[StudentID] AS [StudentID], 
    [Extent1].[LastName] AS [LastName], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[EnrollmentDate] AS [EnrollmentDate]
    FROM [dbo].[Student] AS [Extent1]
    WHERE (( CAST(CHARINDEX(UPPER(@p__linq__0), UPPER([Extent1].[LastName])) AS int)) > 0) OR (( CAST(CHARINDEX(UPPER(@p__linq__1), UPPER([Extent1].[FirstName])) AS int)) > 0)
)  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[LastName] ASC',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'Alex',@p__linq__1=N'Alex'

(下列教學課程說明如何檢查傳送至 SQL Server.) 的查詢

下一節說明如何實作存放庫方法,讓您指定資料庫應該完成這項工作。

您現在已建立控制器與 Entity Framework 資料庫內容之間的抽象層。 如果您要使用此應用程式執行自動化單元測試,您可以在實作 的單元測試專案中建立替代存放 IStudentRepository 庫類別此模擬存放庫類別可以操作記憶體內部集合,而不是呼叫內容來讀取和寫入資料,以測試控制器函式。

實作泛型存放庫和工作類別的單位

為每個實體類型建立存放庫類別可能會導致大量備援程式碼,而且可能會導致部分更新。 例如,假設您必須將兩個不同的實體類型更新為相同交易的一部分。 如果每個實例都使用不同的資料庫內容實例,其中一個可能會成功,另一個可能會失敗。 最小化備援程式碼的其中一種方式是使用泛型存放庫,以及確保所有存放庫都使用相同的資料庫內容 (,因此協調所有更新) 是使用工作類別的單位。

在本教學課程的本節中,您將建立 GenericRepository 類別和 UnitOfWork 類別,並在控制器中 Course 使用這些類別來存取 DepartmentCourse 實體集。 如先前所述,若要讓本教學課程的這個部分保持簡單,您不會為這些類別建立介面。 但是,如果您要使用它們來協助 TDD,您通常會以您執行存放庫的相同方式,以介面實作 Student 它們。

建立泛型存放庫

DAL 資料夾中,建立 GenericRepository.cs ,並以下列程式碼取代現有的程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.Entity;
using ContosoUniversity.Models;
using System.Linq.Expressions;

namespace ContosoUniversity.DAL
{
    public class GenericRepository<TEntity> where TEntity : class
    {
        internal SchoolContext context;
        internal DbSet<TEntity> dbSet;

        public GenericRepository(SchoolContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    }
}

類別變數會針對資料庫內容宣告,以及針對具現化存放庫的實體集:

internal SchoolContext context;
internal DbSet dbSet;

建構函式會接受資料庫內容實例,並初始化實體集變數:

public GenericRepository(SchoolContext context)
{
    this.context = context;
    this.dbSet = context.Set<TEntity>();
}

方法 Get 會使用 Lambda 運算式來允許呼叫程式碼指定篩選準則和資料行來排序結果,而字串參數可讓呼叫端提供以逗號分隔的導覽屬性清單來進行積極式載入:

public virtual IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "")

程式碼 Expression<Func<TEntity, bool>> filter 表示呼叫端會根據 TEntity 類型提供 Lambda 運算式,而此運算式會傳回布林值。 例如,如果存放庫已針對 Student 實體類型具現化,則呼叫方法中的程式碼可能會為 filter 參數指定 student => student.LastName == "Smith 「 。

程式碼 Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy 也表示呼叫端會提供 Lambda 運算式。 但在此情況下,運算式的輸入是 IQueryable 型別的物件 TEntity 。 運算式會傳回該 IQueryable 物件的已排序版本。 例如,如果存放庫已針對 Student 實體類型具現化,則呼叫方法中的程式碼可能會針對 orderBy 參數指定 q => q.OrderBy(s => s.LastName)

方法中的 Get 程式碼會 IQueryable 建立 物件,然後在有下列專案時套用篩選運算式:

IQueryable<TEntity> query = dbSet;

if (filter != null)
{
    query = query.Where(filter);
}

接下來,它會在剖析逗號分隔清單之後套用積極式載入運算式:

foreach (var includeProperty in includeProperties.Split
    (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) 
{ 
    query = query.Include(includeProperty); 
}

最後,如果有運算式並傳回結果,則會套用 orderBy 運算式;否則它會從未排序的查詢傳回結果:

if (orderBy != null)
{
    return orderBy(query).ToList();
}
else
{
    return query.ToList();
}

當您呼叫 Get 方法時,您可以對 方法傳回的 IEnumerable 集合執行篩選和排序,而不是提供這些函式的參數。 但是,排序和篩選工作接著會在網頁伺服器上的記憶體中完成。 藉由使用這些參數,您可以確定工作是由資料庫完成,而不是網頁伺服器。 替代方式是針對特定實體類型建立衍生類別,並新增特製化 Get 方法,例如 GetStudentsInNameOrderGetStudentsByName 。 不過,在複雜的應用程式中,這可能會導致大量的這類衍生類別和特製化方法,這可能會有更多工作可維護。

InsertUpdate 方法中的 GetByID 程式碼類似于您在非泛型存放庫中所看到的程式碼。 (您未在簽章中 GetByID 提供積極式載入參數,因為您無法使用 Find method 執行積極式載入。)

方法提供兩個 Delete 多載:

public virtual void Delete(object id)
{
    TEntity entityToDelete = dbSet.Find(id);
    dbSet.Remove(entityToDelete);
}

public virtual void Delete(TEntity entityToDelete)
{
    if (context.Entry(entityToDelete).State == EntityState.Detached)
    {
        dbSet.Attach(entityToDelete);
    }
    dbSet.Remove(entityToDelete);
}

其中一個可讓您只傳入要刪除之實體的識別碼,而其中一個會採用實體實例。 如您在 處理並行 教學課程中所見,針對並行處理,您需要採用 Delete 包含追蹤屬性原始值的實體實例的方法。

此泛型存放庫會處理一般 CRUD 需求。 當特定實體類型有特殊需求時,例如更複雜的篩選或排序,您可以建立具有該類型其他方法的衍生類別。

建立工作單元類別

工作類別的單位有一個用途:為了確保當您使用多個存放庫時,它們會共用單一資料庫內容。 如此一來,當工作單位完成時,您可以在該內容實例上呼叫 SaveChanges 方法,並確保所有相關變更都會協調。 類別所需的所有專案都是 Save 每個存放庫的方法和屬性。 每個存放庫屬性都會傳回已使用與其他存放庫實例相同的資料庫內容實例具現化的存放庫實例。

DAL 資料夾中,建立名為 UnitOfWork.cs 的類別檔案,並以下列程式碼取代範本程式碼:

using System;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class UnitOfWork : IDisposable
    {
        private SchoolContext context = new SchoolContext();
        private GenericRepository<Department> departmentRepository;
        private GenericRepository<Course> courseRepository;

        public GenericRepository<Department> DepartmentRepository
        {
            get
            {

                if (this.departmentRepository == null)
                {
                    this.departmentRepository = new GenericRepository<Department>(context);
                }
                return departmentRepository;
            }
        }

        public GenericRepository<Course> CourseRepository
        {
            get
            {

                if (this.courseRepository == null)
                {
                    this.courseRepository = new GenericRepository<Course>(context);
                }
                return courseRepository;
            }
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

程式碼會為資料庫內容和每個存放庫建立類別變數。 context針對變數,會具現化新的內容:

private SchoolContext context = new SchoolContext();
private GenericRepository<Department> departmentRepository;
private GenericRepository<Course> courseRepository;

每個存放庫屬性都會檢查存放庫是否已存在。 如果沒有,它會具現化存放庫,並傳入內容實例。 因此,所有存放庫都會共用相同的內容實例。

public GenericRepository<Department> DepartmentRepository
{
    get
    {

        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

方法 Save 會在資料庫內容上呼叫 SaveChanges

如同具現化類別變數中資料庫內容的任何類別,類別 UnitOfWorkIDisposable 實作並處置內容。

變更課程式控制制器以使用 UnitOfWork 類別和存放庫

以下列程式碼取代您目前在 CourseController.cs 中的程式碼:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;

namespace ContosoUniversity.Controllers
{
   public class CourseController : Controller
   {
      private UnitOfWork unitOfWork = new UnitOfWork();

      //
      // GET: /Course/

      public ViewResult Index()
      {
         var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
         return View(courses.ToList());
      }

      //
      // GET: /Course/Details/5

      public ViewResult Details(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         return View(course);
      }

      //
      // GET: /Course/Create

      public ActionResult Create()
      {
         PopulateDepartmentsDropDownList();
         return View();
      }

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
          [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Insert(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
         }
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      public ActionResult Edit(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
           [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Update(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
         }
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
      {
         var departmentsQuery = unitOfWork.DepartmentRepository.Get(
             orderBy: q => q.OrderBy(d => d.Name));
         ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
      }

      //
      // GET: /Course/Delete/5

      public ActionResult Delete(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         return View(course);
      }

      //
      // POST: /Course/Delete/5

      [HttpPost, ActionName("Delete")]
      [ValidateAntiForgeryToken]
      public ActionResult DeleteConfirmed(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         unitOfWork.CourseRepository.Delete(id);
         unitOfWork.Save();
         return RedirectToAction("Index");
      }

      protected override void Dispose(bool disposing)
      {
         unitOfWork.Dispose();
         base.Dispose(disposing);
      }
   }
}

此程式碼會新增 類別的 UnitOfWork 類別變數。 (如果您在這裡使用介面,則不會在此初始化變數;相反地,您會實作兩個建構函式的模式,就像您針對 Student repository.)

private UnitOfWork unitOfWork = new UnitOfWork();

在 類別的其餘部分,所有資料庫內容的參考都會由適當存放庫的參考取代,並使用 UnitOfWork 屬性來存取存放庫。 方法 DisposeUnitOfWork 處置 實例。

var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Insert(course);
unitOfWork.Save();
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Update(course);
unitOfWork.Save();
// ...
var departmentsQuery = unitOfWork.DepartmentRepository.Get(
    orderBy: q => q.OrderBy(d => d.Name));
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Delete(id);
unitOfWork.Save();
// ...
unitOfWork.Dispose();

執行網站,然後按一下 [ 課程] 索引標籤

Courses_Index_page

頁面看起來和運作方式相同,如同變更之前所做的一樣,其他 Course 頁面也會運作相同。

總結

您現在已實作存放庫和工作單位模式。 您已使用 Lambda 運算式作為泛型存放庫中的方法參數。 如需如何將這些運算式與物件搭配 IQueryable 使用的詳細資訊,請參閱 MSDN Library 中的 IQueryable (T) 介面 (System.Linq) 。 在下一個教學課程中,您將瞭解如何處理一些進階案例。

您可以在 ASP.NET 資料存取內容對應中找到其他 Entity Framework 資源的連結。