2012年11月8日 星期四

EF with Repository Pattern - 簡易版

情境:
上一篇中我們有使用到一個NorthwindReposity類別來存取資料庫,不過我們也發現到這樣的寫法與呼叫端的耦合度還是有點高。若我要使用的資料所要做的事情是固定的,像是瀏覽產品資訊時的產品資料是從主資料庫中找到產品的資料表並取出指定產品的資料,那麼其實對這個部分來說其實他就只關心產品得資料如何存取和操作而已。一個簡單的想法是,我們讓呼叫端用一個介面來針對他所感興趣的部份去做操作。

實作:
我們用的資料庫依然是北風資料庫,而且和上一篇是一樣的!避免頁面切來切去這邊就直接寫囉~
using System.Data.Entity;
System.ComponentModel.DataAnnotations.Schema;
public class Northwind : DbContext
{
    public DbSet<product> Products { get; set; }

    public DbSet<category> Categories { get; set; }
}
public class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public Decimal? UnitPrice { get; set; }
    public bool Discontinued { get; set; }
    [ForeignKey("Category")]
    public int CategoryID { get; set; }

    public virtual Category Category { get; set; }
}
public class Category
{
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
    public string Description { get; set; }
    public byte[] Picture { get; set; }

    public virtual ICollection<product> Products { get; set; }
}
接下來就是我們上一篇用來存取資料庫的主要類別,也是我們這次的修改標的物。
public class NorthwindRepository : IDisposable
    {
        private Northwind _db;
        
        public NorthwindRepository()
        {
            _db = new Northwind();
        }

        public ICollection<string> ListAllProductName()
        {
            return _db.Products.Select(a => a.ProductName).ToList();
        }

        public ICollection<string> ListAllCategoryName()
        {
            return _db.Categories.Select(a => a.CategoryName).ToList();
        }

        public void Dispose()
        {
            if (_db != null)
                _db.Dispose();
        }
    }
依照先前所說,我們先來做一個讓呼叫端使用的介面吧~我們這邊是以Product資料為例子
public interface IProductRepository
{
     IList<Product> GetProducts();
     Product GetProductByID(int productId);
     Product GetProductByName(string productName);
     void InsertProduct(Product product);
     void DeleteProduct(int productId);
     void UpdateProduct(Product porduct);
}
接著我們要寫一個實作這個介面的類別,並且讓他實際去取出資料。
public class ProductReposity : IRepository
{
     private Northwind _ db = null;
     public ProductReposity(Northwind context)
     {
          if(context == null) throw new ArgumentNullException("context");
          this._db = context;
     }
     public IList<Product> GetProducts()
     {
          return _db.Products.ToList();
     }
     public Product GetProductByID(int productId)
     {
          return _db.Products.Where(a => a.ProductID == productId).Select(a => a).FirstOrDefault();
     }
     public Product GetProductByName(string productName)
     {
          return _db.Products.Where(a => a.ProductName == productName).Select(a => a).FirstOrDefault();
     }
     public void InsertProduct(Product product)
     {
          _db.Products.Add(product);
          _db.SaveChanges();
     }
     public void DeleteProduct(int productId)
     {
          Product product = _db.Products.Find(productId);
          _db.Products.Remove(product);
          _db.SaveChanges();
     }
     public void UpdateProduct(Product product)
     {
          Product target = _db.Products.First(a => a.ProductID == product.ProductID);
          target = product;
          _db.SaveChanges();
     }
以上~在呼叫端時我們就可以這樣使用:
IProductRepository db = new ProductRepository(new Northwind());
var items = db.GetProducts();
foerach(var p in items)
{
     Console.WriteLine("Product Name {0}", p.ProductName);
}
這樣我們就進一步的把呼叫端跟資料庫分離開一點了,不過寫到這邊就打住的話實在會很虛...那我們就把這個情境稍微擴大,變成「有多個資料的操作都是類似的」。依照這樣的寫法我們需要新增一個IRepository類別和與之相對應的Repository類別,既然操作都是類似的能不能介面只寫一份就好了呢?可以的!那我們就來改改吧~
public interface IRepository<T> : IDisposable
{
     IList<T> GetProducts();
     T GetProductByID(int itemId);
     T GetProductByName(string itemName);
     void InsertProduct(T item);
     void DeleteProduct(int itemId);
     void UpdateProduct(T item);
}
而把ProductRepository改成如下:
public class ProductReposity : IRepository<Product>
{
     // 判定是否已經呼叫Dispos()方法。
     private bool disposed = false;

     private Northwind _ db = null;
     public ProductReposity(Northwind context)
     {
          if(context == null) throw new ArgumentNullException("context");
          this._db = context;
     }
     public IList<Product> GetProducts()
     {
          return _db.Products.ToList();
     }
     public Product GetProductByID(int productId)
     {
          return _db.Products.Where(a => a.ProductID == productId).Select(a => a).FirstOrDefault();
     }
     public Product GetProductByName(string productName)
     {
          return _db.Products.Where(a => a.ProductName == productName).Select(a => a).FirstOrDefault();
     }
     public void InsertProduct(Product product)
     {
          _db.Products.Add(product);
          _db.SaveChanges();
     }
     public void DeleteProduct(int productId)
     {
          Product product = _db.Products.Find(productId);
          _db.Products.Remove(product);
          _db.SaveChanges();
     }
     public void UpdateProduct(Product product)
     {
          Product target = _db.Products.First(a => a.ProductID == product.ProductID);
          target = product;
          _db.SaveChanges();
     }
     // 用戶端主動呼叫表明要釋放資源的函式。
     public void Dispose()
     {
         if (_db != null)
             this.Dispose(true);
         //通知GC不用呼叫 finalize 來釋放資源。
         GC.SuppressFinalize(this);
     }
     /// <remarks>
     /// 無法被外部呼叫。
     /// 若是true表示是被用戶端呼叫,manage resource和unmanage resource都可以釋放。
     /// 若是false表示是被GC呼叫,此時應該只釋放unmanage resource。
     /// </remarks>
     protected virtual void Dispose(bool disposing)
     {
         if (!disposed)
         {
             if (disposing)
             {
                 _db.Dispose();
             }
             disposed = true;
         }
     }

     /// <summary>
     /// finalizer。這個無法自行呼叫是由GC來使用的。
     /// </summary>
     ~ProductReposity()
     {
         //以false告知Dispose函數是從垃圾回收器(GC)在呼叫Finalize。
         Dispose(false);
     }
}
ps(這邊的Dispose()寫法的好處是,若呼叫端忘記呼叫Dispose()方法還有Finalize讓GC可以幫忙把物件使用的manage/unmanage resource回收,若呼叫端呼叫Dispose()方法時可以即時把資源進行回收動作,而且不用GC又呼叫一次可以比較有效率點。)
好哩~這樣新的IRepository介面就套用成功了!其實沒什麼變嘛~不過...這樣的話之後若對資料操作的邏輯是相似的話用同一個介面就好了,不需要產生那麼多的介面,也算是省了那麼一點工吧(?) XD

沒有留言:

張貼留言