前言
早在2005年微軟的程式開發者會議上(PDC),就揭露了LINQ這個新技術,當時就引起了一些開發人員的側目,果然在VS2008產品中發表了這個顛覆傳統的新語法,隨著微軟在媒體的高度曝光下,LINQ預計將成為新下一代.NET架構的標準之一。
在過去,不論是Java或.NET都沒有把資料查詢語言標準納入程式語言當中,LINQ則企圖把查詢語言整合成程式語言的一種標準,直接由開發工具去支援這種查詢語法,進而縮短程式開發與除錯時間,例如,以前程式開發人員透過VB或C#來撰寫查詢資料程式時,會將SQL語法寫到程式當中,再送往資料庫中進行查詢,但這樣經常會因為語法有錯或Table或欄位不存在而出現了錯誤,而且要Run的時候才會知道是否有錯,最終影響了程式開發的效率。
而LINQ從程式語言層面來解決ORM(Object Relative Mapping)與查詢語言的問題,除了可以應用在關聯式資料庫與XML查詢以外,還保留了延伸到其他資料形式的可能性,其中包括Object與目錄等等。文本將討論LINQ的基本概念及LINQ to SQL的基礎應用實例,希望能讓各位對LINQ有個初淺的認識。
何謂LINQ
LINQ是Language Integrated Query的簡稱,它是整合在.NET程式語言中的一種新的語法,已成為程式語言的一部分,可在編寫程式時得到編譯器準確的語法檢查與編譯,因此LINQ為一系列語言延伸模組,可以型別安全的方式支援資料查詢的功能。LINQ由鼎鼎大名 Anders Hejlsberg 所創,Turbo Pascal、Delphi、C#等叫好又叫座的產品,都是出自這位大師手裡,可見LINQ極為可能成為下一代資料存取語言的主流。
別以為LINQ只是一種資料庫的查詢語言,它不僅是資料庫的查詢語言,而且未來可以做到隔絕資料庫的特性,就是不管資料庫廠商為何其語法皆會一致,甚至連XML的 DOM、XQuery或是目錄或檔案(XPath),還有各種物件集合的屬性存取等等,都可以使用LINQ共同的方式來操作其資料,這樣就可以讓開發人員以一致性的語法來操作這些不同類型的物件與資料格式,可以大幅縮短程式開發的學習與維護的門檻,不但可以讓程式碼得以縮小,並可讓程式碼較為一致性。
LINQ除了為一個強型別語法之外,LINQ查詢也有許多標準的查詢運算子可以強化其功能,這些標準查詢運算子會操作序列,讓你執行各項作業,例如,判斷序列中是否有某個值和執行序列彙總函數 (例如加總)等等。
LINQ架構
下圖為Microsoft LINQ的架構圖,我們簡單將其說明如下:
LINQ包括五個部分:LINQ to Objects、LINQ to DataSets、LINQ to SQL、LINQ to Entities、LINQ to XML。
LINQ to Objects:提供對各種集合類型(IEnumerable)的存取操作,如對List/Array等集合類型來直接操作。如:
var query = from Student student in arrList
where student.Scores[0] > 95
select student;
(arrList為一個ArrayList的陣列)
LINQ to SQL:基於關聯式資料庫的.NET語言整合查詢,專用於以物件形式來管理MS SQL資料庫,並提供了豐富的查詢功能。這種查詢方式與原來的SQL語法較為近似,此類的應用也特別廣泛,是LINQ的主力。如:
var q =
from c in Customers
where c.City == "London"
select c;
(Customers為資料庫的TableName)
LINQ to DataSets:與LINQ to SQL類似,不同的是,此操作對象為一個DataSet,代表為一個離線的記憶體資料表(DataSet)。如:
IEnumerable<DataRow> query =
from product in products.AsEnumerable()
select product;
(products為一個DataSet, product為一個Table Name)。
LINQ to Entities:與LINQ to SQL近似,差別的是LINQ to SQL為服務SQL Server的關聯式資料庫,而LINQ to Entities則為服務SQL Server之外的關聯式資料庫實體,作為其他資料庫產品所提供的LINQ相容性擴展介面。
LINQ to XML:可用來實現對XML的操作,可以更為高效、易用的方式來存取記憶體中的XML,它提供文件物件模型 (DOM) 的記憶體中文件修改能力,而且支援 LINQ 查詢運算式,雖然這些查詢運算式在語法上與 XPath 不同,但是它們利用更強型別的方式,提供類似的功能。如:
IEnumerable<string> partNos =
from item in purchaseOrder.Descendants("Item")
select (string) item.Attribute("PartNumber");
(取PurchaseOrder.XML資料)
配合LINQ的C#擴充
為了配合LINQ新的擴展,在VS2008中,必須增加一些新的功能來配合,如下:
1、隱含類型區域變數(Local Variable Type Inference)
C#3.0(在VS2008中為3.0),引進了var這個新關鍵字,在宣告區域變數時可用來取代原先的類型,即當一 個變數宣告為var類型並且該範圍區域中沒有var名稱類型存在時,那麼這個宣告就稱為隱含類型區域變數。如:
// C# 3.0以前
string s = "infolight";
// C# 3.0
var s = "infolight";
這種變數有下列幾個特性:
(1) var為關鍵字:可以根據後面的初始化語句自動推斷類型,這個類型為強類型。
(2) 用var定義的變數一定必須初始化,一旦初始化之後,只可以存儲這種類型。如:
var s; // 錯誤
var s = null; // 錯誤
var s = "infolight";
s = 22; // 錯誤,s初始化為string,不能存int
(3) var宣告僅限於區域變數,不可用於欄位,但可以用於for,foreach,using等語句中。
string[] ss = new string[]{"a", "b"};
foreach (var s in ss)
{
}
(4) 陣列也可以作為隱含類型,如:
var s = new string[]{"a", "b"};
2、Lambda運算式
Lambda運算式允許我們使用一種更接近人的思維、更自然的方式來實現類似於匿名方法同樣的效果,它
可以包含運算式和陳述式 (Statement)兩種。我們可以用下面的例子來說明Lambda:
public partial class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
List<Blog> blogs = new List<Blog>{
new Blog(Id=1, Name="linq簡介", Author="infolight"),
new Blog(Id=2, Name="eep2006簡介", Author="infolight"),
};
// 查詢作者為infolight的blog,代碼更加簡單同時更符合自然語言習慣。
var blog = blogs.Where(blog => blog.Author == "infolight").ToList();
Lambda運算式格式說明如下: (參數列表)=>運算式或陳述式
左方的參數列表:代表傳入參數的定義,這個參數將傳入右方的運算式來運算,可以定義多個。 右方的運算式或陳述式:代表在Lambda所對應的對象(如DataSet、XML、Table、ArrayList、目錄等等),所要執行的運算式或陳述式,好回應一個或多個結果。
如:
blog => blog.Author == "infolight"
blog代表參數,blog.Author 必須為”infolight”的內容
Lambda運算式參數型別可以是隱式型別(不定型別)或顯式型別(定義好型別)。在顯式列表中,每個參數的型別是事先指定的,在隱式列表中,參數的型別由Lambda運算式出現的那時再自動推斷型別。
Lambda運算式的參數列表可以有一個或多個參數,或者無參數,而且在單一的隱式型別參數的Lambda運算式中,括號是可以被省略的,以下我們列出幾個Lambda的實例:
(x, y) => x * y; //多參數,隱式型別=>運算式,
x => x * 10;//單參數,隱式型別=>運算式
x => { return x * 10; }; //單參數,隱式型別=>陳述式
(int x) => x * 10;//單參數,顯式型別=>運算式
(int x) => { return x * 10; };//單參數,顯式型別=>陳述式
() => Console.WriteLine(); //無參數
LINQ to SQL開發實例
接著,我們用VS2008,建立一個實例來說明LINQ to SQL的具體應用。
1. 建立一個新的WebSite,並在資料庫TEST中建立一個新的Blog資料表,Blob其定義為:
USE [test]
GO
/****** Object: Table [dbo].[Blog] Script Date: 04/17/2008 14:14:30 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Blog](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](255) NULL,
[Author] [nvarchar](255) NULL,
PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
2 .在WebSite中,去新增一個項目,我們選擇”LINQ to SQL”這個Templates,這樣在VS2008的設計環境上會增加一個dbml檔(Database Mark Language,資料庫描述語言,是一種xml格式的文檔,用來描述資料庫),如圖:
3. 使用VS2008的工具/Server Explorer將Blob這個資料表拖放到dbml的設計器上,並將其保存,如圖。
4. 此時,VS2008會自動產生Blog這個Class與BlogDataContext Class,並自動建立和Blog資料表的映射關係,Blog
class是Blog資料表的一個映射類別,BlogDataContext class是從System.Data.Linq.DataContext上繼承下來的,
DataContext 是透過資料庫連接而對應之所有實體的來源,它可以追蹤或紀錄你對所有已擷取實體所做的操
作與變更(具有Log的功能),一般來說,DataContext用於把LINQ查詢句法真正翻譯成SQL語句,以及把資料
從資料庫返回給LINQ的對象或讓LINQ去執行資料庫實體的新增修改與刪除,因此,DataContext是實體和資
料庫之間的橋樑。另外,DataContext 十分精簡,且建立的效能與空間都不浪費,一般的 LINQ to SQL 應用
程式都會以類別成員來建立DataContext 的執行個體,代表相關資料庫作業的一組邏輯並代為操作。
上圖中,Blog.designer.cs中自動生成了兩個類,分別是BlogDataContext和Blog,程式如下:
// 定義映射的資料庫為test
[System.Data.Linq.Mapping.DatabaseAttribute(Name="test")]
public partial class BlogDataContext : System.Data.Linq.DataContext
{
…
// 定義Blog類映射dbo.Blog數據表
[Table(Name="dbo.Blog")]
public partial class Blog : INotifyPropertyChanging, INotifyPropertyChanged
{
…
// 定義Blog類的Name屬性映射dbo.Blog資料表中的Name欄位
[Column(Storage="_Name", DbType="NVarChar(255)")]
public string Name
{
…
}
…
}
5. 開發一個新增的頁面,我們可以在Default的Page中,貼入兩個TextBox元件,並貼一個Button來做為新增Blog資料的功能,如下圖:
接著,在Button1上的Click事件中,寫以下程式:
protected void Button1_Click(object sender, EventArgs e)
{
// 創建一個Blog類的實例
Blog blog = new Blog();
blog.Name = TextBox1.Text;
blog.Author = TextBox2.Text;
// 創建DataContext
BlogDataContext blogDataContext = new BlogDataContext();
// 向DataContext緩存中添加創建的blog
blogDataContext.Blogs.InsertOnSubmit(blog);
// DataContext緩存提交到資料庫中
blogDataContext.SubmitChanges();
}
你是否發現,這樣的語句,我們並沒有使用任何SQL語句,而且程式也很精簡。
6. 接著我們同樣在Default的Page上,貼入另一個TextBox3,同樣貼入一個Button2來做為查詢的按鈕,如下圖,我們將透過Lambda運算式來達到查詢的功能。
在Button2下設計Click的事件:
protected void Button2_Click(object sender, EventArgs e)
{
BlogDataContext blogDataContext = new BlogDataContext();
// 第一種 LINQ的語法,找出 Blog.Name為 TextBox3.Text的作者
var blogsQuery = blogDataContext.Blogs.Where(blog => blog.Name.Contains(TextBox3.Text.Trim()));
// 另一種LINQ的語法,與上面可達到同樣的結果
var blogsQuery = from blogs in blogDataContext.Blogs
where (blogs.Name.Contains(TextBox3.Text.Trim()))
select blogs;
GridView2.DataSource = blogsQuery; //將查詢結果設定給GridView2.DataSource
GridView2.DataBind();
}
不管使用第一種與第二種LINQ的語法,同樣都可以讓語法很精簡,並讓開發的工作變得更輕省。
7. 執行結果,如下圖:
LINQ to SQL的利弊
LINQ to SQL和通用的ORM框架一樣,有自己的優點也存在很多的不足,筆者以自己個人觀點分享如下。
優點:
1、隱藏了資料訪問細節,“封閉”了與資料庫密切的交互,它使得我們與資料庫交互變得更簡單,並且完全不用考慮到SQL語句,尤其是前端的UI確實可以與後端的Table與Column分離,可做到完全無關。
2、可以方便在不同版本的資料庫上完成遷移,通用的ORM框架能提供多種不同資料庫的Provider,遷移資料庫只要修改資料庫連接字串即可。傳統的SQL語言相對複雜很多而且標準不一,雖然SQL有標準,但實際上每家資料庫廠商都有自己的SQL差異,使用不同的資料庫就要學習不同的SQL語法。雖然現在LINQ還沒有Oracle的Provider,但是應該很快的就會有Oracle/Infomix/Mysql等等廠商開發相對的Provider。
3、LINQ to SQL更加符合物件導向的開發方式,我們可以從上面的例子中看到在LINQ中輕易以 Lambda來使用物件來結合語句,你也可說是等於進階版的SQL語句,語法將會讓開發人員可以更容易閱讀與維護,開發人員可以花更多的時間來關注業務邏輯。
4、LINQ to SQL可以使用Lambda運算式,查詢資料更加方便和容易,開發的程式碼更精簡,開發效能更快。
缺點:
1、無可避免的,因為有DataContext在中間對應和關聯管理的關係,代價就是犧牲少許性能。但可讓LINQ to SQL延時載入,這樣就可讓性能提升一些。
2、對於複雜Query查詢,LINQ to SQL還是有些力不從心,這方面還是有待各專家往下努力,畢竟LINQ才剛出世,還有一段路程要走。
3、LINQ to SQL不支援批量刪除,雖然我們也可以直接下delete table where …這樣的SQL來快速刪除資料,但是出現這樣的SQL語句在程式中,畢竟不是件很協調的程式,慢慢可以看到網路上已經有了對LINQ to SQL進行功能的擴展,以支援批量操作。
4、LINQ透過DataContext不易分層來管理,DataContext在創立的同時,必須要有資料庫連線,就好像是之前的2-Tier架構,只是我們將SQL語法改成LINQ的語法而已,如果像EEP這種N-Tier架構上,LINQ是很難分層(將U/I層與商業邏輯層分離),面對現今已經大肆流行的SOA框架,如果只靠LinqDataSource這個單純的Web元件來中介Web元件與DataContext間的溝通是無法滿足需求的。
結語
LINQ這樣說來,已經打穿了資料查詢與程式語言間難以溝通的屏障,那從此以後,SQL真的就會煙消雲散的被取代了嗎?當然答案是NO,因為LINQ的推出並沒有要取代SQL的企圖,但是對於一個程式開發的初學者來說,LINQ將可以讓其變得更加容易學習而降低入門門檻,並對程式開發的品質與效率也會有顯著的改善,如果對於資深的程式開發人員來說,SQL的撰寫仍是必備的開發行為,畢竟要改變一個資深人員的開發習慣並不是一件容易的事,必須要花一點時間來適應並改變觀念。
隨著LINQ的氣勢高漲,我們也立即將這個革命性的架構放入在EEP2008產品中,並解決最困難的LINQ分層問題,分層不只是Tier與Service的分層,更要是跨Server的分層,這將是我們另一個全新的挑戰,我們將以另一編文章來專門介紹EEP2008的LINQ架構與其功能,並讓所有EEP的客戶能盡早享用LINQ所帶來的方便與應用。(完) |