2011-06-03 14:25:33| 分類: 默認分類 | 標簽: |字號大中小 訂閱
ADO.NET Entity Framework操作數(shù)據庫的過程對用戶是透明的(當然我們可以通過一些工具或方法了解發(fā)送到數(shù)據庫的SQL語句等)。我們唯一能做的是操作EDM,EDM會將這個操作請求發(fā)往數(shù)據庫。
Entity Framework實現(xiàn)了一套類似于ADO.NET2.0中連接類(它們使用方式相同,均基于Provider模式)的被稱作EntityClient的類用來操作EDM。ADO.NET2.0的連接類是向數(shù)據庫發(fā)送SQL命令操作表或視圖,而EntityClient是向EDM發(fā)送EntitySQL操作Entity。EntityClient在EntityFramework中的作用是相當重要的,所有發(fā)往EDM的操作都是經過EntityClient,包括使用LINQ to Entity進行的操作。
上文提到對EDM的操作,首先通過一個圖來展現(xiàn)一下目前我們可用的操作的EDM的方式:
這幾種訪問方式使用介紹如下:(部分示例代碼來源MSDN Magzine)
示例代碼:
string city = "London";
using (EntityConnection cn = new EntityConnection("Name=Entities"))
{
cn.Open();
EntityCommand cmd = cn.CreateCommand();
cmd.CommandText = @"SELECT VALUE c FROM Entities.Customers AS c WHERE
c.Address.City = @city";
cmd.Parameters.AddWithValue("city", city);
DbDataReader rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (rdr.Read())
Console.WriteLine(rdr["CompanyName"].ToString());
rdr.Close();
}
在有EntityClient+EntitySQL這種使用方式下,使用ObjectService+EntitySQL的方式是多此一舉,不會得到任何編輯時或運行時的好處。在ObjectContext下使用EntitySQL的真正作用是將其與LINQ to Entity結合使用。具體可見下文所示。
示例代碼:
string city = "London";
using (Entities entities = new Entities())
{
ObjectQuery<Customers> query = entities.CreateQuery<Customers>(
"SELECT VALUE c FROM Customers AS c WHERE c.Address.City = @city",
new ObjectParameter("city", city)
);
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
方式一:
string city = "London";
using (Entities entities = new Entities())
{
var query = from c in entities.Customers
where c.Address.City == city
select c;
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
方式二:
string city = "London";
using (Entities entities = new Entities())
{
var query = entities.Customers.Where(r => r.Address.City == city);
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
這兩段示例代碼中的entities.Customer的寫法隱式調用了2中示例的ObjectQuery<Customers>來進行查詢(關于此可以參見EDM的設計器文件-xxx.designer.cs)。在方式二中的Where方法傳入的是一個Lambda表達式,你也可以傳入一條EntitySQL語句做參數(shù)來將LINQ與EntitySQL結合使用。如下代碼演示其使用:
string city = "London";
using (Entities entities = new Entities())
{
var query = entities.Customers.Where("r.Address.City = '"+city+"'");
foreach (Customers c in query)
Console.WriteLine(c.CompanyName);
}
這也是上文提到的在ObjectContext下使用EntitySQL的一個主要作用,上面的例子比較簡單可能看不到這樣使用的優(yōu)勢,但是如下兩種情況下使用EntitySQL可能是最好的選擇。
ObjectQuery.Where(LambdaExpression1) .Where(LambdaExpression2)… |
但是當這個條件的存在與否需要在運行時判斷時,我們只能通過組合字符串來得到這個條件,我們可以將條件組合為EntitySQL并傳遞給Where()方法。
下面代碼演示使用EntitySQL的like完成模糊查詢:
context.Customer.Where("it.CustomerID LIKE @CustomerID", new System.Data.Objects.ObjectParameter("CustomerID","%V%")); |
這個并不是只能使用EntitySQL來實現(xiàn),LINQ to Entity也可以很容易完成。如下代碼:
context.Customer.Where(r => r.CustomerID.Contains("V")); |
同理,"V%"、"%V"可以分別使用StartsWith()與EndsWith()函數(shù)實現(xiàn)。
使用LINQ to Entity需要注意的一個方面是,在完成查詢得到需要的結果后使用ToList或ToArray方法將結果轉變?yōu)閮却嬷械膶ο?,然后使用LINQ to Objects來處理,否則處在Entity Framework的聯(lián)機模式下對性能有很大的影響。
首先用下圖來說明一個執(zhí)行過程。
圖中所示表達的意思已經非常清楚,稍加解釋的是,無論是通過EntityClient直接提供給Entity Client Data Provider的Entity SQL還是通過ObjectService傳遞的Entity SQL(或是LINQ to Entity),都在Entity Client Data Provider中被解釋為相應的Command Tree,并進一步解釋為對應數(shù)據庫的SQL。這樣來看使用LINQ to Entity與Entity SQL的效率應該差不多,但是還有一個問題,那就是EntitySQL所轉換的最終SQL可能要比LINQ to Entity生成的SQL效率高,這在一定程度上使兩者效率差增大,但是LINQ to Entity有其它技術無法比擬的好處,那就是它的強類型特性,編輯時智能感知提醒,編譯時發(fā)現(xiàn)錯誤,這都是在一個大型項目中所需要的。雖然現(xiàn)在也有了調試EntitySQL的工具,但其與強類型的LINQ to Entity還是有很大差距。
另外在ObjectService與直接使用EntityClient問題的選擇上。如果你想更靈活的控制查詢過程,或者進行臨時查詢建議選擇EntityCLient,如果是操作數(shù)據那只能采用ObjectService。
上文總結了各種操作EDM的方式,下面引用MSDN的一個對這幾種技術進行比較的表格:
|
EntityClient 和實體 SQL |
對象服務和實體 SQL |
對象服務和 LINQ |
定向到 EntityClient 提供程序 |
是 |
否 |
否 |
適合臨時查詢 |
是 |
是 |
否 |
可直接發(fā)出 DML |
否 |
否 |
否 |
強類型化 |
否 |
否 |
是 |
可將實體作為結果返回 |
否 |
是 |
是 |
通過這個表可以很好對某一場合下應該選擇的技術進行判斷。EntityClient 和實體 SQL可以進行最大的控制,而使用LINQ to Entity可以獲得最佳的編輯時支持。
通過EdmGen更靈活的控制EDM
在.NET Framework 3.5的文件夾下有一個名為EdmGen的工具,Visual Studio的實體設計器就是調用這個工具來完成EDM的生成等操作。通過直接使用這個工具的命令行選項我們可以進行更多的控制。
這個命令的參數(shù)及作用如下:
EdmGen 選項 /mode:EntityClassGeneration 從 csdl 文件生成對象 /mode:FromSsdlGeneration 從 ssdl 文件生成 msl、csdl 和對象 /mode:ValidateArtifacts 驗證 ssdl、msl 和 csdl 文件 /mode:ViewGeneration 從 ssdl、msl 和 csdl 文件生成映射視圖 /mode:FullGeneration 從數(shù)據庫生成 ssdl、msl、csdl 和對象 /project:<字符串> 用于所有項目文件的基名稱 (短格式: /p) /provider:<字符串> 用于 ssdl 生成的 Ado.Net 數(shù)據提供程序的名稱。(短格式: /prov) /connectionstring:<連接字符串> 您要連接到的數(shù)據庫的連接字符串 (短格式: /c) /incsdl:<文件> 從中讀取概念模型的文件 /refcsdl:<文件> 包含 /incsdl 文件所依賴的類型的 csdl 文件 /inmsl:<文件> 從中讀取映射的文件 /inssdl:<文件> 從中讀取存儲模型的文件 /outcsdl:<文件> 將生成的概念模型寫入到其中的文件 /outmsl:<文件> 將生成的映射寫入到其中的文件 /outssdl:<文件> 將生成的存儲模型寫入到其中的文件 /outobjectlayer:<文件> 將生成的對象層寫入到其中的文件 /outviews:<文件> 將預生成的視圖對象寫入到其中的文件 /language:CSharp 使用 C# 語言生成代碼 /language:VB 使用 VB 語言生成代碼 /namespace:<字符串> 用于概念模型類型的命名空間名稱 /entitycontainer:<字符串> 用于概念模型中的 EntityContainer 的名稱 /help 顯示用法信息 (短格式: /?) /nologo 取消顯示版權消息 |
使用示例:
從 Northwind 示例數(shù)據庫生成完整 Entity Model。
EdmGen /mode:FullGeneration /project:Northwind /provider:System.Data.SqlClient /connectionstring:"server=.\sqlexpress;integrated security=true; database=northwind" |
從 ssdl 文件開始生成 Entity Model。
EdmGen /mode:FromSSDLGeneration /inssdl:Northwind.ssdl /project:Northwind |
驗證 Entity Model。
EdmGen /mode:ValidateArtifacts /inssdl:Northwind.ssdl /inmsl:Northwind.msl /incsdl:Northwind.csdl |
通過對比上面圖4與圖2、圖3我們可以很清楚的看到使用Entity Framework一個很大的好處,我們可以把實體類的定義由一個單獨的項目使用C# class完成這樣一種設計方式轉變?yōu)槭褂脁ml文件定義并集成到數(shù)據訪問層。
在以往要在一個項目中動態(tài)創(chuàng)建實體,我所知的方法是把要添加的實體放入一個程序集,然后通過反射加載程序集?,F(xiàn)在可以通過動態(tài)更改EDM的方法來增加實體并將其映射到數(shù)據庫,后者是以前無法實現(xiàn)的。
便于更改數(shù)據庫,當更換數(shù)據庫后,只需修改SSDL的定義,(如果數(shù)據庫的表明有變動,也只需多修改MSL),對CSDL沒有任何影響,從而也不需要對程序的BLL等上層部分做任何改動。
要想讓一個數(shù)據庫支持Entity Framework,一個必要條件就是該數(shù)據庫需提供相應的Entity Client Data Provider,這樣才能將Entity SQL轉換為針對此數(shù)據此數(shù)據庫的SQL并交由ADO.NET來執(zhí)行。當然該數(shù)據庫還需要提供ADO.NET Data Provider。
Entity Framework技術的效率問題是其幾乎唯一一個稍有不足之處。首先其將EntitySQL轉換為SQL的方式屬于解釋性轉換,性能較差。另外Entity Framework在每次應用啟動時需要讀取EDM,這個過程較慢(但在后續(xù)操作時,就不再存在這個問題)。
由于當前的EntitySQL不支持DML操作,所以當前版本的Entity Framework的插入、更新及刪除操作需要通過Object Service來完成。在EDM的設計器文件xxx.designer.cs中自動生成了一些簽名為
void AddToEntity(EntityType entity)
的方法。我們只需要新建一個實體對象并調用這個方法添加實體即可。注意這個函數(shù)內部調用
entities.AddObject("EntitySetName", entity);
最后調用entities.SaveChanges()方法將修改保存回數(shù)據庫,這是所有三種更新操作所需的。更新與刪除操作都需要先使用ObjectService定位操作的實體對象,更新操作直接使用賦值運算符,刪除操作則調用
entites.DeleteObject(object o);
方法。之后調用entities.SaveChanges()方法保存,這個過程簡單,不再贅述。
當前版本的Entity Framework不支持自動延遲加載,所有當前未使用的關系中的相關實體默認按不加載處理,當我們需要通過關系獲取一個實體對象時,我們可以采用兩種方法:
using (Entities entities = new Entities())
{
var query = (from o in entities.Orders
where o.Customers.CustomerID == "ALFKI"
select o);
foreach (Orders order in query)
{
if (!order.CustomersReference.IsLoaded)
order.CustomersReference.Load();
Console.WriteLine(order.OrderID + " --- " +
order.Customers.CompanyName);
}
}
先看代碼示例
using (Entities entities = new Entities())
{
var query = (from o in entities.Orders.Include("Customers")
where o.ShipCountry == "USA"
select o);
foreach (Orders order in query)
Console.WriteLine(order.OrderID + " --- " +
order.Customers.CompanyName);
}
查詢中針對 Orders 實體調用的 Include 方法接受了一個參數(shù),該參數(shù)在本示例中將要求查詢不僅要檢索 Orders,而且還要檢索相關的 Customers。這將生成單個 SQL 語句,它會加載滿足 LINQ 查詢條件的所有 Order 和 Customer。
兩種加載關系實體的方式的選擇根據:如果針對關系數(shù)據你只需做一到兩次查詢,則使用顯示加載更高效,如果要持續(xù)訪問關系實體中數(shù)據,則使用預先加載。
關系下的添加,更新與刪除與上述操作基本相同,唯一需要注意的是刪除操作不支持級聯(lián)刪除,需要手工遍歷所有的相關項并將其一一刪除。注意這里刪除操作不能使用foreach來遍歷需要刪除的關系實體。取而代之的有兩種方法:
while (result.Order_Details.Count > 0)
{
// 刪除操作…
}
var items = result.Order_Details.ToList();
foreach (var item in items)
{
// 刪除操作…
}
.NET Framework提供了許多xxxDataSource控件,如SqlDataSource,ObjectDataSource等,這些數(shù)據源控件大大方便了我們的數(shù)據綁定操作。不幸的是目前還沒有針對Entity Framework的數(shù)據源控件發(fā)布,但是將數(shù)據綁定到諸如ListBox,Grrdview或DetailsView控件也是很簡單的。這源于使用ObjectContext操作返回的IQueryable<T>對象或是使用EntityClient查詢返回的ObjectQuery對象都實現(xiàn)了IEnumerable接口。這樣很容易將這些數(shù)據綁定到數(shù)據顯示控件。更新操作可以按上文所述在相應的時間處理函數(shù)中寫更新EDM的程序即可。
默認情況下(Visual Studio對Entity Framework數(shù)據項目的默認設置),EDM這個XML文件被作為資源在編譯時嵌入到程序集中。這種情況下當更改EDM后需要重新編譯這個程序集才能使更改生效。通過更改項目屬性也可以讓EDM作為三個獨立的XML文件存在于項目中。為了讓應用程序可以找到EDM(無論其以什么方式存儲)需要一個鏈接字符串來指示EDM所在的位置。實體模型設計器生成的鏈接字符串如下所示:
<add name="ASSEntities" connectionString=" metadata=res://*/ass.csdl| res://*/ass.ssdl| res://*/ass.msl; provider=System.Data.SqlClient; provider connection string="Data Source=(local);Initial Catalog=ASS;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" /> |
http://msdn.microsoft.com/zh-cn/library/cc716756.aspx
關鍵的一點應用程序是怎樣找到這個字符串的,對于使用EntityClient的情況,可