9-7. 在WCF服務中序列化代理問題從一個查詢里返回一個動態代理對象,想要把它序列為一個POCO(Plain-Old CLR Objects)對象.實現基於POCO實體對象, 在運行時,EF會為每個實體自動生成一個派生類型,被稱為動態代理對象,代理對象會為POCO類重載很多虛擬屬性來註入執行操作的...
9-7. 在WCF服務中序列化代理
問題
從一個查詢里返回一個動態代理對象,想要把它序列為一個POCO(Plain-Old CLR Objects)對象.
實現基於POCO實體對象, 在運行時,EF會為每個實體自動生成一個派生類型,被稱為動態代理對象,
代理對象會為POCO類重載很多虛擬屬性來註入執行操作的掛鉤,像變更跟蹤,和延遲載入關聯的實體。
解決方案
假設我們有一個如Figure 9-7.所示的客戶模型
Figure 9-7. 客戶模型
我們將使用ProxyDataContractResolver類在服務端把一個代理對象反序列化為Client的POCO類
1. 創建Wcf服務應用程式.添加一個ADO.NET實體數據模型,並選擇”Client”表,創建好的模型,就如 Figure 9-7.所示.
2.打開Client的 POCO類, 為每個屬性添加virtual關鍵字,如Listing 9-33所示 . 這樣EF就可以創建動態代理類了。
============================================================================================
■■註意:如果你修改EDMX文件,EF會自動重新生成類,會重寫第2步里你對類的修改,你可以再次修改類或是修改T4模板來生成實體代碼。
=======================================================================
Listing 9-33. Our Client POCO Class and Our Object Vontext
public partial class Client
{
public virtual int ClientId { get; set; }
public virtual string Name { get; set; }
public virtual string Email { get; set; }
}
3.我們需要為DataContractSerializer使用ProxyDataContractResolver類為WCF服務的客戶端把client 代理轉換為client實例.為此我們將創建一個操作行為特性 ,並且讓GetClient() 方法使用這個特性。新特性的代碼如 Listing 9-34 所示.註意:ProxyDataContractResolver 類屬於EF命名System.Data.Entity.Core.Objects
Listing 9-34. Our Custom Operation Behavior Attribute
namespace Recipe7
{
public class ApplyProxyDataContractResolverAttribute :
Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description,
BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
DataContractSerializerOperationBehavior
dataContractSerializerOperationBehavior =
operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
}
}
4.用Listing 9-35里的代碼修改IService1.cs 介面
Listing 9-35. Our IService1 Interface Definition, Which Replaces the Code in IService1.cs
[ServiceContract]
public interface IService1
{
[OperationContract]
void InsertTestRecord();
[OperationContract]
Client GetClient();
[OperationContract]
void Update(Client client);
}
5. 用Listing 9-36里的代碼修改Service1.svc.cs 文件來實現服務介面。
Listing 9-36. The Implementation of the IService1 Interface, Which Replaces the Code in IService1.svc.cs
public class Service1 : IService1
{
public void InsertTestRecord()
{
using (var context = new EFRecipesEntities())
{
//刪除之前的測試數據
context.Database.ExecuteSqlCommand("delete from chapter9.client");
//插入新的測試數據
context.Database.ExecuteSqlCommand(@"insert into chapter9.client(name,email)values('Jerry Jones','[email protected]')");
}
}
[ApplyProxyDataContractResolver]
public Client GetClient()
{
using (var context = new EFRecipesEntities())
{
context.Configuration.LazyLoadingEnabled = false;
return context.Clients.Single();
}
}
public void Update(Client client)
{
using (var context = new EFRecipesEntities())
{
context.Entry(client).State = EntityState.Modified;
context.SaveChanges();
}
}
}
6.在解決方案中添加一個新的Windows控制台應用程式,這是我們用來測試的客戶端,代碼如
Listing 9-37所示,添加WCF的引用。
Listing 9-37. Our Windows console application test client
class Program
{
static void Main(string[] args)
{
using (var serviceClient=new ServiceReference1.Service1Client())
{
serviceClient.InsertTestRecord();
var client = serviceClient.GetClient();
Console.WriteLine("Client is :{0} at {1}",client.Name,client.Email);
client.Name = "Alex Park";
client.Email = "[email protected]";
serviceClient.Update(client);
client = serviceClient.GetClient();
Console.WriteLine("Client changed to: {0} at {1}",client.Name, client.Email);
Console.WriteLine("\npress any key to exit...");
Console.ReadKey(true);
}
}
}
以下是控制台輸出結果:
===================================================================
Client is: Jerry Jones at [email protected]
Client changed to: Alex Park at [email protected]
=================================================================================
它是如何工作的?
微軟建議為WCF使用POCO對象,方便序列化實體對象。如果我們的應用程式使用POCO對象,並支持變更通知(把屬性設為virtual and導航對象集合類型為ICollection), EF會為從查詢返回的實體創建動態代理。這裡有兩個關於動態代理和WCF的問題,第一個問題是:必須序列化代理。 而DataContractSerializer 只能序列化和反序列化已知的類型,例如我們例子中的Client實體.然而 EF為Client 實體自動生成一個動態代理類,我們需要序列化這個代理類, 而不是 Client類,DataContractResolver就是解決這個問題的. 它能在序列化期間把一個類型映射到另一個類型. ProxyDataContractResolver 來源於DataContractResolver 並映射代理類型到 POCO類, 例如我們的 Client 實體. 為了使用ProxyDataContractResolver, 我們創建特性 (見 Listing 9-34) 來解決代理轉換成POCO類.我們在GetClient()方法上應用這個特性 (見Listing 9-36). 這樣Client實體的動態代理能正確的序列化,並被GetClient() 返回給WCF服務的調用者。第二個問題:必須處理延遲載入的問題.當 DataContractSerializer 序列化實體時, 它訪問實體的每個屬性,這會觸發延遲載入導航屬性. 這當然不是我們所希望的,所以我們要關閉延遲載入,如Listing 9-36所示。