2012年10月28日 星期日

ASP.NET MVC 專案分層架構 Part.2 抽出 Repository 裡相同的部份

2014-12-02 補充說明:
這一系列的文章並不適合初階及中階的開發人員,如果你是程式開發的初學者或是 ASP.NET MVC 初學者,甚至是開發經驗少於兩年的開發人員,請馬上離開此篇文章。

經過「ASP.NET MVC 專案分層架構 Part.1 初學者的起手式」這一篇之後,雖不能說對於分層架構有很詳細的理解,但至少對於分層架構有了初步的概念與想法,上一篇我們已經做到了把各個 Model 的資料存取抽離出來並另外建立個別的 Repository,而這也只是剛開始而已,上一篇文章有人反應篇幅過長,以致無法耗費太多時間將整篇文章看完,所以這一篇就不再長篇大論,只專注於一個主題上,接下來要說的是把個別 Repository 中相同的部份給抽離出來,讓我們關注於這一點吧!

 


我們來看前面那一篇在 CategoryRepository 與 ProductRepository 所分別繼承實作的介面內容,

ICategoryRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models.Interface
{
    public interface ICategoryRepository : IDisposable
    {
        void Create(Categories instance);
 
        void Update(Categories instance);
 
        void Delete(Categories instance);
 
        Categories Get(int categoryID);
 
        IQueryable<Categories> GetAll();
 
        void SaveChanges();
    }
}

IProductRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models.Interface
{
    public interface IProductRepository : IDisposable
    {
        void Create(Products instance);
 
        void Update(Products instance);
 
        void Delete(Products instance);
 
        Products Get(int productID);
 
        IQueryable<Products> GetAll();
 
        void SaveChanges();
 
    }
}

比較一下兩個介面的內容,可以看得出來兩個介面除了所使用的類別不同之外,其他的方法幾乎都是一模一樣的,因為我們這裡只有對 Category 與 Product 這兩個類別來寫資料操作的程式,所以在這樣的情境下有人會看不出來這樣一模一樣的介面內容有何不妥,而有些人一定馬上反應過來,有反應的人就會想到,假如專案中所使用的類別有很多的時候,豈不是每一個類別都要去建立內容幾乎一樣的介面嗎?

 

將共同的部份給抽離出來另外建立 IRepository 介面

像這樣基本的資料操作方法(C, R, U, D)除了操作的類別不同之外,其餘的幾乎是一樣的,真的不需要在每個類別的 Repository 介面都要去定義相同的方法,所以我們可以善用「泛型」的特性來處理這一部分,以下是幾篇 MSDN 中對於泛型的介紹,如果有對泛型不了解的朋友可以詳加閱讀。


IRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models.Interface
{
    public interface IRepository<TEntity> : IDisposable
        where TEntity : class
    {
        void Create(TEntity instance);
 
        void Update(TEntity instance);
 
        void Delete(TEntity instance);
 
        TEntity Get(int primaryID);
 
        IQueryable<TEntity> GetAll();
 
        void SaveChanges();
 
    }
}

接著就是修改 ICategoryRepository.cs 與 IProductRepository.cs 所實作的介面,我們這邊並不直接去修改 CategoryRepository 與 ProductRepository 所繼承的介面,而是讓這兩個 Repository 介面去繼承 IRepository,這麼做是有原因的,不過這在本篇的稍後將會提到,以修改 ICategoryRepository 為例來做說明,

image

1. 將原本的 ICategoryRepository.cs 去繼承 IRepository.cs

2. 因為在 IRepository 裡面都已經有定義了一樣的 methods,所以不需要繼續留著原有的方法,雖然建置方案是可以通過,但是在錯誤報告裡還是會告訴我們要做修改的警告,如下,

image

所以我們就移除原本在 ICategoryRepository 所定義的方法,重新建置方案後就不會出現警告了,而同樣的也對 IProductRepository 做一樣的修改,修改之後就如同以下內容:

ICategoryRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models.Interface
{
    public interface ICategoryRepository : IRepository<Categories>
    {
 
    }
}

IProductRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models.Interface
{
    public interface IProductRepository : IRepository<Products>
    {
 
    }
}

而這樣的修改對於 CategoryRepository 與 ProductRepository 的程式實作內容並沒有任何影響與改變,甚至於 Controllers 中對各類別的資料操作也不會有改變,最後程式的執行也就不會有任何問題,還是一樣正常地執行。

 

抽出同樣的操作內容

剛才我們增加了一個 IRepository 的介面,並且讓原本的 Repository 介面去繼承這個 IRepository,所以不需要在每個類別的 Repository 介面裡去定義相同的方法,再來就是觀察 CategoryRepository 與 ProductRepository 這兩個程式,

CategoryRepository.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using Mvc_Repository.Models.Interface;
 
namespace Mvc_Repository.Models
{
    public class CategoryRepository : ICategoryRepository
    {
        protected TestDBEntities db
        {
            get;
            private set;
        }
 
        public CategoryRepository()
        {
            this.db = new TestDBEntities();
        }
 
 
        public void Create(Categories instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                db.Categories.Add(instance);
                this.SaveChanges();
            }
        }
 
        public void Update(Categories instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                db.Entry(instance).State = EntityState.Modified;
                this.SaveChanges();
            }
        }
 
        public void Delete(Categories instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                db.Entry(instance).State = EntityState.Deleted;
                this.SaveChanges();
            }
        }
 
        public Categories Get(int categoryID)
        {
            return db.Categories.FirstOrDefault(x => x.CategoryID == categoryID);
        }
 
        public IQueryable<Categories> GetAll()
        {
            return db.Categories.OrderBy(x => x.CategoryID);
        }
 
 
        public void SaveChanges()
        {
            this.db.SaveChanges();
        }
 
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.db != null)
                {
                    this.db.Dispose();
                    this.db = null;
                }
            }
        }
 
    }
}

ProductRepository.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using Mvc_Repository.Models.Interface;
 
namespace Mvc_Repository.Models
{
    public class ProductRepository : IProductRepository
    {
        protected TestDBEntities db
        {
            get;
            private set;
        }
 
        public ProductRepository()
        {
            this.db = new TestDBEntities();
        }
 
        
        public void Create(Products instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                db.Products.Add(instance);
                this.SaveChanges();
            }
        }
 
        public void Update(Products instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                db.Entry(instance).State = EntityState.Modified;
                this.SaveChanges();
            }
        }
 
        public void Delete(Products instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                db.Entry(instance).State = EntityState.Deleted;
                this.SaveChanges();
            }
        }
 
 
        public Products Get(int productID)
        {
            return db.Products.FirstOrDefault(x => x.ProductID == productID);
        }
 
        public IQueryable<Products> GetAll()
        {
            return db.Products.Include(p => p.Categories).OrderByDescending(x => x.ProductID);
        }
 
 
        public void SaveChanges()
        {
            this.db.SaveChanges();
        }
 
 
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.db != null)
                {
                    this.db.Dispose();
                    this.db = null;
                }
            }
        }
    }
}

這兩個 Repository 只在 GetAll() 方法有不同的操作之外,其餘方法的操作都是如出一轍,我們不需要在每個類別的 Repository 程式中寫同樣的操作,所以我們可以建立一個通用的 Repository,讓這個通用 Repository 去繼承 IRepository 介面,並且實作介面所定義的方法內容,

GenericRepository.cs(尚未實作內容)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Mvc_Repository.Models.Interface;
 
namespace Mvc_Repository.Models.Repository
{
    public class GenericRepository<TEntity> : IRepository<TEntity>
        where TEntity : class
    {
 
        public void Create(TEntity instance)
        {
            throw new NotImplementedException();
        }
 
        public void Update(TEntity instance)
        {
            throw new NotImplementedException();
        }
 
        public void Delete(TEntity instance)
        {
            throw new NotImplementedException();
        }
 
        public TEntity Get(int primaryID)
        {
            throw new NotImplementedException();
        }
 
        public IQueryable<TEntity> GetAll()
        {
            throw new NotImplementedException();
        }
 
        public void SaveChanges()
        {
            throw new NotImplementedException();
        }
 
        public void Dispose()
        {
            throw new NotImplementedException();
        }
    }
}

上面的內容當然是不能執行,所以接著就是完成各個方法的實作內容,不過這邊會遇到一個問題,那就是 Get() 方法,這個方法在各類別的 Repository 的實作裡,我們會依據各類別主鍵的不同來實作 Get() 方法的內容,如下:

CategoryRepository 的 Get()

public Categories Get(int categoryID)
{
    return db.Categories.FirstOrDefault(x => x.CategoryID == categoryID);
}

ProductRepository 的 Get()

public Products Get(int productID)
{
    return db.Products.FirstOrDefault(x => x.ProductID == productID);
}
 

因為這兩個類別的主鍵不同,所以在 GenericRepository 裡就改採用 Expression<Func<TEntity, bool>> 的方法,以傳入 predicate 參數的方式來解決,這麼一來使用各類別的 Repository 的 Get() 方法時就傳入 predicate,如同這個方式:「Queryable.FirstOrDefault<TSource> 方法 (IQueryable<TSource>, Expression<Func<TSource, Boolean>>)」,最後我們的 IRepository 就修改為以下的內容,這麼一修改就連帶其他的程式就必須要跟著改變。

IRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
 
namespace Mvc_Repository.Models.Interface
{
    public interface IRepository<TEntity> : IDisposable
        where TEntity : class
    {
        void Create(TEntity instance);
 
        void Update(TEntity instance);
 
        void Delete(TEntity instance);
 
        TEntity Get(Expression<Func<TEntity, bool>> predicate);
 
        IQueryable<TEntity> GetAll();
 
        void SaveChanges();
 
    }
}

GenericRepository.cs(完整實作)

using System;
using System.Data;
using System.Data.Entity;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using Mvc_Repository.Models.Interface;
 
namespace Mvc_Repository.Models.Repository
{
    public class GenericRepository<TEntity> : IRepository<TEntity>
        where TEntity : class
    {
        private DbContext _context
        {
            get;
            set;
        }
 
        public GenericRepository()
            : this(new TestDBEntities())
        {
        }
 
        public GenericRepository(DbContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }            
            this._context = context;
        }
 
        public GenericRepository(ObjectContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            this._context = new DbContext(context, true);
        }
 
 
 
        /// <summary>
        /// Creates the specified instance.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <exception cref="System.ArgumentNullException">instance</exception>
        public void Create(TEntity instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                this._context.Set<TEntity>().Add(instance);
                this.SaveChanges();
            }
 
        }
 
        /// <summary>
        /// Updates the specified instance.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        public void Update(TEntity instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                this._context.Entry(instance).State = EntityState.Modified;
                this.SaveChanges();
            }
        }
 
        /// <summary>
        /// Deletes the specified instance.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        public void Delete(TEntity instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                this._context.Entry(instance).State = EntityState.Deleted;
                this.SaveChanges();
            }
        }
 
        /// <summary>
        /// Gets the specified predicate.
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <returns></returns>
        public TEntity Get(Expression<Func<TEntity, bool>> predicate)
        {
            return this._context.Set<TEntity>().FirstOrDefault(predicate);
        }
 
        /// <summary>
        /// Gets all.
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> GetAll()
        {
            return this._context.Set<TEntity>().AsQueryable();
        }
 
 
        public void SaveChanges()
        {
            this._context.SaveChanges();
        }
 
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this._context != null)
                {
                    this._context.Dispose();
                    this._context = null;
                }
            }
        }
 
    }
}

完成 GenericRepository 之後會發現到,我們已經在 GenericRepository 的實作中已經涵蓋了 CategoryRepository 與 ProductRepository 所有的方法,也就是說我們不再需要 CategoryRepository 與 ProductRepository,同樣也就不需要相對應的介面,所以就可以把 CatgoryRepository, ICatgoryRepository, ProductRepository, IProductRepository 都給移除,當然在移除上述的四個程式之後就勢必要對 Controller 來做程式內容的調整與修改。

image

 

修改 Controllers 內容

原本的 CategoryController 與 ProductController 都是以 ICategoryRepositorty 與 IProductRepository 來進行資料的操作,而如今已經把這兩個類別的 Repository 與介面都給刪除了,但這一次有新建立 IRepository 與 GenericRepository,所以在 Controllers 檔案中就改以使用 IRepository 與 GenericRepository 來做資料的存取,

image

image

 

修改的內容如下:

CategoryController.cs

using System.Data;
using System.Linq;
using System.Web.Mvc;
using Mvc_Repository.Models;
using Mvc_Repository.Models.Interface;
using Mvc_Repository.Models.Repository;
 
namespace Mvc_Repository.Controllers
{
    public class CategoryController : Controller
    {
        private IRepository<Categories> categoryRepository;
 
        public CategoryController()
        {
            this.categoryRepository = new GenericRepository<Categories>();
        }
 
 
        public ActionResult Index()
        {
            var categories = this.categoryRepository.GetAll()
                .OrderByDescending(x => x.CategoryID)
                .ToList();
 
            return View(categories);
        }
 
        //=========================================================================================
 
        public ActionResult Details(int? id)
        {
            if (!id.HasValue)
            {
                return RedirectToAction("index");
            }
            else
            {
                var category = this.categoryRepository.Get(x => x.CategoryID == id.Value);
                return View(category);
 
            }
        }
 
        //=========================================================================================
 
        public ActionResult Create()
        {
            return View();
        }
 
        [HttpPost]
        public ActionResult Create(Categories category)
        {
            if (category != null && ModelState.IsValid)
            {
                this.categoryRepository.Create(category);
                return RedirectToAction("index");
            }
            else
            {
                return View(category);
            }
        }
 
        //=========================================================================================
 
        public ActionResult Edit(int? id)
        {
            if (!id.HasValue)
            {
                return RedirectToAction("index");
            }
            else
            {
                var category = this.categoryRepository.Get(x => x.CategoryID == id.Value);
                return View(category);
            }
        }
 
        [HttpPost]
        public ActionResult Edit(Categories category)
        {
            if (category != null && ModelState.IsValid)
            {
                this.categoryRepository.Update(category);
                return View(category);
            }
            else
            {
                return RedirectToAction("index");
            }
        }
 
        //=========================================================================================
 
        public ActionResult Delete(int? id)
        {
            if (!id.HasValue)
            {
                return RedirectToAction("index");
            }
            else
            {
                var category = this.categoryRepository.Get(x => x.CategoryID == id.Value);
                return View(category);
            }
        }
 
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            try
            {
                var category = this.categoryRepository.Get(x => x.CategoryID == id);
                this.categoryRepository.Delete(category);
            }
            catch (DataException)
            {
                return RedirectToAction("Delete", new { id = id });
            }
            return RedirectToAction("index");
        }
 
    }
}


ProductController.cs

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Mvc_Repository.Models;
using Mvc_Repository.Models.Interface;
using Mvc_Repository.Models.Repository;
 
namespace Mvc_Repository.Controllers
{
    public class ProductController : Controller
    {
        private IRepository<Products> productRepository;
        private IRepository<Categories> categoryRepository;
 
        public IEnumerable<Categories> Categories
        {
            get
            {
                return categoryRepository.GetAll();
            }
        }
 
        public ProductController()
        {
            this.productRepository = new GenericRepository<Products>();
            this.categoryRepository = new GenericRepository<Categories>();
        }
 
        public ActionResult Index()
        {
            var products = productRepository.GetAll()
                .OrderByDescending(x => x.ProductID)
                .ToList();
 
            return View(products);
        }
 
        //=========================================================================================
 
        public ActionResult Details(int id = 0)
        {
            Products product = productRepository.Get(x => x.ProductID == id);
            if (product == null)
            {
                return HttpNotFound();
            }
            return View(product);
        }
 
        //=========================================================================================
 
        public ActionResult Create()
        {
            ViewBag.CategoryID = new SelectList(this.Categories, "CategoryID", "CategoryName");
            return View();
        }
 
        [HttpPost]
        public ActionResult Create(Products products)
        {
            if (ModelState.IsValid)
            {
                this.productRepository.Create(products);
                return RedirectToAction("Index");
            }
 
            ViewBag.CategoryID = new SelectList(this.Categories, "CategoryID", "CategoryName", products.CategoryID);
            return View(products);
        }
 
        //=========================================================================================
 
        public ActionResult Edit(int id = 0)
        {
            Products product = this.productRepository.Get(x => x.ProductID == id);
            if (product == null)
            {
                return HttpNotFound();
            }
            ViewBag.CategoryID = new SelectList(this.Categories, "CategoryID", "CategoryName", product.CategoryID);
            return View(product);
        }
 
        [HttpPost]
        public ActionResult Edit(Products products)
        {
            if (ModelState.IsValid)
            {
                this.productRepository.Update(products);
                return RedirectToAction("Index");
            }
            ViewBag.CategoryID = new SelectList(this.Categories, "CategoryID", "CategoryName", products.CategoryID);
            return View(products);
        }
 
        //=========================================================================================
 
        public ActionResult Delete(int id = 0)
        {
            Products product = this.productRepository.Get(x => x.ProductID == id);
            if (product == null)
            {
                return HttpNotFound();
            }
            return View(product);
        }
 
        [HttpPost, ActionName("Delete")]
        public ActionResult DeleteConfirmed(int id)
        {
            Products product = this.productRepository.Get(x => x.ProductID == id);
            this.productRepository.Delete(product);
            return RedirectToAction("Index");
        }
 
    }
}


在兩個 Controller 的程式裡,原本是要使用 ICategoryRepository 與 IProductRepository 與相對應的 Repository,但我們另外建立了 IRepository 與 GenericRepository 之後就不再需要用到之前所建立的 Repository ,將基本的資料操作給抽離出來並使用泛型,如此一來就不需要重複地去建立基本的資料操作。

 

但如果個別的類別資料存取方式是這些基本資料操作所沒有涵蓋的呢?

這就是我們下一篇要繼續說明的部份啦!所以敬請期待下一篇的內容。

 


系列文章下一篇:

ASP.NET MVC 專案分層架構 Part.3 - 個別 Repository 的資料存取操作

 

以上

32 則留言:

  1. 好感人啊,寫的真好,謝謝版主分享

    回覆刪除
  2. 寫的好棒,期待下一篇。

    回覆刪除
  3. 請教一下

    在GenericRepository程式碼中的建構子
    DbContext和ObjectContext有何不同??

    還有
    Expression>這句語法是什麼意思啊??

    回覆刪除
    回覆
    1. Expression(Func(TEntity, bool))

      刪除
    2. 新版本的 EF 是使用 DbContext,而前一版本的 EF 則是使用 ObjectContext,
      兩者不同,無法直接轉換。

      刪除
    3. MSDN - System.Linq.Expressions 命名空間
      http://msdn.microsoft.com/zh-tw/library/bb506649.aspx

      MSDN - Expression 類別
      http://msdn.microsoft.com/zh-tw/library/bb335710.aspx

      例如 repository.Get() 方法傳入的參數為 Expression,
      這樣我就可以使用 this.categoryRepository.Get(x => x.CategoryID == id.Value);
      在方法的參數中傳入 Linq 的表達式.

      刪除
    4. 很感謝您的回覆和提供的資料,
      對我真的有很大的幫助,

      再請教一下
      this._context.Entry(instance).State = EntityState.Modified;或
      this._context.Entry(instance).State = EntityState.Deleted;
      this.SaveChanges();

      為什麼改變instance的State,資料庫就知道要為這筆做更新或刪除??

      刪除
    5. 你所問的「為什麼改變instance的State,資料庫就知道要為這筆做更新或刪除」
      我在程式中改變 State,資料庫是不會知道的,
      而是將 DbContext 裡對應到我們要修改的那個 instance 其狀態更改為 Modified or Deleted,
      直到執行 SaveChanges() 方法後,DbContext 才會真的在資料庫裡去修改或刪除資料。

      你對 ADO.NET Entity Framework 應該不是很熟悉,
      我會建議你先對 ADO.NET Entity Framework 有初步的認識之後再回來看這一系列的文章會比較好,
      MSDN - ADO.NET Entity Framework
      http://msdn.microsoft.com/zh-tw/library/bb399572.aspx
      MSDN - 使用者入門 (Entity Framework)
      http://msdn.microsoft.com/zh-tw/library/bb386876(v=vs.100).aspx

      刪除
  4. 謝謝Kevin的文章教學,讓我又更多了一些認識與技巧。

    回覆刪除
  5. Kevin大,您好:
    內容中提到 DataRepository.cs(尚未實作內容),這部分 cs 名稱跟後續提到的 GenericRepository.cs(完整實作)有不同,不知道是否有誤?
    感謝 Kevin大 這系列的文章,非常受用。

    回覆刪除
    回覆
    1. 啊.哈哈哈... 這的確是筆誤,我都是先寫文章大綱之後再寫出文章內容與貼程式,這明顯就是兩者不相符,因為早先時候我是習慣使用 DataRepository,而後才改用 GenericRepository,感謝你的告知,馬上做修正。

      刪除
    2. Kevin大,太拚了...這麼晚還回覆 XD

      刪除
    3. 還沒三點... 還早還早,對於寫程式的人來說,深夜是黃金時段呀.

      刪除
  6. 程式跑到 this._context.Entry(instance).State = EntityState.Modified;

    會出現 An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. 錯誤

    回覆刪除
    回覆
    1. 關鍵字很好 Google 到相關訊息的,另外我有寫一篇相關的文章
      「Entity Framework 更新時出現「ObjectStateManager 中已經有具有相同索引鍵的物件。ObjectStateManager 無法追蹤多個具有相同索引鍵的物件。」錯誤」
      http://kevintsengtw.blogspot.tw/2013/05/objectstatemanager-objectstatemanager.html

      刪除
  7. Kevin大, 您好:
    若在一個Action裡需要同時對2個Table以上做處理, 並且要在同一個Transaction內.
    這樣的需求是否也可以使用Repository Pattern的方式處理呢?

    回覆刪除
    回覆
    1. 其實面對這樣的處理,我會建議採用 Unit of Work 的方式來處理,而這也是我以後(不知道哪時候)會講到的內容,可以參考 ASP.NET MVC 官方教學課程的其中一個章節介紹:
      Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)
      http://goo.gl/vJbvq

      其實 ADO.NET Entity Framework 已經有隱含的 Transaction 機制,多個資料處理在進行 SaveChanges 時都可算在同一個 Transaction,在這一系列文章的第六篇「ASP.NET MVC 專案分層架構 Part.6 - DI/IoC 使用 Unity.MVC」就已經有把 DbContext 的實例化從 Repository 與 Service 給分離出來,然後在每次的 Request 進來時建立 DbContext instance,以確保每次 Request 裡的資料操作是同一個 DbContext.

      刪除
  8. Kevin 大您好,您的資源真的非常多

    看到實在非常感動 好多東西都學不完

    回覆刪除
  9. 感謝Kevin用心的撰寫教學文章~
    最近剛好要撰寫資料庫的程式,步驟和解釋非常詳細~ :)
    (差一點就在Part 1問這篇要談到的東西~XDDD)

    回覆刪除
    回覆
    1. 感謝你的回應,
      這系列的每一篇都可以是一篇獨立的章節,可以先做到每一篇的階段,等到熟悉了某個階段的做法之後,再往下看並且往下改,千萬不要一下子就想要在第一個嘗試做分層的專案就想把整個系列的文章看完然後都用在專案裡,千萬不要這樣,一個階段一個階段慢慢來。

      刪除
    2. 「千萬不要一下子就想要在第一個嘗試做分層的專案就想把整個系列的文章看完然後都用在專案裡」 當頭棒喝~XD
      已經把系列文章看完,差點要這樣做了...(汗)
      我先操練前面的部分,系統完成後,再慢慢用後面新章節的概念重構~ :)

      刪除
    3. 請問Kevin:
      GenericRepository.cs(完整實作)中的Update和Delete,Comment部分寫的是否要統一寫成"System.ArgumentNullException" ?

      刪除
    4. 這邊是分開,因為是簡單的實作,所以看起來好像應該要把重複的區塊給抽離出來然後去共用,但是如果往後的結構越來越複雜,那麼各種的操作都會需要個別去做處理時,就會遇到麻煩,至於這邊是否分開還是統一,沒有任何限定或是建議,就看要如何去管理這部分的異常處理。

      刪除
  10. 請問一下,EF版本是使用5,已經有using System.Data.Entity;但還是發生錯誤:
    "找不到類型或命名空間名稱 'DbContext' (您是否遺漏 using 指示詞或組件參考?)"

    這個錯誤該如何解決?

    回覆刪除
    回覆
    1. 缺少組件就去找來裝
      Entity Framework 5 是一定有 DbContext 類別的
      所以請重新檢視或專案重新安裝 Entity Framework 5 套件
      https://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext(v=vs.103).aspx
      https://msdn.microsoft.com/en-us/library/system.data.entity(v=vs.103).aspx

      刪除
    2. 抱歉,沒有描述清楚,組件確實有安裝,但還是發生該錯誤才感到困惑,

      我重新安裝看看好了~謝謝回覆

      刪除
    3. 補充,重新安裝組件後正常。

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。