[VS2010] ADO.NET Entity Framework: 解構永續儲存無知物件

[VS2010] ADO.NET Entity Framework: 解構永續儲存無知物件

經過了前一篇的簡單實驗,讓你體驗了會令人尖叫的 POCO 物件建構模型的功能,這篇咱們就來深入了解一下那些程式在幹什麼的。

 

1. 在 Visual Studio 建立一個 Console 專案,名稱為 AdoEF_CustomPoco,並加入一個 Customer.cs 類別檔。

2. 加入 Customer 類別的宣告,這個類別很簡單,它就只是一個 POCO 物件。

 

public class Customer
{
    public int CustomerID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }
}

 

 

若要在 Entity Framework 中使用永續儲存無知的功能,則原始的 POCO 物件必須滿足下列的條件:

 

A. 類別必須是 public。

B. 類別不可以宣告成 sealed 或是 abstract 類別,因為這樣的話 Entity Framework 無法生成物件。

C. 類別不可以實作 IEntityWithChangeTracker 或 IEntityWithRelationships 介面。

D. 每個將對應到概念層的實體屬性都必須要是 public。

E. 類別必須要有一個預設無參數建構式 (或者完全沒有任何建構式)。

F. 每個定義在概念層的巡覽屬性都必須要和定義在類別的巡覽屬性一致,為了支援相關物件的延遲載入,屬性必須宣告為 virtual 並且不可以是 sealed。

G. 每個一對多或多對多關聯的巡覽屬性都必須回傳實作 ICollection<T> 的型別,且 T 是另一端物件的型別。

H. 每個在資料模型中對於到實體屬性的屬性都必須實作 virtual 的 get 和 set 存取子。

I. 在概念層定義的複雜屬性 (complex property) 都必須對應到類別中回傳參考型別的屬性,複雜屬性無法對應到實值型別或列舉值。

 

簡言之,作為資料物件的 POCO 類別愈簡單愈好,相關的屬性都要有 get/set 存取子,若有要實作關聯時,則作為關聯的屬性必須要傳回物件集合,而作為 complex 型別 (其他的類別物件) 的屬性,則必須要傳回參考的類別物件。筆者所示範的類別就是一個很簡單的 POCO 物件。

 

3. 建立一個 CustomerModel 類別,並加入下列程式碼:

 

public class CustomerModel : ObjectContext
{
    public CustomerModel(EntityConnection connection)
        : base(connection)
    {
        DefaultContainerName = "CustomerModel";
    }

    public IObjectSet<Customer> Customers
    {
        get { return base.CreateObjectSet<Customer>(); }
    }
}

 

如果曾寫過 Entity Framework 1.0 的人,可能會知道 ObjectContext 這個類別,它是封裝 .NET Framework 物件和資料庫之間聯繫的類別,下列程式碼就是使用 Object Service 存取 Entity 的作法:

 

using (NorthwindLib.Northwind nwContext = new Northwind())
{

    ObjectQuery<DbDataRecord> categories =
        nwContext.CreateQuery<DbDataRecord>(
        @"SELECT c.CategoryName, c.Description FROM Categories
            AS c ORDER BY c.CategoryName");

    foreach (DbDataRecord categoryRec in categories)
    {
        Console.WriteLine("{0}\t{1}",
            categoryRec["CategoryName"], categoryRec["Description"]);
    }
}

 

你可能會問,為什麼看不到 ObjectContext,因為它被封裝在 NorthwindLib.Northwind 類別中了,在 Entity Framework 1.0,ObjectContext 會被 Entity Model Designer 自動取用以產生 EDM 類別物件,但到了 Entity Framework 2.0 時,這個類別已經可以由開發人員自己取用,並且搭配新的 IObjectSet<T> 介面讓物件變成一個集合,並顯露給 Entity Framework。IObjectSet<T> 實作了 IEnumerable<T> 以及 IQueryable<T>,這代表由這個介面所封裝的物件都可以利用 LINQ 來存取。而配合這個新介面,ObjectContext 中也加入了一個新的方法叫做 CreateObjectSet<T>(),它是負責傳回實作 IObjectSet<T> 介面的物件,不過開發人員只要很簡單的使用 return CreateObjectSet<T>(); 就可以得到一組已實作 IObjectSet<T> 的集合物件了。Entity Framework 將會利用這個方法來生成 IObjectSet<T>。

 

繼承 ObjectContext 的意義在於自己使用程式碼創造新的 Entity Model,也就是使用程式碼建立好概念層 (Conceptual Schema),但 Entity Framework 有三層,目前只完全了一層,另外兩層呢?看下去吧。

 

4. 在 Program.cs 中加入下列程式碼:

class Program
{
    static void Main(string[] args)
    {
        // 稍後實作。
    }
}

public class CustomerModelConfiguration : EntityConfiguration<Customer>
{
    public CustomerModelConfiguration()
    {
        Property(c => c.CustomerID).IsIdentity(); // 設定為 IDENTITY
        Property(c => c.Name).HasMaxLength(103).IsRequired(); // 設定最大長度,以及其為必要欄位。
        Property(c => c.Address).Optional = true; // 設定其為選擇性欄位。
        Property(c => c.Phone).Optional = true; // 設定其為選擇性欄位。
    }
}

 

EntityConfiguration<T> 是 Entity Framework 2.0 的新類別,但它不是封裝在 System.Data.Entity.dll 中,而是封裝在 Microsoft.Data.Objects 命名空間 (Microsoft.Data.Entity.Ctp.dll),這個 DLL 必須要到微軟下載 ADO.NET Entity Framework Feature CTP 2,才可以在安裝目錄的 Binaries 目錄中找到,這個必須要另外加入參考,並在程式碼中宣告使用 Microsoft.Data.Objects 命名空間才行。EntityConfiguration<T> 是用來設定屬性的相關設定,像是欄位是否為識別欄位 (Identity Field),最大長度,是否為必須,以及可否為 NULL 等等,它等於是 Entity Framework 的 Storage Schema 與 Mapping Schema,用來將物件對應到欄位,並且宣告欄位的實體屬性資訊,以交給提供者來處理資料庫的實體作業。而且你可以發現,以前在 MSL 要宣告的一大堆東西,現在只用了一行簡單的 Lambda Expression (c=> c.CustomerID) 就解決了 …

 

5. 在 Program.cs 的 Main 方法中,加入下列程式碼:

 

static void Main(string[] args)
{
    var builder = new ContextBuilder<CustomerModel>();
    builder.Configurations.Add(new CustomerModelConfiguration());

    var connection = new SqlConnection("initial catalog=CustomerTestDB; integrated security=SSPI");

    using (var ctx = builder.Create(connection))
    {
        if (ctx.DatabaseExists())
            ctx.DeleteDatabase();

        ctx.CreateDatabase();
    }

}

 

將 Entity Framework 所需要的 CSDL (CustomerModel),SSDL 與 MSL (CustomerModelConfiguration) 定義好以後,最後就是要決定送交給哪個資料庫來跑,這個工作由 ContextBuilder<TModel> 來負責,ContextBuilder<TModel> 會使用 ObjectContext 中內含的 CSDL 以及 EntityConfiguration 的 SSDL/MSL 載入 (利用 ContextBuilder<TModel>.Configurations.Add() 加入),以及將指令送交給資料來源提供者 (Entity Framework Providers) 執行,轉換指令為 SQL 的工作就由 Provider 執行即可,開發人員無須負擔這一層的工作。

 

6. 按 F5 或 CTRL + F5 編譯並執行它後,使用 SSMS 去瀏覽資料庫,即會看到新的資料庫 CustomerTestDB 以及表格 Customers。

 

在此例子中,資料庫名稱由連線字串的 initial catalog 決定資料表名稱由 Model 中回傳 IObjectSet<T> 的屬性名稱決定欄位名稱則由 POCO 中的屬性名稱來決定,因此若需要調整 Entity 以符合命名規則的話,只要由這幾個地方的命名下手即可。

 

參考資料:

POCO in the Entity Framework part 1:

http://blogs.msdn.com/adonet/archive/2009/05/21/poco-in-the-entity-framework-part-1-the-experience.aspx

Visual Studio 2010 與 .NET Framework 4.0 文件庫:
http://msdn.microsoft.com/en-us/library/w0x726c2(VS.100).aspx

Reflector 工具。