Programming WCF Services:資料契約版本控制

myattitude發表於2008-07-09

隨著版本的不斷演化,客戶端與服務端的資料契約可能會出現版本不一致的情況。在WCF中,關於資料契約的版本控制有兩種情況:新增成員與缺失成員。新增成員是指傳送方包含了新增成員,預設處理方式為忽略新增成員。缺失成員則是指傳送方缺少成員,預設處理方式是為缺失成員賦予其預設值。

在缺失成員的情況下,如果僅僅是為缺少的成員賦予預設值,有時候會出現無法預料的錯誤。原因在於缺失的成員有可能是正確執行操作的必要條件。為了避免出現這樣的情況,可以將缺失的成員設定為必備成員,方法是利用DataMember特性的IsRequired屬性,將其值設定為true。例如:

[DataContract]   
struct Contact 
{
[DataMember]
public string FirstName; [DataMember]
public
string LastName;
[DataMember(IsRequired =
true)] public string Address;
}

如果訊息中的成員被標記為必備成員,當接收端的DataContractSerializer無法找到所需的資訊進行反序列化時,就會取消這次呼叫,傳送端會引發NetDispatcherFaultException異常。例如,服務端的資料契約如上的定義,其中Address欄位為必備成員,而客戶端的資料契約則如下所示:

[DataContract]   
struct Contact
{
[DataMember]
public string FirstName; [DataMember]
public string LastName;
}

此時,如果客戶端向服務發出呼叫,則由於引發了異常,該呼叫就不會到達服務。

客戶端和服務都能夠將它們的資料契約中的部分或所有資料成員標記為必備,彼此之間是完全獨立的。被標記為必備的成員越多,則與服務或客戶端之間的互動就越安全,但這卻是以犧牲靈活性與版本相容性為代價的。

本書總結了資料契約版本控制的幾種情形,並以表顯示了必備成員的版本相容性:

IsRequired V1 to V2 V2 to V1
False Yes Yes
True No Yes

假定V2包含了V1的所有資料成員,同時還定義了新增成員。則表涵蓋了版本的幾種情況。

V1到V2:代表了缺失成員的情況。如果IsRequired為false,則互動正常,對於缺失成員則設定為預設值。如果IsRequired為true,就會丟擲異常,訊息不能正常傳送。

V2到V1:代表了新增成員的情況。不管IsRequired的值為true還是false,WCF均以忽略新成員的方式進行互動,互動正常。

WCF對於一些特殊的資料型別,支援仍然不夠。這在一定程度上限制了CLR開發人員對WCF的設計。這些特殊的資料型別包括:列舉、委託、DataSet和DataTable、泛型、集合。

列舉

WCF對列舉的支援還算不錯。首先,列舉型別自身是支援序列化的。不需要設定任何特性,列舉型別的所有成員都會是資料契約的一部分。如果,列舉型別中只有一部分成員需要成為資料契約的一部分,就需要用到DataContract與EnumMember特性。例如:

[DataContract] 
enum ContactType
{
[EnumMember] Customer,
[EnumMember]
Vendor,
//Will not be part of data contract Partner
}

客戶端生成的表示形式則為: 

 enum ContactType
{
Customer,
Vendor
}

委託

WCF對委託以及事件的支援都不夠好。這是因為委託的內部呼叫列表的具體結構是本地的,客戶端或服務無法跨服務邊界共享委託列表的結構。此外,我們不能保證內部列表中的目標物件都是可序列化的,或者都是有效的資料契約。這會導致序列化的操作時而成功,時而失敗。因此,最佳實踐是不要將委託成員或事件作為資料契約的一部分。

資料集與資料表

DataSet和DataTable型別是可序列化的,因而我們可以在服務契約中接收或返回資料表或資料集。

如果服務契約使用了DataSet和DataTable型別,生成的代理檔案不會直接使用DataSet和DataTable型別,而是包含DataTable資料契約的定義(只包含DataTable的樣式,而不包含任何程式碼)。但我們可以手工修改這些定義。例如這樣的服務契約: 

[ServiceContract()]   
public interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
void AddContacts(DataTable contacts); [OperationContract]
DataTable GetContacts();
}

那麼生成的代理檔案可能會是這樣: 

public interface IContactManager 
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IContactManager/AddContact", ReplyAction="http://tempuri.org/IContactManager/AddContactResponse")] [System.ServiceModel.XmlSerializerFormatAttribute()]
void AddContact(Contact contact); [System.ServiceModel.OperationContractAttribute(Action=
"http://tempuri.org/IContactManager/AddContacts", ReplyAction="http://tempuri.org/IContactManager/AddContactsResponse")] [System.ServiceModel.XmlSerializerFormatAttribute()] AddContactsResponse AddContacts(AddContactsRequest request); [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IContactManager/GetContacts", ReplyAction="http://tempuri.org/IContactManager/GetContactsResponse")] [System.ServiceModel.XmlSerializerFormatAttribute()] GetContactsResponse GetContacts(GetContactsRequest request);
}
代理類的定義則如下所示:
[System.Diagnostics.DebuggerStepThroughAttribute()] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]

public
partial class ContactManagerClient : System.ServiceModel.ClientBase, IContactManager
{

//其餘成員略;
public
void AddContact(Contact contact)
{
base.Channel.AddContact(contact); }
AddContactsResponse IContactManager.AddContacts(AddContactsRequest request) {

return
base.Channel.AddContacts(request);
}

public
void AddContacts(AddContactsContacts contacts)
{
AddContactsRequest inValue =
new AddContactsRequest(); inValue.contacts = contacts;
AddContactsResponse retVal = ((IContactManager)(
this)).AddContacts(inValue);
}
GetContactsResponse IContactManager.GetContacts(GetContactsRequest request) {

return
base.Channel.GetContacts(request);
}

public
GetContactsResponseGetContactsResult GetContacts()
{
GetContactsRequest inValue =
new GetContactsRequest();
GetContactsResponse retVal = ((IContactManager)(
this)).GetContacts(inValue); return retVal.GetContactsResult;
}
}

我們可以手動將AddContacts()與GetContacts()方法修改為: 

 public  void AddContacts(DataTable contacts) 
{
AddContactsRequest inValue =
new AddContactsRequest();
inValue.contacts = contacts;
AddContactsResponse retVal = ((IContactManager)(
this)).AddContacts(inValue); }
public
DataTable GetContacts()
{
GetContactsRequest inValue =
new GetContactsRequest();
GetContactsResponse retVal = ((IContactManager)(
this)).GetContacts(inValue); return retVal.GetContactsResult;
}

當然,前提條件是我們需要修改AddContactRequest類以及GetContactsResponse,例如將AddContactRequest類的contacts成員由原來的AddContactsContacts型別修改為DataTable型別;將GetContactsResponse中的GetContactsResult成員由原來的GetContactsResponseGetContactsResult型別修改為DataTable型別。

自動生成的代理類非常複雜,實際上我們完全可以簡化。首先將客戶端的服務契約定義修改為與服務端服務契約完全一致的定義: 

 [ServiceContract()]   
public interface IContactManager 
{
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
void AddContacts(DataTable contacts); [OperationContract] DataTable GetContacts();
}

然後修改代理類ContactManagerClient:

修改後執行的結果完全相同。

注意,DataRow型別是不能序列化的。

在WCF中,還可以使用DataTable和DataSet的型別安全的子類。書中也給出了相應的例子。然而,WCF的最佳實踐則是避免使用DataTable和DataSet,以及使用DataTable和DataSet的型別安全的子類。書中闡釋了原因:
“對於WCF的客戶端與服務而言,雖然可以通過ADO.NET和Visual Studio工具使用DataSet、DataTable以及它們的型別安全的派生物件,但這種方式過於繁瑣。而且,這些資料訪問型別都是特定的.NET型別。在序列化時,它們生成的資料契約樣式過於複雜,很難與其它平臺進行互動。在服務契約中使用資料表或者資料集還存在一個缺陷,那就是它可能暴露內部的資料結構。同時,將來對資料庫樣式的修改會影響到客戶端。雖然在應用程式內部可以傳遞資料表,但如果是跨越應用程式或公有的服務邊界傳送資料表,卻並非一個好的主意。通常情況下,更好的做法是暴露資料的操作而非資料本身。”

最好的做法是將DataTable轉換為陣列型別。書中提供了DataTableHelper類,可以幫助將DataTable轉換為陣列型別。

泛型

非常遺憾,我們並不能在資料契約中定義泛型。但是,WCF使用了一個折中的辦法,使得我們可以在服務端照常使用泛型,但在生成的資料契約定義時,泛型會被具體的型別所取代,重新命名的格式為:
Of

WCF還支援將自定義型別作為泛型引數。此外,還可以通過資料契約的Name屬性為匯出的資料契約指定不同的名字。例如,如下的服務端資料契約:

如下的服務端資料契約:

 [DataContract]   
class SomeClass
{...}
[DataContract(Name = "MyClass")]

class
MyClass
{...}
[OperationContract]
void MyMethod(MyClass obj);

匯出的資料契約為:

[DataContract]   
class SomeClass 
{...}
[DataContract]
class MyClass
{...}
[OperationContract]
void MyMethod(MyClass obj);

集合

WCF支援泛型集合、定製集合,但與傳統的.NET程式設計不一樣,WCF對集合的操作存在許多約束。對於這些約束,本書描述得非常清楚。本文不再贅述。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14780914/viewspace-374777/,如需轉載,請註明出處,否則將追究法律責任。

相關文章