MyORM Framework 的 C# Project Templates 已經上市集

警語:

在使用 MyORMWizardExtensions 請斟酌使用,這是在團隊有共識的情況下、已有共同規範下減輕重複性工作使用 (這個重複性工作在於你的團隊已經有這些重複性工作的 Skill),所以在使用前,請先參考小編先前撰寫、也有 PO 在軟體開發之路的文章「導入團隊 Project Templates 樣版設計 - (首部曲)」。

另外,如果您初學者,不代表您直接拿來使用,而可以不去了解它到底幫你產生什麼樣的程式碼,也就是說,對於你個人能力的提升,還是要能夠自行撰寫出該程式碼,甚至建立你們團隊的規範、程式碼的範本精靈,而不是只是使用別人建立的範本精靈。

先前筆者的課程「架構設計好簡單系列 - 如何設計符合團隊的範本精靈 (Project Template)」的重點 7. 一致性的團隊的開發規範 - Coding Standard (Programming Rule),也是教您如何建立你自己團隊的規範,只是透過範本精靈來簡化工作。

為避免有人誤解,所以在此聲明。

 

前言

先前筆者在 Visual Studio Everywhere 台北場分享的「團隊開發永遠的痛-談導入團隊開發的共同規範」課程,與先前所開立課程「[第二梯][台北 5/28 (星期六)] 架構設計好簡單系列 - 如何設計符合團隊的範本精靈 (Project Template)」與的內容中所使用的 C# Project Templates 樣版,本文將介紹筆者自行開發的 Project Templates (以下簡稱為樣版),這個樣版現在已經被我包裝為Visual Studio 的擴充套件,也發佈到 Visual Studio Gallery 市集上,您可以在 Visual Studio Gallery 上下載、並安裝這個套件。

也可以直接從 Visual Studio 的擴充套件管理員下載安裝。

 

MyORMWizardExtensions 擴充套件說明

這個擴充套件目前發佈的版本只支援 Visual Studio 2017 ,下方筆者說明這個套件的功能、用途、以及使用方式。

在安裝了 MyORMWizardExtensions 擴充套件之後,您可以在你的 Visual Studio 2017 新增專案的是窗看到「MyORM Framework」的節點資料夾,裡面會有五個專案樣版可以選擇,如下:

 

下面筆者一一介紹這個擴充套件中的 5 個樣版分別可以為我們產生什麼樣的程式碼。

1). MyORM2UnitOfWorkRepositoryProject

這個樣版是屬於 Data Access Layer 類型的樣版,它是從 第 (5)個樣版「MyORMUnitOfWorkRepositoryLib」改進而來 (等一下我會介紹MyORMUnitOfWorkRepositoryLib樣版,再來比較他們的不同之處),它會幫我們產生一個以 Code-First 為基礎的 Model 類型的樣板。

點開「MyORM2UnitOfWorkRepositoryProject」這個樣版後,會出現一個精靈的對話框如下:

這時候,您可能需要有資料庫連線,以便 「MyORMWizardExtensions」可以透過資料庫 Tables 欄位、Metadata 等相關資訊來產生 DbContext、Repository、Entities 等相關程式碼,該資料庫連線可以支援 MS SQL Server、SQL Express、Local DB、等來源。

這邊我使用「Northwind」資料庫來 Demo 的這個樣版。

如果連線 OK,會帶出這個資料庫裡所有的 Tables,畫面如下:

這時候我們可以勾選目前要開發的 Data Access Layer 模組會使用到哪些 Tables,接著點選確定 (這邊筆者先全部勾選),接著產生的專案項目程式碼如下:

很多對不對!因為我們剛剛全選的關係,不過,沒有有關係,我們來看幾個重點的部分,前面我們提到過,這是一個包含 UnitOfWork 與 Repository 的樣版,而且支援交易,產生的程式碼中,我已經用資料夾(Namespace)加以區別了 Infrastrature、Models、Repository,這三個。

我們先看 Infrastrature 這個資料夾,這資料夾裡有:IUnitOfWork.cs、NotBeginTranException.cs、UnitOfWork.cs 三個檔案

Infrastrature 資料夾

IUnitOfWork.cs

這是 UnitOfWork 的介面定義,目前只有定義 BeginTransaction 與 Commit 這兩個方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NorthwindUnitOfWork2Models
{
    public interface IUnitOfWork
    {
        void StartTransaction();
        void Commit();
    }
}

 

UnitOfWork.cs

這就是前面 IUnitOfWork 介面的實作,我們來看看精靈幫我們產生了什麼

using NorthwindUnitOfWork2Models.Models;
using NorthwindUnitOfWork2Models.Repository;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindUnitOfWork2Models.Infrastructure
{
    public class UnitOfWork: IUnitOfWork, IDisposable
    {
        private Northwindmodel _context = null;
        private DbContextTransaction _transaction = null;

        
        private CategoriesRepository _categoriesRepository;
		public CategoriesRepository CategoriesRepository
		{
			get
			{
				return _categoriesRepository ?? (_categoriesRepository = new CategoriesRepository(_context));
			}
		}

        private CustomercustomerdemoRepository _customercustomerdemoRepository;
		public CustomercustomerdemoRepository CustomercustomerdemoRepository
		{
			get
			{
				return _customercustomerdemoRepository ?? (_customercustomerdemoRepository = new CustomercustomerdemoRepository(_context));
			}
		}

        private CustomerdemographicsRepository _customerdemographicsRepository;
		public CustomerdemographicsRepository CustomerdemographicsRepository
		{
			get
			{
				return _customerdemographicsRepository ?? (_customerdemographicsRepository = new CustomerdemographicsRepository(_context));
			}
		}

        private CustomersRepository _customersRepository;
		public CustomersRepository CustomersRepository
		{
			get
			{
				return _customersRepository ?? (_customersRepository = new CustomersRepository(_context));
			}
		}

        private EmployeesRepository _employeesRepository;
		public EmployeesRepository EmployeesRepository
		{
			get
			{
				return _employeesRepository ?? (_employeesRepository = new EmployeesRepository(_context));
			}
		}

        private EmployeeterritoriesRepository _employeeterritoriesRepository;
		public EmployeeterritoriesRepository EmployeeterritoriesRepository
		{
			get
			{
				return _employeeterritoriesRepository ?? (_employeeterritoriesRepository = new EmployeeterritoriesRepository(_context));
			}
		}

        private Order_detailsRepository _order_detailsRepository;
		public Order_detailsRepository Order_detailsRepository
		{
			get
			{
				return _order_detailsRepository ?? (_order_detailsRepository = new Order_detailsRepository(_context));
			}
		}

        private OrdersRepository _ordersRepository;
		public OrdersRepository OrdersRepository
		{
			get
			{
				return _ordersRepository ?? (_ordersRepository = new OrdersRepository(_context));
			}
		}

        private ProductsRepository _productsRepository;
		public ProductsRepository ProductsRepository
		{
			get
			{
				return _productsRepository ?? (_productsRepository = new ProductsRepository(_context));
			}
		}

        private RegionRepository _regionRepository;
		public RegionRepository RegionRepository
		{
			get
			{
				return _regionRepository ?? (_regionRepository = new RegionRepository(_context));
			}
		}

        private ShippersRepository _shippersRepository;
		public ShippersRepository ShippersRepository
		{
			get
			{
				return _shippersRepository ?? (_shippersRepository = new ShippersRepository(_context));
			}
		}

        private SuppliersRepository _suppliersRepository;
		public SuppliersRepository SuppliersRepository
		{
			get
			{
				return _suppliersRepository ?? (_suppliersRepository = new SuppliersRepository(_context));
			}
		}

        private TerritoriesRepository _territoriesRepository;
		public TerritoriesRepository TerritoriesRepository
		{
			get
			{
				return _territoriesRepository ?? (_territoriesRepository = new TerritoriesRepository(_context));
			}
		}


        public UnitOfWork()
        {
            _context = new Northwindmodel();
        }

        /// <summary>
        /// 開始交易
        /// </summary>
        public void StartTransaction()
        {
            _transaction = _context.Database.BeginTransaction();
        }
    
        /// <summary>
        /// 確認交易
        /// </summary>
        public void Commit()
        {
            if (_transaction == null)
                throw new NotBeginTranException("必須先呼叫 StartTransaction 方法。");

            
            _context.SaveChanges();
            _transaction.Commit();
        }

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

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_context != null)
                {
                    _context.Dispose();
                    _context = null;
                }
            }
        }

    }
}

讀者應該發現程式碼很常對不對?但事實上,除了主要控制交易的部分外,其他是為了含括到交易內容實作的 Repository 而已,程式碼看起來常只是因為我們剛才 Tables 是全選的關係。等等筆者挑一個 Repository 的實作給各位讀者看看,我想大家就會明白了。

NotBeginTranException.cs

這是提供交易相關的 Exception 例外的實作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindUnitOfWork2Models.Infrastructure
{
    /// <summary>
    /// 交易失敗相關 Exception.
    /// </summary>
    public class NotBeginTranException : Exception
    {
        public NotBeginTranException(string Message) : base(Message) { }
    }
}

 

Models 資料夾

這個資料夾裡面顧名思義都是 Entities,筆者就不全部列出了,挑一個「Categories.cs」給讀者參考:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace NorthwindUnitOfWork2Models.Models
{
	public class Categories
	{
		[Key]
		[Column(Order = 0)]
		public int CategoryID {get; set;}


		[StringLength(50)]
		public string CategoryName {get; set;}


		[StringLength(50)]
		public string Description {get; set;}


		public byte[] Picture {get; set;}
	}
}


喔!對了,Models 資料夾還包含 DbContext,前面筆者有提到「MyORM2UnitOfWorkRepositoryProject」使用的是 Entity Framework 的 Code-First,所以來看看 MyORMWizardExtensions 產生的 DbContext 定義長什麼樣。

namespace NorthwindUnitOfWork2Models.Models
{
    using System;
    using System.Data.Entity;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;

    public partial class Northwindmodel : DbContext
    {
        public Northwindmodel()
            : base("name=Northwindmodels")
        {
        }
        		public virtual DbSet<Categories> Categories { get; set; }
		public virtual DbSet<Customercustomerdemo> Customercustomerdemo { get; set; }
		public virtual DbSet<Customerdemographics> Customerdemographics { get; set; }
		public virtual DbSet<Customers> Customers { get; set; }
		public virtual DbSet<Employees> Employees { get; set; }
		public virtual DbSet<Employeeterritories> Employeeterritories { get; set; }
		public virtual DbSet<Order_details> Order_details { get; set; }
		public virtual DbSet<Orders> Orders { get; set; }
		public virtual DbSet<Products> Products { get; set; }
		public virtual DbSet<Region> Region { get; set; }
		public virtual DbSet<Shippers> Shippers { get; set; }
		public virtual DbSet<Suppliers> Suppliers { get; set; }
		public virtual DbSet<Territories> Territories { get; set; }


        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        }
    }
}

其實就跟你加入 ADO.NET 實體資料模型沒什麼不同。

 

Repository 資料夾

然後是 Repository 樣式的部分了,前面我們看到在 UnitOfWork 裡面包了許多的 Repository,我們現在就挑一個來看,看看這個 Repository 裡面到底有什麼東西?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NorthwindUnitOfWork2Models.Models;

namespace NorthwindUnitOfWork2Models.Repository
{
    public class CategoriesRepository: GenericRepository<Categories>
    {
        public CategoriesRepository(Northwindmodel context)
            : base(context)
        {

        }
    }
}


如何?很減單吧?它也是傳入 Northwindmodel 的 DbContext,然後,發現主要實作其實是使用 GenericRepositor!是的!沒有錯,這裡精靈使用 Generic 的 Repository 來實作 CRUD,我們來看一下程式碼。

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NorthwindUnitOfWork2Models.Models;

namespace NorthwindUnitOfWork2Models.Repository
{
    /// <summary>
    /// 泛型的 Repository (提供各Repository繼承使用)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class GenericRepository<T> 
        where T : class
    {
        private Northwindmodel _context;


        private DbSet<T> table = null;

        public GenericRepository()
        {

        }

        public GenericRepository(Northwindmodel context)
        {
            this._context = context;
            table = _context.Set<T>();
        }

        public IEnumerable<T> SelectAll()
        {
            return table.ToList();
        }

        public T SelvectById(object id)
        {
            return table.Find(id);
        }

        public void Insert(T obj)
        {
            table.Add(obj);
        }

        public void Update(T obj)
        {
            table.Attach(obj);
            _context.Entry(obj).State = EntityState.Modified;
        }

        public void Delete(object id)
        {
            T existing = table.Find(id);
            table.Remove(existing);
        }
    }
}

程式碼其實不難,對吧?GenericRepository 會讓每一個 Repository 實作,並傳入 DbContext 實作,然後 Northwindcontext 也是 GenericRepository 的私有變數,最後為了確保交易的一致性,每一個 Repository 的實作會定義在 UnitOfWork 中。

 

2). MyORMEntitiesClassLib

這個樣版就單純許多,他純粹是一個 Entities 專案,只是他使用的是筆者前一版的精靈畫面:

產生的程式碼如下:

眼尖的讀者應該會發現這個樣版等於產生的只有「MyORM2UnitOfWorkRepositoryProject」的「Models」的部分而已。

 

3). MyORMRepositoryModelLib

再來是這個「MyORMRepositoryModelLib」樣版,這個樣版也是 DAL 框架,而這個是更早期的版本了,應該算是比「MyORMUnitOfWorkRepositoryLib」還要更早一代的 DAL 框架,只不過他是使用筆者的 NuGet 套件「Gelis Framework」裡的 MSSQLObject + SqlGenerator 所組合而成的。

所謂的「Gelis Framework」就是先前筆者在「軟體開發之路」的FB社團發佈的 NuGet Package

這樣版的資料存取部分是怎麼做的?我們現在就來一探究竟吧!當然,相同的我們使用 Northwind 資料庫來建立。

建立好的專案如下:

如上圖,他會幫我切好 BLL、DAL、Entities、Services 等,基本的大致上都有了,只是都在同一個專案裡面,當然,您也可以將它拆解開來。

我們從外部「Services」服務層開始往裡面看好了,我想這樣比較容易理解,而且我只挑一個 Northwind 裡面的 Table「Categories」來看。

首先,我們把 Services 層下面的「CategoriesServices.cs」檔案點開來,如下:

using MyORMRepositoryModelLib1.BLL;
using MyORMRepositoryModelLib1.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyORMRepositoryModelLib1.Services
{
    public class CategoriesService
    {
        private CategoriesRepository _CategoriesRepository = null;

        public CategoriesService(CategoriesRepository CategoriesRepository)
        {
            if (CategoriesRepository == null)
                throw new ArgumentNullException(string.Format("參數名稱 {0} 不可以是 null.", "CategoriesRepository"));

            _CategoriesRepository = CategoriesRepository;
        }

        public IEnumerable<Categories> GetAllCategoriess()
        {
            return _CategoriesRepository.GetAll();
        }

        public int GetAllCategoriesCount()
        {
            return GetAllCategoriess().Count();
        }
    }
}

如上程式碼中,我們可以輕易看出來這個 Service 只是簡單的傳入 CategoriesRepository 而已,而且每個 Repository 都會實作 GetAll() 方法。

OK,那我們就直接來看 「CategoriesRepository」的實作部分。如下:

using MyORMRepositoryModelLib1.BLL;
using MyORMRepositoryModelLib1.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyORMRepositoryModelLib1.BLL
{
    /// <summary>
    /// 
    /// </summary>
    public class CategoriesRepository
    {
        private readonly ICategoriesRepositoryProvider _CategoriesRepositoryProvider = null;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="$custommessage$RepositoryProvider"></param>
        public CategoriesRepository(ICategoriesRepositoryProvider CategoriesRepositoryProvider)
        {
            if (CategoriesRepositoryProvider == null)
                throw new ArgumentNullException(string.Format("參數名稱 {0} 不可以是 null.", "CategoriesRepositoryProvider"));

            _CategoriesRepositoryProvider = CategoriesRepositoryProvider;
        }

        public IEnumerable<Categories> GetAll()
        {
            return _CategoriesRepositoryProvider.GetAll();
        }
    }
}

如上在「CategoriesRepository」的程式碼中,相信讀者會發現整個程式碼的 Style 都相同,這也是我一直在講的一致性的共同規範,只是一開始都只會有 GetAll() 方法,您可以根據實際的客戶需求而增加實際需要的方法出來。

且上方的程式碼已經屬於 BLL 層級,也就是說,您可以在此處撰寫一些商業邏輯,不過當然,這不完全走 Repository,重點比較在於一致性,各層的程式碼各司其職,沒使用到什麼太複雜的 UnitOfWork,比較適合讓 Junior 的工程師配合執行,並從實作中學習,因為雖然沒使用到任何一種 IoC Framework,但概念上告訴工程師使用外部注入 ICategoriesRepositoryProvider 介面的概念,讓 BLL 操作 DAL 層。

好,再讓我們往下一層來看「ICategoriesRepositoryProvider 」這個介面裡面有什麼東西?

using MyORMRepositoryModelLib1.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyORMRepositoryModelLib1.BLL
{
    public interface ICategoriesRepositoryProvider: IRepository<Categories>
    {
    }
}

疑,您會發現這個介面空無一物?是的!預設他只會實作 IRepository<Categories>,這邊是讓系統設計師針對需求開立需要的方法介面,然後讓 PG 撰寫程式。

繼承的  IRepository<Categories> 已經幫你定義了基本的 CRUD,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyORMRepositoryModelLib1.BLL
{
    public interface IRepository<T>
    {
        /// <summary>
        /// 新增一筆資料
        /// </summary>
        /// <param name="entity"></param>
        int Add(T entity);
        /// <summary>
        /// 刪除一筆資料
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        int Del(T entity);
        /// <summary>
        /// 編輯一筆資料
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        int Edit(T entity);
        /// <summary>
        /// 取得所有資料
        /// </summary>
        /// <returns></returns>
        IQueryable<T> GetAll();
    }
}


對了,我們還有 DAL 還沒看,要看這個 DAL 的實作部分的話,那就要來看實作「ICategoriesRepositoryProvider」這個介面的的人是誰了!它就是「SqlCategoriesRepositoryProvider」

using GelisDAL = GelisFrameworks.Data.DAL;
using GelisFrameworks.Data.Sql.SqlHelper;
using MyORMRepositoryModelLib1.BLL;
using MyORMRepositoryModelLib1.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyORMRepositoryModelLib1.DAL
{
    /// <summary>
    /// 資料存取物件 Categories
    /// </summary>
    public class SqlCategoriesRepositoryProvider : ICategoriesRepositoryProvider
    {
        public int Add(Categories entity)
        {
            GelisDAL.MSSQLObject obj = new GelisDAL.MSSQLObject(new GelisDAL.DataAccess());
            obj.SqlStatement = new SqlGenerator().GetInsert(typeof(Categories), new string[] { "CategoryID" }, "Categories");
            return obj.UpdateData<Categories>(entity);

        }

        public int Del(Categories entity)
        {
            GelisDAL.MSSQLObject obj = new GelisDAL.MSSQLObject(new GelisDAL.DataAccess());
            Categories delObj = new Categories() { CategoryID = entity.CategoryID };
            obj.SqlStatement = new SqlGenerator().GetDelete<Categories>(
                delObj,
                new string[] { "CategoryID" },
                "Categories");
            return obj.UpdateData<Categories>(delObj);

        }

        public int Edit(Categories entity)
        {
            GelisDAL.MSSQLObject obj = new GelisDAL.MSSQLObject(new GelisDAL.DataAccess());
            obj.SqlStatement = new SqlGenerator().GetUpdate(typeof(Categories), new string[] { "CategoryID" }, "Categories");
            return obj.UpdateData<Categories>(entity);
        }

        public IQueryable<Categories> GetAll()
        {
            GelisDAL.MSSQLObject obj = new GelisDAL.MSSQLObject(new GelisDAL.DataAccess());
            obj.SqlStatement = new SqlGenerator().GetSelect(typeof(Categories), "Categories");
            return obj.GetAll<Categories>().AsQueryable();

        }
    }
}

這段程式碼就是使用了「Gelis Framework」來實作基本的對單一資料表的 CRUD,關鍵點在 SqlGenerator,他會幫你產生 Insert/Delete/Update 等基本 SQL Statement,若使用不同種類型的資料庫只需要更換 MSSQLObject 資料庫物件即可。

如上就是「MyORMRepositoryModelLib」樣版的基本架構。

至於 Entities 資料夾與前面專案都相同,就不在贅述了。

 

4). MyORMServiceClassLib

這個樣版純粹是筆者當時的課程「[最後一梯][台北 05/21 (星期六)] 如何有架構性將現有 ASP.NET Web Form 轉換為 MVC?」規劃需要,而設計了這個樣版,這個樣版可以搭配上一個「3). MyORMRepositoryModelLib」樣版,取代掉 Services 層,並獨立成一個專案。

 

5). MyORMUnitOfWorkRepositoryLib

前面筆者有提到這個樣版是第 1 個「MyORM2UnitOfWorkRepositoryProject」樣版的前身,它也是筆者為了課程「[最後一梯][台北 05/21 (星期六)] 如何有架構性將現有 ASP.NET Web Form 轉換為 MVC?」所設計出來的樣版,它與「MyORM2UnitOfWorkRepositoryProject」相同使用 UnitOfWork,但是它更簡單,它不支援交易,而最大的差異是,它不包含任何的 DAL 底層實作,他是讓你橋接現有的 DAL 層而使用的。

我們來看看它要怎麼橋接?首先,一樣建立這個樣版,不過這一次為了避免複雜化,筆者只建立「Categories」這個 Table,專案建立完成如下:

如上的專案結構看似複雜,但事實上並不複雜,有學員曾經問過為什麼這麼切割?讓我娓娓道來。

 

DbContextFactory 資料夾

橋接現有 DAL 使用,它的介面「IDbContextFactory.cs」定義如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindModelsOriginal.DbContextFactory
{
    /// <summary>
    /// 
    /// </summary>
    public interface IDbContextFactory
    {
        //請將下面的 MyDALClass 類別 置換成您實際的 DAL 類別
        MyDALClass GetDbContext();
    }
}

用已取得現有DAL執行個體而已,如上程式碼中的「MyDALClass」是一個空的、不存在任何實作的類別,用以讓您取代掉你現有的 DAL 類別。

接著來看實作「IDbContextFactory」介面的「DbContextFactory」類別的程式碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindModelsOriginal.DbContextFactory
{
    /// <summary>
    /// 
    /// </summary>
    public class DbContextFactory : IDbContextFactory
    {
        private string _ConnectionString = string.Empty;
        public DbContextFactory(string connectionString)
        {
            this._ConnectionString = connectionString;
        }
        //請將 MyDALClass 類別 置換成您實際的 DAL 類別
        private MyDALClass _dbContext;
        //請將 MyDALClass 類別 置換成您實際的 DAL 類別
        private MyDALClass dbContext
        {
            get
            {
                if (this._dbContext == null)
                {
                    Type t = typeof(MyDALClass);
                    this._dbContext = (MyDALClass)Activator.CreateInstance(t);
                }
                return _dbContext;
            }
        }
        /// <summary>
        /// 請將 MyDALClass 類別 置換成您實際的 DAL 類別
        /// </summary>
        /// <returns></returns>
        public MyDALClass GetDbContext()
        {
            return this.dbContext;
        }
    }
    /// <summary>
    /// 請將 MyDAL 類別 Mark 掉,置換成您實際的 DAL 類別
    /// </summary>
    public class MyDALClass : IDisposable
    {
        public void Dispose()
        {
        }
    }
}

我想程式碼應該還蠻容易懂吧!它只是實作,並完成 GetDbContext() 好讓外部可以取得 DAL 的執行個體。

 

Interface 資料夾

再來是 Interface 的部分,這部分「ICategoriesRepository.cs」、「IRepository.cs」,這兩個檔案內容都是空的,目的是讓你橋接、撰寫現有 DAL 提供的方法,只有「IUnitOfWork.cs」會包含一個回傳 IRepository<T> 介面的定義,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindModelsOriginal.Interface
{
    /// <summary>
    /// 
    /// </summary>
    public interface IUnitOfWork
    {
        void Dispose();
        IRepository<T> Repository<T>() where T : class;
    }
}


而有趣的是,這裡我的 IRepository<T> 反過來實作 ICategoriesRepository 介面,為什麼這樣做?因為我提到了這是為了橋接現有的 DAL 而做的一個暫時性的架構,目的是蒐集所有現有的 DAL 的方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindModelsOriginal.Interface
{
    /// <summary>
    /// DAL: 共通的 Reposiroty 樣式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IRepository<T> : ICategoriesRepository
    {
    }
}


這裡是讓你定義現有 DAL 中,有哪一些與 Categories 相關的 DAL 方法,改以介面的方式定義在這裡。

using NorthwindModelsOriginal.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NorthwindModelsOriginal.Interface
{
    public interface ICategoriesRepository
    {
        //在這裡定義你的介面
    }
}

Repository

這個資料夾裡面包含三個檔案,一個是「CategoriesRepository.cs」、「GenericRepository.cs」、「UnitOfWork.cs」,值得說明的是,這裡的 UnitOfWork 只是用來產生對 IRepository<T> 實作的執行個體,因為GenericRepository<TEntity> 實作了 IRepository<TEntity>,而且 IRepository<TEntity> 又實作了每一個橋接的 ICategoriesRepository、IXXXXRepository 等介面,所以 GenericRepository<TEntity> 能夠轉型為任何有實作 IRepository<T> 的介面並透過 UnitOfWork 動態建立並回傳。

詳細的 UnitOfWork 程式碼如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NorthwindModelsOriginal.DbContextFactory;
using NorthwindModelsOriginal.Interface;
using NorthwindModelsOriginal.Repository;
using NorthwindModelsOriginal.Interface;

namespace NorthwindModelsOriginal.Repository
{
    /// <summary>
    /// 
    /// </summary>
    public class UnitOfWork : IUnitOfWork, IDisposable
    {
        private MyDALClass _context { get; set; } //請將 MyDALClass 類別 置換成您實際的 DAL 類別
        private IDbContextFactory _factory;

        private Hashtable _repositories;
        private bool _disposed = false;

        public UnitOfWork(IDbContextFactory factory)
        {
            if (factory == null)
            {
                throw new ArgumentException("IDbContextFactory is null!.");
            }
            this._factory = factory;
            this._context = factory.GetDbContext();
        }
        /// <summary>
        /// 取得實作 IRepository 的物件.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public IRepository<T> Repository<T>() where T : class
        {
            if (_repositories == null)
                _repositories = new Hashtable();

            var type = typeof(T).Name;

            //判斷 Hashtable 中是否已經擁有符合的 type
            if (!_repositories.ContainsKey(type))
            {
                //建立需要的Repository
                var repositoryType = typeof(GenericRepository<>);

                var repositoryInstance =
                    Activator.CreateInstance(
                        repositoryType.MakeGenericType(typeof(T)),
                        _factory);

                _repositories.Add(type, repositoryInstance);
            }

            return (IRepository<T>)_repositories[type];
        }

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

        /// <summary>
        /// 釋放物件
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}
 
結語:

這個 MyORM 的框架是筆者所設計,它是將筆者常用的程式碼規範,我的專案內、與包含客戶端所使用的 Coding Standard/Coding Rules 蒐集後,撰寫成 C# Project Templates,並透過 IWizard 來產生這些常用的框架,因此,透過使用這個 MyORM 能夠簡化專案成員重複造輪子的情況,且更有一致性,因為透過程式碼產生器自動化產生,也大大降低錯誤率的發生。

留言

這個網誌中的熱門文章

軟體工程師 - 成長的 10 個階段

常見的程式碼壞味道(Code Smell or Bad Smell)

什麼是 gRPC ?