Oracle在 2011-12-28 發佈了最新一版的 ODAC with Oracle Developer Tools for Visual Studio,
ODAC 11.2 Release 3 與 ODAC 11.2 Release 4 更新相隔了一年,而這一次讓我注意到的是 ODAC 11.2 Release 4(11.2.0.3.0) 包含了對 Entity Framework 的支援,也就是說原本還在 BETA 3 的ODAC for MS Entity Framework and LINQ to Entities 已經納入正式版了,所以有使用到 Oracle 資料庫開發的專案,想要在專案中使用Entity Framework技術是已經有 Oracle 所發佈的正式支援,然而在之前文章「ASP.NET MVC + Oracle 11g XE + Entity Framework. Part 4」最後所提到的問題是否在這個版本有獲得解決呢?
在本篇文章我們就來仔細的瞧瞧!
ODAC 11.2 Release 4 (11.2.0.3.0) with Oracle Developer Tools for Visual Studio
http://www.oracle.com/technetwork/database/windows/downloads/index-101290.html
Release:2011-12-28
Download:ODAC 11.2.0.3.0 with Oracle Developer Tools for Visual Studio
- Includes support for Entity Framework and LINQ
64-bit Oracle Data Access Components (ODAC) Downloads
http://www.oracle.com/technetwork/database/windows/downloads/index-090165.html
有需要安裝64-bit ODAC的開發者請務必要先安裝32-bit的ODAC後再安裝64-bit的ODAC XCopy,
Important:
The 32-bit Oracle Developer Tools for Visual Studio download is required for Entity Framework design-time features. The above download fully supports Entity Framework deployment, but does not contain design-time tools.
ODAC (11.2.0.3.0) Installation Instructions, Setup, and Notes
http://www.oracle.com/technetwork/topics/dotnet/downloads/install112030-1440546.html
有關EF的訊息
The following items are required for ODAC:
Additional Notes on Requirements:
安裝新版本的ODAC前,如果你之前有安裝過 BEAT版的ODAC for Entity Framework,請務必移除,而有安裝過之前版本的ODAC 也需要移除過後再進行安裝。
再測試之前的程式
廢話不多說,我們用「ASP.NET MVC + Oracle 11g XE + Entity Framework. Part 4」文章中的程式來做測試:
(如果是拿之前使用就版本ODAC所建立的專案來做測試,建議將原建立的EDMX檔案給刪除後再重新建立一次)
public ActionResult Index()
{
ViewBag.Message = "ASP.NET MVC + Oracle 11g XE + Northwind";
using (Entities db = new Entities())
{
var query = from o in db.Orders
orderby o.OrderDate
select new HomeIndexViewModel
{
OrderID = o.OrderID,
CustomerID = o.CustomerID,
OrderDate = o.OrderDate.Value,
Amount = o.OrderDetails.Sum(x => x.UnitPrice * x.Quantity)
};
ViewBag.Orders = query.ToList();
}
return View();
}
抱著期待的心情給它執行下去……
……
……
……
竟然給我出現這似曾相似的錯誤訊息!Orz
檢視詳細資料來仔細查看…
這錯誤還不都跟之前使用BETA版所出現的錯誤是一樣的啊!?
同樣的錯誤,一樣的發生。
ORA-904 “Invalid Identifier” when add a new Entity Model to VS2010 project
http://www.oracle.com/technetwork/topics/dotnet/tech-info/default-338300.html#mozTocId870787
前略..
Solution: Please use a 10.2.0.2 or later database until this is fixed. Because Oracle Database XE 10g is version 10.2.0.1, it cannot currently be used with the EF beta.
問題是我已經用 Oracle Database XE 11g,測試到這裡讓我好無言,總覺得 Oracle 好像只是為了要在 2012 年之前把 EF 支援的功能給納入正式發佈版本,這種看起來如此基本操作的執行卻無法能夠得到正確的結果。
(我目前環境無法使用Oracle Database 11g,有Oracle DB 11g 的朋友可以測試,並提供測試後的結果給我,謝謝)
與使用 MS-SQL 的版本做比較
我們同樣的使用MS-SQL Server中的Northwind,並且使用相同的程式來執行並且做個比較,執行環境使用LINQPad:
執行結果:
轉換的SQL Script:
SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[OrderDate] AS [OrderDate2], (
SELECT SUM([t2].[value])
FROM (
SELECT [t1].[UnitPrice] * (CONVERT(Decimal(29,4),[t1].[Quantity])) AS [value], [t1].[OrderID]
FROM [Order Details] AS [t1]
) AS [t2]
WHERE [t2].[OrderID] = [t0].[OrderID]
) AS [Amount]
FROM [Orders] AS [t0]
ORDER BY [t0].[OrderDate]
而我們同樣拿Oracle for Entity Framework所轉換出來的SQL Script來做個比對:
SELECT
"Project2"."C1" AS "C1",
"Project2"."OrderID" AS "OrderID",
"Project2"."CustomerID" AS "CustomerID",
"Project2"."OrderDate" AS "OrderDate",
"Project2"."C2" AS "C2"
FROM ( SELECT
"Project1"."OrderID" AS "OrderID",
"Project1"."CustomerID" AS "CustomerID",
"Project1"."OrderDate" AS "OrderDate",
1 AS "C1",
"Project1"."C1" AS "C2"
FROM ( SELECT
"Extent1"."OrderID" AS "OrderID",
"Extent1"."CustomerID" AS "CustomerID",
"Extent1"."OrderDate" AS "OrderDate",
(SELECT
SUM("Filter1"."A1") AS "A1"
FROM ( SELECT
"Extent2"."UnitPrice" * ( CAST( "Extent2"."Quantity" AS number(19,0))) AS "A1"
FROM "NORTHWIND_USER"."OrderDetails" "Extent2"
WHERE ("Extent1"."OrderID" = "Extent2"."OrderID")
) "Filter1") AS "C1"
FROM "NORTHWIND_USER"."Orders" "Extent1"
) "Project1"
) "Project2"
ORDER BY "Project2"."OrderDate" ASC
怎麼…Oracle for Entity Framework的轉換卻是變得如此複雜?
不死心的我,同樣在LINQPad中去加入OracleNorthwind的連結來做測試,先測試個簡單的操作,不去操作關連資料的欄位資料運算:
可以知道在LINQPad中對於Oracle for Entity Framework的執行是沒有問題的。(不過在「SQL」頁籤中是無法取得轉換後的SQL Script)
接著就來測試有著關連資料欄位運算的程式:
一樣的結果……
換個操作方式
好吧~既然無法在EF的LINQ Query查詢式中去操作關連資料的欄位運算,那看看可不可以取得關連資料呢?
var query = Orders
.OrderBy(x => x.OrderID)
.Select(x => new { order = x, details = x.OrderDetails });
執行結果:
主要是看是否可以取出關連資料,而依據結果來看是可以正確的取得關連的OrderDetails資料。
所以就把程式改成以下的形式:
1: public ActionResult Another()
2: {3: ViewBag.Message = "Another Way";
4:5: using (Entities db = new Entities())6: {7: var query = db.Orders8: .OrderBy(x => x.OrderID)9: .Select(x => new { order = x, details = x.OrderDetails })
10: .ToList();11:12: var result = from r in query
13: select new HomeIndexViewModel
14: {15: OrderID = r.order.OrderID,16: CustomerID = r.order.CustomerID,17: OrderDate = r.order.OrderDate.HasValue ? r.order.OrderDate.Value : default(DateTime),
18: Amount = r.details.Sum(x => x.Quantity * x.UnitPrice)19: };20:21: ViewBag.Orders = result;22: }23: return View();
24: }25:
應該有人會提出質疑,為何query的查詢式最後還是要ToList()呢?這樣不就會把所有的資料從資料庫給取出來嗎?這是因為如果在Line:12 ~ Line: 19這邊,如果query還是以Queryable類別為Data Collection,到了foreach迴圈中,到了Line: 18 這裡的Sum()運算式還是會發生錯誤,其錯誤與原先最早的程式內容是如出一轍的,
如下圖所示:
所以我們改成在一開始的查詢式,就把Order以及其關連的OrderDetails資料給取出來,接著再以query為Data Collection用foreach迴圈的方式去逐一計算每筆Order資料的OrderDetails的金額總和,
執行結果:
所得到的結果就是我們所要的。
所以得到一個結論,那就是使用Oracle for Entity Framework去下Query運算式時,有時候MS SQL Server環境下可以使用的方法,環境換成Oracle時,就不能依樣畫葫蘆,因為兩者真的不一樣,看看上面那相同的Query運算式所轉換的SQL Script內容,Oracle For Entity Framework就是會變得無比複雜,遇到這樣的情況時,就不要太執著一定非得要用一樣的查詢運算式,可以轉換方式,既然可以取得關連的資料出來,那就用「迂迴」的方式於查詢後再做一次的程式運算,不一定非要每個Query運算式中就要去做最直接的關連資料欄位運算,最後所得到的結果一樣是正確的。
其他的查詢運算操作
join + group
使用 join 以及 group 兩種運算來取得一樣的結果,
var query = from o in Orders
join d in OrderDetails on o.OrderID equals d.OrderID
select new
{
OrderID = o.OrderID,
Amount = d.Quantity * d.UnitPrice
};
var result = from r in query
group r by r.OrderID into g
orderby g.Key
select new
{
OrderID = g.Key,
Amount = g.Sum(x=>x.Amount)
};
執行結果:
Oracle for Entity Framewwork 轉換的SQL Script:
SELECT
"Project1"."C2" AS "C1",
"Project1"."OrderID" AS "OrderID",
"Project1"."C1" AS "C2"
FROM ( SELECT
"GroupBy1"."A1" AS "C1",
"GroupBy1"."K1" AS "OrderID",
1 AS "C2"
FROM ( SELECT
"Extent1"."K1" AS "K1",
SUM("Extent1"."A1") AS "A1"
FROM ( SELECT
"Extent1"."OrderID" AS "K1",
( CAST( "Extent1"."Quantity" AS number(19,0))) * "Extent1"."UnitPrice" AS "A1"
FROM "NORTHWIND_USER"."OrderDetails" "Extent1"
) "Extent1"
GROUP BY "K1"
) "GroupBy1"
) "Project1"
ORDER BY "Project1"."OrderID" ASC
Left Outter Join
執行另一種的查詢運算內容
var query = from c in Categories
join p in Products on c equals p.Categories into ps
from p in ps.DefaultIfEmpty()
select new
{
CategoryID = c.CategoryID,
CategoryName = c.CategoryName,
Description = c.Description,
ProductName = p == null ? "(No products)" : p.ProductName
};
執行結果:
Skip() , Take()
這兩個運算式時常會運用在資料分頁上。
Oracle for EF所轉換的SQL Script:
SELECT *
FROM (
SELECT
"Extent1"."OrderID" AS "OrderID",
"Extent1"."CustomerID" AS "CustomerID",
"Extent1"."EmployeeID" AS "EmployeeID",
"Extent1"."OrderDate" AS "OrderDate",
"Extent1"."RequiredDate" AS "RequiredDate",
"Extent1"."ShippedDate" AS "ShippedDate",
"Extent1"."ShipVia" AS "ShipVia",
"Extent1"."Freight" AS "Freight",
"Extent1"."ShipName" AS "ShipName",
"Extent1"."ShipAddress" AS "ShipAddress",
"Extent1"."ShipCity" AS "ShipCity",
"Extent1"."ShipRegion" AS "ShipRegion",
"Extent1"."ShipPostalCode" AS "ShipPostalCode",
"Extent1"."ShipCountry" AS "ShipCountry"
FROM ( SELECT "Extent1"."OrderID" AS "OrderID", "Extent1"."CustomerID" AS "CustomerID", "Extent1"."EmployeeID" AS "EmployeeID", "Extent1"."OrderDate" AS "OrderDate", "Extent1"."RequiredDate" AS "RequiredDate", "Extent1"."ShippedDate" AS "ShippedDate", "Extent1"."ShipVia" AS "ShipVia", "Extent1"."Freight" AS "Freight", "Extent1"."ShipName" AS "ShipName", "Extent1"."ShipAddress" AS "ShipAddress", "Extent1"."ShipCity" AS "ShipCity", "Extent1"."ShipRegion" AS "ShipRegion", "Extent1"."ShipPostalCode" AS "ShipPostalCode", "Extent1"."ShipCountry" AS "ShipCountry", row_number() OVER (ORDER BY "Extent1"."OrderID" ASC) AS "row_number"
FROM "NORTHWIND_USER"."Orders" "Extent1"
) "Extent1"
WHERE ("Extent1"."row_number" > 20)
ORDER BY "Extent1"."OrderID" ASC
)
WHERE (ROWNUM <= (10) )
寫到這裡已經是本篇文章的最後了,其實目前Oracle for Entity Framework 雖然已經內含在正式發佈的ODAC之中,雖然大部分的功能已經可以正常的執行,但還是有少部分的關聯資料運算還是有問題,我並沒有做太多比較進階的查詢運算式的測試,所以無法下定論說Oracle for Entity Framework是否建議用於專案中,但用於一般中小型的專案應該是綽綽有餘,足以應付大部分的查詢運算,如果是要執行比較複雜資料查詢,還是可以使用Stored Procedure 或是執行SQL Script,因為沒有用過商業版的Oracle to Entity Framework Solution,所以無從比較,但如果是一些第三方開放原始碼的免費套件,我倒是可以說,改使用Oracle原廠發佈的 Oracle for Entity Framework吧!完全整合於Visual Studio的這一個優勢就真的沒得比了。
原本在MS-SQL Server + EF 下的查詢運算式是可以執行的,但是在Oracle for Entity Framework中是未必可行,專案使用Oracle for Entity Framework,查詢運算式要多做嘗試並且不要執著於抹種固定形式,讓想法可以多轉、迂迴一些。
LINQPad - 好用到爆炸、.NET開發人員必備的好用工具
另外真的建議搭配使用「LINQPad」來輔助開發,很多查詢運算式不必在VS2010中去執行才曉得結果,使用「LINQPad」就可以立即知道你所下的LINQ查詢運算式的結果或是執行是否正確。
最後還是要真的建議各位,用這一類的ORM Solution,必須要以物件導向的思考為主,不要以SQL指令的操作方式來思考如何取出資料,如果執著於以SQL指令的方式去篩選資料,只會讓你覺得不對,而這種不對的感覺越來越多時就會在心中對LINQ或是ORM有了疑慮甚至於抗拒、排斥,要想想為什麼程式設計會有那麼多人討論ORM又為何程式設計如此地強調物件導向的觀念。
而LINQ語法並不是只專屬於ORM(LINQ to SQL or Entity Framework)而存在或是專用,LINQ(Language-Integrated Query)之所以會這麼受人重視,主要是它讓程式設計時可以對物件的資料集合去做各種的運算,不熟悉LINQ的人可以去看看,為何使用了ORM技術所開發的專案中很少會看到以往在ADO.NET操作常看到的DataSet, DataTable,因為「只要使用語言關鍵字和您熟悉的運算子,就可以針對強型別的物件集合撰寫查詢」,就是因為強型別,如此在設計期就有了完整的物件型別檢查以及在Visual Studio的Intellisense 支援。
參考資料:
MSDN Developer Samples: Learn Through Code(About LINQ)
MSDN - 101 LINQ Samples
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
MSDN - LINQ (Language-Integrated Query)
http://msdn.microsoft.com/zh-tw/library/bb397926.aspx
Oracle - Entity Framework, LINQ and Model-First for the Oracle Database
以上
沒有留言:
張貼留言