LINQ To SQL
與 N-Tier應用程式
文
/
黃忠成
(原刊登於RUN! PC)
從發表第一篇LINQ 首部曲到現在,轉眼間LINQ系列已來到第八篇了,這是我繼2000年IntraWeb以來,於Run! PC中連載次數最多的文章了。在這長達十個月的期間,我們由最基礎的LINQ To Objects,談到了LINQ To XML及LINQ To DataSet的擴充應用,來到了LINQ To SQL的ORM世界,最後將於LINQ To SQL的實務應用中劃下句點。
當初,LINQ之所以會吸引我目光的原因有二,一是其與語言整合的奇想,讓我覺得相當的新鮮且有創意,二則是ORM的誘惑,多年來,我一直對ORM有著相當高的興趣,從研究ORM概念到使用ORM產品,乃至於自行開發ORM Framework,對於這個技術,我有著深入的研究及無限的渴望。
做為Microsoft第一個公開的ORM實作體,LINQ To SQL不管是於效率還是易用性上,都有著不俗的表現,但在N-Tier應用上,LINQ To SQL卻一直處於相當不明確的狀態,
即使到了Visual Studio 2008正式上市的今天,仍然沒有一個完整的文件及範例,告訴我們如何將LINQ To SQL應用於N-Tier系統架構中,本系列文章將以一個小型的N-Tier範例做為結尾,為讀者們演示,如何將LINQ To SQL應用於以WPF做為UI層的N-Tier系統架構上。
LINQ To SQL
的N-Tier應用
在開始討論LINQ To SQL與N-Tier之前,我們得先定位出什麼是N-Tier應用程式,傳統的N-Tier應用程式架構如圖1所示。
圖1
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/1.jpg)
說穿了,N-Tier系統架構就是於傳統的Client/Server架構中添加一台架設應用程式伺服器的電腦,做為橋接Client端與Server端(也就是架設資料庫系統的那台電腦)的中介者。那為何要這麼做呢?主要原因有兩個,一是擴充性的考量:在傳統的Client/Server架構中,Server端必須負擔服務所有客戶端的重責大任,一旦客戶端增多,Server端電腦的等級也必須隨之提升,否則效能會因大量的客戶端而降低,架設中介伺服器可以分擔原本由Server端一手包辦的工作。
在面對大量使用者同時操作系統的情況下,中介伺服器可以快取部份常用的資料,以暫存方式來取代真正的資料庫操作,進而減輕Server端的負擔。在N-Tier架構中,中介伺服器並不僅限於一台電腦,這意味著我們可以架設多台中介伺服器,然後讓Client端連往主控的中介伺服器,再由此中介伺服器決定該Client端是要連往那一台中介伺服器,這也是N-Tier用語中常見的『負載平衡』。
二是安全性的考量:原本在Client/Server架構中,Client端是直接與Server端溝通的,這意味著如果應用程式需要透過網際網路來連結Server時,該Server就必須曝露於網際網路上才行,但這個Server端通常是一個架設資料庫系統的電腦,將整個公司重要的資料曝露於眾人可及的網路上,相信沒有幾家公司有這個勇氣或膽識吧。
藉助於中介伺服器的架設,可讓後端資料庫免除於曝露於網路上的危機。而Client端與中介伺服器間的溝通立基於有限的通訊規格,所以即使中介伺服器需曝露於網際網路上,有心人士透過中介伺服器來傷害後端資料庫的機會及層次也降低了不少,何況通訊加密是所有N-Tier應用程式必須實作的部份,比起簡單的資料庫通訊協定,專為N-Tier應用程式所設計的安全通訊協定如Web Services,自然安全許多。
.NET Framework 3.5
下的N-Tier
當決定採用N-Tier架構時,Client端與中介伺服器間的通訊協定便成了第一個得做出的選擇,從.NET Framework 3.0開始,Microsoft便致力於打造新一代可用於N-Tier架構的通訊協定:Windows Communication Foundation,簡稱WCF。在原始的設計概念中,WCF必須解決兩個問題,一是效率的問題,以往的N-Tier程式可分成兩大陣營,一是使用公開的標準規格:Web Services做為通訊協定,二是使用封閉的規格:Remoting。由於可使用Binary(二進位)協定來封裝訊息,所以Remoting在效能上有著相當耀眼的表現,但Remoting是封閉的協定,所以失去了與其它平台互通的可能性。使用Web Services雖然可以得到了平台互通的特色,但卻必須以效率做為交換條件。
WCF
在訊息封裝層做了一個抽象化的設計,可以讓WCF應用程式於執行時期切換使用的通訊協定,這也就是說,當在網路環境良好,且沒有平台互通性考量的情況下,我們可以在不重新編譯應用程式的狀態下,僅修改組態檔就能將架構於WCF的應用程式之通訊層換成Binary格式,當需要互通性時,也只要修改組態檔就能改為Web Services格式,此項設計讓WCF應用程式可以輕鬆遊走於兩種通訊協定間,不像以往般,一旦選擇了Remoting後,要改成Web Services就很難了。
WCF
出現的第二個目的是要實作更多的Web Services規格,這幾年來Web Services的規格鋪天蓋地的出現,能支援更多的規格代表著與其它平台的互通性也就越高,WCF中支援了如WS-Security、WS-ReliableMessage、WS-Coordation、WS-Address等新規格。因此,在.NET Framework 3.5中,不管是依據效能或是互通性的考量,WCF都是最好的選擇。
圖2 .NET Framework 3.5的N-Tier架構
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/2.jpg)
Entity Object
的序列化
談完了系統架構後,現在讓我們回到主軸上,如何將LINQ To SQL應用於N-Tier應用程式架構中,在這個階段首要必須解決的問題是,LINQ To SQL的Entity Object能否透過Web Service、Remoting、WCF來傳遞?
答案是肯定的,不支援這個的話,LINQ To SQL就幾乎沒有存在的價值了。不過這有個小技巧,在預設設定中,LINQ To SQL Designer所產生出來的Entity Class是不支援序列化的,你必須在LINQ To SQL Designer中調整Serialization Mode屬性來要求其產生可序列化的Entity Class。
圖3
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/3.jpg)
在設定Serialization Mode為Unidirectional後,所產生的Entity Class便會標上傳統物件序列化所須的[Serializable]及WCF所須的[DataContrast]等兩個Attribute,有了這兩個Attribute後,Entity Object便可傳遞於Remoting、Web Services、WCF等通訊協定。
中介伺服器端的實作
用WCF來實作中介伺服器是一件相當輕鬆的事,首先請建立一個WCF Service Application專案。
圖4
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/4.jpg)
然後添加一個LINQ To SQL Classes項目於此專案中,本例於此添加了北風資料庫的Customers資料表。
圖5
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/5.jpg)
接著刪除自動產生的IService1.cs,於Service1.svc.cs中鍵入程式1的程式碼。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Runtime.Serialization;
using
System.ServiceModel;
using
System.Text;
namespace
WCFDataService
{
[ServiceContract]
public interface IDataService
{
[OperationContract]
Customer[] GetAllData();
}
public class DataService : IDataService
{
#region
IDataService Members
public Customer[] GetAllData()
{
NorthwindDataContext context = new NorthwindDataContext();
return (from s1 in context.Customers select s1).ToArray();
}
#endregion
}
}
|
最後調整app.config中關於Service的設定後,便完成了此一執行於中介伺服器上的WCF Service的建構工作。
WPF
客戶端的實作
那如何在WPF客戶端使用這個WCF Service呢?如同以往使用Web Service般,我們可以透過Visual Studio 2008來引用此WCF Service。
圖6
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/6.jpg)
就像以往的Add Web Reference功能一樣,此精靈會引導我們取得WCF Service,然後產生出Proxy Class。
圖7
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/7.jpg)
取得Proxy Class後,接下來就是建構UI的工作了,這裡我使用與前幾期WPF同樣的UI畫面,稍加調整程式碼,透過WCF Service來取得資料。
程式2
private
void Window_Loaded(object sender, RoutedEventArgs e)
{
var result = (from s1 in client.GetAllData() orderby s1.CustomerID select s1).ToList();
bindingList = new BindingList<DataService.Customer>(
(IList<DataService.Customer>)result);
DataContext = bindingList;
view = CollectionViewSource.GetDefaultView(DataContext);
}
|
執行結果如圖8。
圖8
![](http://www.dreams.idv.tw/~code6421/files/LINQBG/8.jpg)
Tracking Changes
機制?
OK
,我們能夠順利的由WCF Service取得資料,那麼接下來的更新動作該如何做呢?,照 MSDN中『順帶一提』的說明中,要於N-Tier情況下實作更新功能必須視UI層而定,於ASP.NET中可透過ObjectDataSource控件的協助完成,但在WPF、Windows Form中,程式設計師則必須自行實作Client端的Tracking Changes機制。
那什麼是Tracking Changes機制呢?簡單的說,更新一筆資料需要有兩個資訊,一是Entity Object的現值,一則是Entity Object的原始值,在更新資料時,我們必須將這兩個資訊送達WCF Service,然後由WCF Service依據原值來取得欲更新的資料列後,將現值更新進去。
問題在,LINQ To SQL Designer只是將Entity Class標示為可序列化,並未產生出任何的Tracking Changes所需要的程式碼,這也就是說!在WPF端時,我們必須於資料列更新時,將原值先記錄下來,否則就無法透過WCF Service來更新該筆資料了。更確切的說,就是少了一個類似DataSet之GetChanges函式的機制。
那該如何實作這個機制呢?很幸運的,LINQ To SQL Designer所產生出的Entity Class實作了INotifyPropertyChanging介面,因此我們可掛載事件至其所定義的PropertyChanging事件中,於物件屬性值改變時,事先將原值記錄下來,完成Tracking Changes機制。
但也很不幸的,透過WCF Service所產生的Proxy Class忽略了此介面,並沒有產生出對應的程式碼,所以透過INotifyPropertyChanging介面來實作Tracking Changes的想法是不可能達到的。
退而求其次,我們只能以Context的概念來實作Tracking Changes,也就是說於取得物件的同時,將所有物件複製一份,將原值保留下來,然後透過另一介面INotifyPropertyChanged來偵測物件是否已被改變。
程式3
using
System;
using
System.ComponentModel;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Reflection;
namespace
WpfDataConsumer
{
public class TrackingContext<T,TUpdate>
{
……………
.
public void Initialize(IList<T> objs)
{
_states.Clear();
_update_original = typeof(TUpdate).GetProperty("Original");
_update_current = typeof(TUpdate).GetProperty("Current");
_update_state = typeof(TUpdate).GetProperty("State");
foreach (T item in objs)
{
object updateData = Activator.CreateInstance(typeof(TUpdate), false);
_update_original.SetValue(updateData, CloneObject(item),null);
_update_current.SetValue(updateData, item, null);
_update_state.SetValue(updateData,
WpfDataConsumer.DataService.UpdateState.UnChanged, null);
((INotifyPropertyChanged)item).PropertyChanged +=
new
PropertyChangedEventHandler(TrackingContext_PropertyChanged);
_states.Add(item, (TUpdate)updateData);
}
}
void
TrackingContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_states.ContainsKey((T)sender))
{
object o = _states[(T)sender];
DataService.UpdateState state =
(DataService.UpdateState)_update_state.GetValue(o, null);
if (state == WpfDataConsumer.DataService.UpdateState.Insert)
return;
_update_state.SetValue(o,
WpfDataConsumer.DataService.UpdateState.Update, null);
}
}
}
}
|
實作Server端的Update機制
有了Tracking Changes機制的協助後,WCF Service端可以獲得欲更新資料物件的原值與現值,要更新資料就不難了。
程式4
public
void UpdateData(UpdateData<Customer>[] updatedData)
{
NorthwindDataContext context = new NorthwindDataContext();
try
{
foreach (UpdateData<Customer> item in updatedData)
{
if (item.State == UpdateState.Insert)
context.Customers.InsertOnSubmit((Customer)item.Current);
}
foreach (UpdateData<Customer> item in updatedData)
{
if (item.State == UpdateState.Update)
context.Customers.Attach((Customer)item.Current, (Customer)item.Original);
}
foreach (UpdateData<Customer> item in updatedData)
{
if (item.State == UpdateState.Delete)
{
context.Customers.Attach((Customer)item.Current);
context.Customers.DeleteOnSubmit((Customer)item.Current);
}
}
context.SubmitChanges();
}
finally
{
context.Dispose();
}
}
|
下一期,ADO.NET Entity Framework
在這一期文章中,我給了各位讀者們一個於LINQ To SQL中使用N-Tier架構,並以WPF做為UI層的範例,這個範例的程式碼不多,但是其中的概念卻是相當的複雜,在這個範例中應用到了以Context概念實作Tracking Changes,以泛型作為讓Trancking Changes可以適用於多類型物件的解法,要想於一篇文章中將其完全解釋是不可能的,如果讀者們對此種架構有需求或是興趣,請下載範例後以除錯模式觀察,必能得箇中巧妙之處。
這也是LINQ系列的最後一篇文章,全系列的LINQ文章完整版多數已收錄於我在四月即將出版的新書中,下一期我們將跳出LINQ的框框,與讀者們一起討論Microsoft另一個ORM技術:ADO.NET Entity Framework,別誤會!我們依然還在LINQ的勢力範圍裡,因為!ADO.NET Entity Framework依然支援以LINQ來查詢物件,也就是說你還是會見到LINQ啦,下期再見了。
參考書目:
次世代
.NET
資料庫開發聖典
ASP.NET
篇
–
黃忠成
(
四月份出版
)
Microsoft Windows Communication Foundation
新一代應用程式通訊架構
–
李漢宗
範例下載: