夜的第七章

ASP.NET、C#、.NET开发资源、Javascript等等为主的开发备注收藏注意警告等的一个个人博客。

« jQuery-对Select的操作集合(转)首次接觸 ADO.NET Entity Framework(转) »

ADO.NET Entity Framework概述(转)


ADO.NET 回顧
自從微軟在 COM 上推出 ADO(ActiveX Data Object)以後,大大改變了在微軟平台上存取資料庫的方法,而且 ADO 是一個語法上很有彈性的資料存取元件,開發人員可以用 Command 執行 SQL 指令或產生資料結果集,或者直接用 Recordset 來產生結果集,當時以 VB 與 ASP 為主的開發人員,無不大力捧場,使得 ADO 變成當時 ASP 和 VB 程式開發連結資料庫的首選。不過因為 ADO 是一個連結導向(Connection-Oriented)的資料存取物件,會鎖定住資料庫的游標以及連線資源,對於資料庫連線來說,耗損的速度會變的很快,尤其資料庫連線是很貴的(一個 CAL 大約 2,500 元,一百個就要 25 萬了),所以 ADO 在 .NET Framework 中重新設計,導入了離線資料存取模型(Disconnected Data Access),也就是常聽到的 DataSet 與 DataTable,將連線、存取與結果集處理確實分開,組成現在的 ADO.NET 資料存取模型。

功能 ADO ADO.NET 
連線 ADODB.Connection 配合所屬的 OLE DB Provider,或者由 OLE DB Provider for ODBC 加上資料庫的 ODBC Driver。 IDbConnection 其衍生類別有 SqlConnection, OleDbConnection, OdbcConnection 與 OracleConnection 等。 
指令操作 ADODB.Command IDbCommand 
結果集處理 ADODB.Recordset 連線導向。 IDbDataReader: 連線單向型游標。 
IDbDataAdapter: 填充資料。 
DataSet: 離線型DataTable的集合。 

ADO.NET 由 IDbConnection, IDbCommand, IDbDataReader 與 IDbDataAdapter,以及 DataSet/DataTable 等離線資料物件所組成,主要的資料操作都是在 DataSet 和 DataTable 中,而與資料庫連線則是用 IDbConnection,SQL 指令處理在 IDbCommand,另外若想要快速的存取資料,則可以利用 IDbDataReader 所提供的單向游標資料流來存取資料庫。ADO.NET 其實是個相當不錯的資料存取模型,只是因為在 .NET Framework 剛推出時,ADO 轉換到 ADO.NET 在操作習慣上的差異,使得還是有不少人使用 ADO,不過在 .NET 2.0 之後,這個比例已經減少很多了。

不過,就算是 ADO.NET 這樣的模型,也還是會有一個永遠解決不了的問題:SQL 指令。沒錯,要能夠將 ADO.NET 功能發揮到極致的話,就必須要直接處理 SQL 指令,包括 CRUD(Create, Retrieve, Update, Delete)與參數化處理,若資料庫中有檢視表或是預存程序的話,也都要處理到,因而讓程式開發人員多一項 SQL 指令學習的負擔(在台灣的話可算稀鬆平常),為了要解決這樣的問題,於是出現了 Hibernate 技術,這個技術可以套用在 Java 與 .NET中(.NET 的稱為 NHibernate),它利用了資料庫與程式物件間的 Object-Relationship Mapping(O/R Mapping)方式,簡化程式開發人員在處理資料庫物件時的工作。

ADO.NET 的下一個版本:ADO.NET “vNext”就是要實作出一個可以隔離程式碼與資料庫介面的共用 API 層次,讓程式開發人員可以用程式碼的方式存取資料庫,而不是經由 ADO.NET 物件,這個功能在 Entity Framework 被實現出來,再搭配 Language-in-Query(LINQ)能力,讓 .NET Framework 3.5 的資料存取功能又邁入了新的里程碑。

ADO.NET Entity Framework 概觀
ADO.NET Entity Framework 是新一代,具有隔離程式碼與資料庫間,並透過一個新的 API 來存取資料庫的資料存取元件,並且和 LINQ 搭配,讓開發人員不必再撰寫 SQL 指令與 ADO.NET 物件,即可存取資料庫。

為了要達成資料庫與程式碼隔離的能力,必須要有一些中介資料(Meta data)描述資料與物件間的對應,以及設定物件間的關聯(Object Relationship),在 ADO.NET Entity Framework,由三層中介資料(XML)來負責這個工作:

 
Conceptual Schema
Conceptual Schema 由 CSDL(Conceptual Schema Definition Language)所組成,在設計資料類別時就可以開始定義,以 OOP 的術語來看,每一個類別構成一個 Entity,在 Entity 中會有資料屬性與關聯設定(Association),例如:


<?xml version="1.0" encoding="utf-8"?>
<Schema xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" 
        xmlns:edm="http://schemas.microsoft.com/ado/2006/04/edm" 
        xmlns="http://schemas.microsoft.com/ado/2006/04/edm" 
        Namespace="NorthwindLib" Alias="Self">

  <EntityType Name="Product">
    <Key>
      <PropertyRef Name="ProductID" />
    </Key>
    <Property Name="ProductID" Type="Int32" Nullable="false"/>
    <Property Name="ProductName" Type="String"
        Nullable="false" MaxLength="40" ConcurrencyMode="fixed" /> 
    <Property Name="UnitPrice" Type="Decimal"
        Nullable="true" Precision="10" Scale="4" ConcurrencyMode="fixed" />
    <NavigationProperty Name="Category" 
        Relationship="Self.Category_Product" 
        FromRole="Product" ToRole="Category" />
  </EntityType>

  <Association Name="Category_Product">
    <End Role="Category" Type="Self.Category" 
        Multiplicity="1" />
    <End Role="Product" Type="Self.Product" 
        Multiplicity="0..*" PluralRole="Products" />
  </Association>

  <EntityContainer Name="Northwind">
    <EntitySet Name="Categories" EntityType="Self.Category" />
    <EntitySet Name="Products" EntityType="Self.Product" />
       <AssociationSet Name="CategoryProducts" Association="Self.Category_Product">
      <End Role="Category" EntitySet="Categories" />
      <End Role="Product" EntitySet="Products" />
    </AssociationSet>
  </EntityContainer>
</Schema>


在 Entity 中會定義 Entity 的主鍵(Primary Key)、每個屬性的資料型別、可否 NULL 以及其他的資訊(例如可容納字串大小)等,這些資訊會影響到未來存取 Entity 時的指令。每個 Conceptual Schema 中可能包含很多的 Entity 以及 Association 所組成,所以要管理這些 Entity,就需要有一個集合來處理,這個集合就是 EntityContainer,不但可以包含多個 Entity,也可以引入其他的 Entity。


<?xml version="1.0" encoding="utf-8"?>
<Schema xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration"
    xmlns:edm="http://schemas.microsoft.com/ado/2006/04/edm"
    xmlns="http://schemas.microsoft.com/ado/2006/04/edm"
    Namespace="MyCompany.ContainerType">

    <EntityContainer name="ContainerOne">
        <Using Namespace="MyCompany.EntityTypes" Alias="basicTypes"/>
        <Using Namespace="MyCompany.RelationshipTypes" Alias="relnTypes"/>

        <EntitySet Name="CustomerSet" EntityType="basicTypes.Customer"/>
        <EntitySet Name="OrderSet" EntityType="basicTypes.Order"/>

        <AssociationSet Name="CustomerOrderSet" 
      Association="relnTypes.CustomerOrder">
            <End EntitySet="CustomerSet" Role="Orders"/>
            <End EntitySet="OrderSet" Role="OrderedBy"/>
        </AssociationSet>
    </EntityContainer>
</Schema>


除了 Entity 外,每個 Entity 間可能會有一些關聯性,這個關聯可能是一對一或一對多等,這些關聯稱為 Association,並且會依多元程度(Degree)、對應實體數(Multiplicity)與方向(Direction)識別。

例如以下的 Schema 就定義了 Product 與 Category 兩個 Entity 的關聯:


圖:一元關係。


圖:二元關係。


圖:三元關係。


<Association Name="Category_Product">
    <End Role="Category" Type="Self.Category" 
        Multiplicity="1" />
    <End Role="Product" Type="Self.Product" 
        Multiplicity="0..*" PluralRole="Products" />
</Association>


Storage Schema
在實體資料庫的部份,是由 Storage Schema 負責,定義了資料庫的各種物件,如資料表、預存程序、使用者定義函數與檢視表等等。


<?xml version="1.0" encoding="utf-8"?>
<Schema Namespace="AdventureWorksHRTarget" Alias="Self"
          xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">

  <EntityType Name="Department">
    <Key>
        <PropertyRef Name="DepartmentID">
    </Key>
    <Property Name="DepartmentID" Type="smallint"
                Nullable="false" StoreGeneratedPattern="identity" />
    <Property Name="Name" Type="nvarchar" 
                              Nullable="false" MaxLength="50" />
    <Property Name="GroupName" Type="nvarchar"
                              Nullable="false" MaxLength="50" />
    <Property Name="ModifiedDate" Type="datetime" Nullable="false" />
  </EntityType>

<EntityType Name="Employee" >
    <Key>
        <PropertyRef Name="EmployeeID">
    </Key>
    <Property Name="EmployeeID" Type="int" Nullable="false"
                                  StoreGeneratedPattern="identity" />
    <Property Name="NationalIDNumber" Type="nvarchar"
                              Nullable="false" MaxLength="15" />
    <Property Name="ContactID" Type="int" Nullable="false" />
    <Property Name="LoginID" Type="nvarchar" 
                             Nullable="false" MaxLength="256" />
    <Property Name="ManagerID" Type="int" />
    <Property Name="Title" Type="nvarchar" Nullable="false"
                                                   MaxLength="50" />
    <Property Name="BirthDate" Type="datetime" Nullable="false" />
    <Property Name="MaritalStatus" Type="nchar" Nullable="false"
                                  MaxLength="1" FixedLength="true" />
    <Property Name="Gender" Type="nchar" Nullable="false"
                              MaxLength="1" FixedLength="true" />
    <Property Name="HireDate" Type="datetime" Nullable="false" />
    <Property Name="SalariedFlag" Type="bit" Nullable="false" />
    <Property Name="VacationHours" Type="smallint" Nullable="false" />
    <Property Name="SickLeaveHours" Type="smallint" Nullable="false" />
    <Property Name="CurrentFlag" Type="bit" Nullable="false" />
    <Property Name="rowguid" Type="uniqueidentifier" Nullable="false" />
    <Property Name="ModifiedDate" Type="datetime" Nullable="false" />
  </EntityType>

<EntityContainer Name="HumanResources">
    <EntitySet Name="Department" EntityType="Self.Department" />
    <EntitySet Name="Employee" EntityType="Self.Employee" />
    <AssociationSet Name="Employee_EmployeeManagerID"
                  Association="Self.Employee_EmployeeManagerID">
      <End Role="Employee" EntitySet="Employee" />
      <End Role="Employee" EntitySet="Employee" />
    </AssociationSet>
  </EntityContainer>

</Schema>


基本上和 Conceptual Schema 差異不大,但因為要對應到實體資料庫,所以所用的設定都要和實體資料庫相同,否則會有對應上的錯誤。

Mapping Schema
當 Conceptual Schema 與 Storage Schema 定義完成後,再把它們串在一起,就完成了整個 Entity Model 的定義,而負責將 Conceptual 與 Storage Schema 串起來的工作,就由 Mapping Schema(由 MSL 組成)來處理。


<?xml version="1.0" encoding="utf-8"?>
<Mapping xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS"
    xmlns:edm="urn:schemas-microsoft-com:windows:storage:mapping:CS"
    Space="C-S">

  <EntityContainerMapping 
    CdmEntityContainer="Northwind" StorageEntityContainer="dbo">

    <EntitySetMapping Name="Categories">
      <EntityTypeMapping TypeName="NorthwindLib.Category">
        <MappingFragment StoreEntitySet="Categories">
          <ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
          <ScalarProperty Name="CategoryName" ColumnName="CategoryName" />
          <ScalarProperty Name="Description" ColumnName="Description" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>

    <EntitySetMapping Name="Products">
      <EntityTypeMapping TypeName="NorthwindLib.Product">
        <MappingFragment StoreEntitySet="Products">
          <ScalarProperty Name="ProductID" ColumnName="ProductID" />
          <ScalarProperty Name="ProductName" ColumnName="ProductName" />
          <!--<ScalarProperty edm:Name="CategoryID"
                                   edm:ColumnName="CategoryID" />-->
          <ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
          <Condition ColumnName="Discontinued" Value="false" />
        </MappingFragment>
      </EntityTypeMapping>
      <EntityTypeMapping TypeName="NorthwindLib.DiscontinuedProduct">
        <MappingFragment StoreEntitySet="Products">
          <ScalarProperty Name="ProductID" ColumnName="ProductID" />
          <ScalarProperty Name="ProductName" ColumnName="ProductName" />
          <!--<ScalarProperty edm:Name="CategoryID"
                                    edm:ColumnName="CategoryID" />-->
          <ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
          <ScalarProperty Name="UnitsInStock" ColumnName="UnitsInStock" />
          <Condition ColumnName="Discontinued" Value="true" />
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>

    <AssociationSetMapping Name="CategoryProducts" 
                TypeName="NorthwindLib.Category_Product" 
                StoreEntitySet="Products">
      <EndProperty Name="Category">
        <ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
      </EndProperty>
      <EndProperty Name="Product">
        <ScalarProperty Name="ProductID" ColumnName="ProductID" />
      </EndProperty>
    </AssociationSetMapping>

  </EntityContainerMapping>

</Mapping>


存取 ADO.NET Entity Framework
在定義好每個 Schema 後,就可以利用 Entity Framework 所提供的 API,或者是透過 LINQ to Entities 來存取 Entity。

存取介面 說明 
LINQ to Entities 直接透過 LINQ 存取,可完全將程式碼與資料庫間完全隔離,並且由 LINQ 自動使用內部的介面(即 Object Service)來存取資料庫。 
Object Service 可以透過 Entity SQL 來存取 Entity,並且直接以物件的方式來存取結果集(因為結果集本身就是物件的集合)。 
EntityClient 透過類似 ADO.NET 的方法,以及 Entity SQL 存取 Entity。 

若是用 LINQ to Entities 來存取,則開發人員可完全不必處理任何SQL指令(除了 LINQ 的語法以外),即可存取資料庫中的資料,而這些資料都以物件方式呈現:


using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
   ObjectQuery<Product> products = AWEntities.Product;

   IQueryable<string> productNames =
      from p in products
      select p.Name;

   Console.WriteLine("Product Names:");
   foreach (var productName in productNames)
   {
      Console.WriteLine(productName);
   }                
}


若是用 Object Service,則會用到一些 SQL(Entity SQL):


using (AdventureWorksEntities advWorksContext =
    new AdventureWorksEntities())
{
    try
    {
        string queryString =
            @"SELECT VALUE Product FROM AdventureWorksEntities.Product AS Product";

        ObjectQuery<Product> productQuery =
            new ObjectQuery<Product>(queryString, advWorksContext, MergeOption.NoTracking);

        // Iterate through the collection of Product items.
        foreach (Product result in productQuery)
            Console.WriteLine("Product Name: {0}; Product ID: {1}",
                result.Name, result.ProductID);
    }
    catch (QueryException ex)
    {
        Console.WriteLine(ex.ToString());
    }
}


若是想要用類似 ADO.NET 的方式來存取 Entity 的話,則可以利用 Entity Client 來實作:


static void Main(string[] args)
{    
    String connString = 
@"Provider=System.Data.SqlClient; 
                      Metadata=.;
                      Provider Connection String='Server=.; 
                      Database=AdventureWorks; 
            Integrated Security=true; 
                      Connection Timeout=5; 
                      MultipleActiveResultSets=true;'";

String esqlQuery = @"SELECT VALUE p.ProductID 
                      FROM AdventureWorksEntities.Product AS p";

    try
    {
            using (EntityConnection conn = 
                new EntityConnection(connString))
            {
                // Open a connection.
                conn.Open();

                EntityCommand cmd = conn.CreateCommand();
                cmd.CommandText = esqlQuery;
                // Execute the query.
                using (EntityDataReader rdr = cmd.ExecuteReader
(CommandBehavior.SequentialAccess))
                {
                    // Start reading records.
                    PrintReader(rdr);
                }
            }
        }
        catch (System.Data.MappingException e)
        {
            Console.WriteLine(e.ToString());
        }
        catch (System.Data.CommandExecutionException e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}


Visual Studio 的支援
在 Visual Studio 2008 中,ADO.NET Entity Framework 提供了圖形化介面的工具:Entity Data Model Wizard,可以自動連接資料庫,產生相對應的 C/S(Conceptual/Storage)與 MSL 等定義,開發人員可以省下很多工夫。另外也提供了一個命令列工具:edmgen.exe,可以讓開發人員產生或檢驗 C/S 與 MSL 等設定格式是否正確。

結語
ADO.NET Entity Framework 將 ADO.NET 的資料存取能力抽象化,讓開發人員可以在不必關注資料庫的情況下,以程式語言提供的方法來存取資料庫,將資料庫管理與存取的工作交由資料庫的專家來處理,無形中可簡化許多在資料存取工作上消耗的時間,未來我們會再進一步討論這個新功能。

參考資料
ADO.NET Entity Framework Beta 2: http://www.microsoft.com/downloads/details.aspx?FamilyID=f1adc5d1-a42e-40a6-a68c-a42ee11186f7&DisplayLang=en

ADO.NET Entity Framework Tools Preview: http://www.microsoft.com/downloads/details.aspx?familyid=09A36081-5ED1-4648-B995-6239D0B77CB5&displaylang=en 


原文地址:http://www.microsoft.com/taiwan/msdn/columns/jhu_ming_jhong/ADO.NET_Entity_Framework_Overview.htm

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-Blog 1.8 Arwen Build 81206



©2007 - 2010 www.leadnt.com 保留一些权利吧