2012年10月5日 星期五

獨立開發Model-使用EF Code First with existing database

前言:
在開發專案時很多時候若有連接到資料庫,通常會把Model這個部分先切割出來開發,這樣可以讓Model獨立在主project之外讓其他projects可以一起使用,在維護的時候其實也會比較方便只需要抽換DLL就好(這也算關注點分離嗎),另外就是Model天生就比較能夠獨立出來開發。不過在獨立開發Model的時候也要注意一些小東西。今天使用Entity Framework (version 5)的Code First with existing database的方式來做個示範。

實作部分:
我們選用的Database是赫赫有名的Northwind資料庫,我想這個資料庫大家應該都比我還熟了。這次我們選出Products和Categories這兩個資料表來做示範的Table吧~!順便提醒一下用Code First with existing database時會有些小地方需要注意!
首先~我們就來把Northwind database bring online吧~


把資料庫online後,接下來就切到Visual Studio中創一個新的Class Library專案吧~
專案名稱就隨便給個Project.Model來代表這個DLL是我們的Model。而因為要用Entity Framework的Code First功能,我們要引用一個EF的DLL:EntityFramework,若是用Nuget來安裝的話會連同System.Data.Entity這個DLL一起裝,不過若是沒有要用視覺設計工具來輔助的話其實是可以不用這個DLL的。
使用Code First的方式來與資料庫做互動的話需要撰寫一個繼承DbContext的類別。這裡我們就把這個類別名稱取為Northwind。

using System.Data.Entity;
///這邊我們只用到Products與Categories這兩個資料表做示範。
///DbSet這個類別代表一組在Context中的實體資料集合,但要注意這個集合內的型別(class)都是相同的,
///不能接受複合型別,像是DbSet<Product,Category>(這邊型別相當於資料庫中的資料表的概念)。
public class Northwind : DbContext
{
    // 這裡的屬性名稱要注意,需要與資料表的名稱相同!
    // 2012-11-16更正!
    // 這邊要注意的應該是DbSet裡的名稱,(也就是Product與Catagory)
    // Case 1:沒有貼Table標籤
    // Code-First預設會依據DbContext中DbSet的泛型名稱(這裡就是Products, Catagories)作為
    // 資料庫中對應的表格名稱。若資料庫中沒有這個表格Code First會幫你產生(很貼心吧,千萬小心)。
    // Case 2:Product有貼Table標籤
    // Code-First會依據標籤所給予的名稱來做為對應到資料庫中的表格名稱。
    public DbSet<Product> Products { get; set; }

    public DbSet<Category> Categories { get; set; }
}
而另外兩個資料表則如下撰寫:
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
    // Code-First 的預設欄位對應會自動把ID結尾的屬性辨識為PrimaryKey,但可以用[Key]標籤來輔助。
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public Decimal? UnitPrice { get; set; }
    public bool Discontinued { get; set; }
    /// 注意!使用 Code First 連接既有資料庫沒有加上這個 Schema 標籤會出錯誤!
    /// 因為使用Category與Product資料表,因此需要將這兩張表格的關係寫明。
    /// 若是只引用Product資料表則不用寫也沒關係。
    /// 當然若兩張表格彼此沒有直接關係的話也不用寫這個。
    [ForeignKey("Category")]
    public int CategoryID { get; set; }

    public virtual Category Category { get; set; }
}
另外一張資料表(Category)如下:
using System.Collections.Generic;
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; }
}
另外我們撰寫另外一個類別來使用Northwind至這個類別(或是說資料庫)~
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();
        }
    }

接下來就是要注意的就是使用Code First with existing database時並不會自動幫我們把連線字串準備好,這必須得自行撰寫。因此我們在App.config(這個檔案會在安裝Entity Framework的時候幫我們把基本的東西宣告好)中,需要加入一段<connectionStrings>標籤~
<add name="Northwind"
         connectionString="Data Source=your source;Initial Catalog=Northwind;User Id=uid;Password=pwd" 
         providerName="System.Data.SqlClient"/>
若不確定連線字串如何撰寫可以參考這個網頁,裡面有很多資料庫的連線字串撰寫方法。
以上都寫好了之後其實就可以來使用我們的Northwind資料庫了~這邊我們新創一個空的web form專案,首先我們要先把我們的連線字串放置web.config中,然後就是引用Project.Model.dll了~!不過這邊要注意,引用的dll不能把copy local設為false,否則在create物件時會出現錯誤!
另外使用於console專案時,也可以直接把App.config複製過去,一樣參考DLL時copy local設為true就可以直接拿來用了!像是這樣~
using Project.Model;
static void Main(string[] args)
{
    // 能用using時儘量用,至少沒有害處。
    using (NorthwindRepository db = new NorthwindRepository())
    {
       foreach (var item in db.ListAllProductName())
       {
            Console.WriteLine("item name: {0}", item);
       }
    }
    Console.WriteLine("\nFinish!");
    Console.Read();
}