在園子里混跡多年,始終保持着“只看帖不回帖”的習慣,看了很多,學了很多,卻從不敢寫些東西貼出來,一來沒什么可寫的,二來水平不夠,怕誤人子弟……
最近在做一個MVC+WCF+EF的項目,遇到問題不少,但大多數問題都是前人遇到並解決了的,感謝園子里的大牛們的無私奉獻。
俗話說“禮尚往來”,我也在此分享一個最近在項目中遇到的問題,就是遠程調用時的Expression表達式的序列化問題的初始解決方案,希望拋出的這塊石頭能引出完美的鑽石來,同時第一次寫博客,請大家多多賜教……
為了說明問題,我將用一個簡單的示例來演示,文章的最后會有示例的源代碼下載。
- 示例說明:
演示項目還是使用傳統的四層結構:

WCF服務契約:契約很簡單,一個Member類的Model,一個通過表達式來查找Member信息的GetMember(Expression<Func<Member, bool>>)方法
1 using System; 2 using System.Linq.Expressions; 3 using System.Runtime.Serialization; 4 using System.ServiceModel; 5 6 namespace Liuliu.Wcf.IContract 7 { 8 [ServiceContract] 9 public interface IAccountContract 10 { 11 [OperationContract] 12 Member GetMember(Expression<Func<Member, bool>> predicate); 13 } 14 15 [DataContract] 16 public class Member 17 { 18 [DataMember] 19 public int MemberID { get; set; } 20 21 [DataMember] 22 public string UserName { get; set; } 23 24 [DataMember] 25 public string Email { get; set; } 26 } 27 }
為簡化客戶端的調用操作,在Contracts項目中添加一個WcfHelper輔助類
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.ServiceModel; 5 using System.Text; 6 7 namespace Liuliu.Wcf.IContract.Helper 8 { 9 public class WcfHelper 10 { 11 public static TReturn InvokeService<TContract, TReturn>(Func<TContract, TReturn> func) 12 { 13 var channelFactory = new ChannelFactory<TContract>("*"); 14 var proxy = channelFactory.CreateChannel(); 15 var iproxy = proxy as ICommunicationObject; 16 if (iproxy == null) 17 { 18 throw new ArgumentException("執行遠程方法時服務契約代理協議為空。"); 19 } 20 try 21 { 22 iproxy.Open(); 23 var result = func(proxy); 24 iproxy.Close(); 25 return result; 26 } 27 catch (CommunicationException) 28 { 29 iproxy.Abort(); 30 throw; 31 } 32 catch (TimeoutException) 33 { 34 iproxy.Abort(); 35 throw; 36 } 37 catch (Exception) 38 { 39 iproxy.Close(); 40 throw; 41 } 42 } 43 } 44 }
服務的實現也非常簡單,就是從數據源DataSource中按條件查找相關信息並返回
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Linq.Expressions; 5 using Liuliu.Wcf.IContract; 6 7 namespace Liuliu.Wcf.Services 8 { 9 public class AccountService : IAccountContract 10 { 11 private readonly static List<Member> DataSource = new List<Member> 12 { 13 new Member {MemberID = 3, UserName = "zhangsan", Email = "zhangsan@abc.com"}, 14 new Member {MemberID = 4, UserName = "lisi", Email = "lisi@abc.com"}, 15 new Member {MemberID = 5, UserName = "wangwu", Email = "wangwu@abc.com"}, 16 new Member {MemberID = 6, UserName = "zhaoliu", Email = "zhaoliu@abc.com"} 17 }; 18 19 public Member GetMember(Expression<Func<Member, bool>> predicate) 20 { 21 return DataSource.SingleOrDefault(predicate.Compile()); 22 } 23 } 24 }
服務端以一個控制台的程序來承載,第一次寫博客,可以力求完美一點^_^
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.ServiceModel; 5 6 using Liuliu.Wcf.Services; 7 8 namespace Liuliu.Wcf.Hosting 9 { 10 class Program 11 { 12 private static readonly List<ServiceHost> OpenedHosts = new List<ServiceHost>(); 13 14 private static void Main(string[] args) 15 { 16 try 17 { 18 var accountHost = new ServiceHost(typeof(AccountService)); 19 var hosts = new List<ServiceHost> { accountHost }; 20 CreateHosting(hosts); 21 OpenHosting(hosts); 22 Console.WriteLine("按任意鍵關閉服務。"); 23 Console.ReadKey(); 24 CloseHosting(OpenedHosts); 25 26 } 27 catch (Exception e) 28 { 29 Console.WriteLine(e); 30 Console.ReadLine(); 31 } 32 } 33 34 private static void OpenHosting(IEnumerable<ServiceHost> hosts) 35 { 36 foreach (var host in hosts) 37 { 38 try 39 { 40 host.Open(); 41 if (!OpenedHosts.Contains(host)) 42 { 43 OpenedHosts.Add(host); 44 } 45 } 46 catch (Exception) 47 { 48 foreach (var openedHost in OpenedHosts) 49 { 50 openedHost.Close(); 51 } 52 throw; 53 } 54 } 55 } 56 57 private static void CreateHosting(IEnumerable<ServiceHost> hosts) 58 { 59 hosts.ToList().ForEach(host => 60 { 61 host.Opened += host_Opened; 62 host.Closed += host_Closed; 63 host.UnknownMessageReceived += host_UnknownMessageReceived; 64 }); 65 } 66 67 private static void CloseHosting(IEnumerable<ServiceHost> hosts) 68 { 69 hosts.ToList().ForEach(host => host.Close()); 70 } 71 72 static void host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e) 73 { 74 var host = (ServiceHost)sender; 75 Console.WriteLine("{0}收到未知消息。", host.Description.ConfigurationName); 76 } 77 78 static void host_Closed(object sender, EventArgs e) 79 { 80 var host = (ServiceHost)sender; 81 Console.WriteLine("{0}已成功關閉。", host.Description.ConfigurationName); 82 } 83 84 static void host_Opened(object sender, EventArgs e) 85 { 86 var host = (ServiceHost)sender; 87 Console.WriteLine("{0}服務啟動完畢\n監聽地址:{1}", host.Description.ConfigurationName, host.Description.Endpoints.First().ListenUri); 88 } 89 } 90 }
當然現在服務端還不能啟動,還需要必要的配置信息,在Hosting項目中添加一個App.Config來進行配置,可以通過VS2010中菜單“工具” - “WCF服務配置編輯器” 來進行可視化添加
為了簡化配置,這里不進行調用的驗證了,設置 clientCredentialType="None"
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <netTcpBinding> <binding name="NewBinding0"> <security> <message clientCredentialType="None" /> </security> </binding> </netTcpBinding> </bindings> <services> <service name="Liuliu.Wcf.Services.AccountService"> <endpoint address="net.tcp://127.0.0.1:9000/account" binding="netTcpBinding" bindingConfiguration="NewBinding0" contract="Liuliu.Wcf.IContract.IAccountContract" /> </service> </services> </system.serviceModel> </configuration>
有了前面的WcfHelper的輔助,客戶端調用就非常愜意了,由UserName查找Member信息並顯示結果的Email信息
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Linq.Expressions; 5 using System.Text; 6 7 using Liuliu.Wcf.IContract; 8 using Liuliu.Wcf.IContract.Helper; 9 10 namespace Liuliu.Wcf.Client 11 { 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 Expression<Func<Member, bool>> predicate = m => m.UserName == "張三"; 17 var result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(predicate)); 18 Console.WriteLine(result.Email); 19 } 20 } 21 }
當然,還要客戶端的配置信息,由“WCF服務配置編輯器”工具可直接由上面配置的服務端配置來生成客戶端配置
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <netTcpBinding> <binding name="NewBinding0"> <security> <message clientCredentialType="None" /> </security> </binding> </netTcpBinding> </bindings> <client> <endpoint address="net.tcp://127.0.0.1:9000/account" binding="netTcpBinding" bindingConfiguration="NewBinding0" contract="Liuliu.Wcf.IContract.IAccountContract" name="" kind="" endpointConfiguration="" /> </client> </system.serviceModel> </configuration>
至此,演示項目的基本架構搭建完畢,理論上運行的話應該能順利啟動,右鍵解決方案 - 設置啟動項目,設置啟動為多項目啟動:

當我們信心滿滿的點下啟動按鍵之后,結果卻不是我們所期待的,服務端在執行到OpenHosting(hosts)的時候引發了異常:
View Code
捕捉到 System.Runtime.Serialization.InvalidDataContractException
Message=無法序列化類型“System.Linq.Expressions.Expression`1[System.Func`2[Liuliu.Wcf.IContract.Member,System.Boolean]]”。請考慮將其標以 DataContractAttribute 特性,並將其所有要序列化的成員標以 DataMemberAttribute 特性。如果類型為集合,則請考慮將其標以 CollectionDataContractAttribute 特性。有關其他受支持的類型,請參見 Microsoft .NET Framework 文檔。
Source=System.Runtime.Serialization
StackTrace:
在 System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type)
在 System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)
在 System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)
在 System.Runtime.Serialization.XsdDataContractExporter.GetSchemaTypeName(Type type)
在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.ValidateDataContractType(Type type)
在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.CreatePartInfo(MessagePartDescription part, OperationFormatStyle style, DataContractSerializerOperationBehavior serializerFactory)
在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.CreateMessageInfo(DataContractFormatAttribute dataContractFormatAttribute, MessageDescription messageDescription, DataContractSerializerOperationBehavior serializerFactory)
在 System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter..ctor(OperationDescription description, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory)
在 System.ServiceModel.Description.DataContractSerializerOperationBehavior.GetFormatter(OperationDescription operation, Boolean& formatRequest, Boolean& formatReply, Boolean isProxy)
在 System.ServiceModel.Description.DataContractSerializerOperationBehavior.System.ServiceModel.Description.IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
在 System.ServiceModel.Description.DispatcherBuilder.BindOperations(ContractDescription contract, ClientRuntime proxy, DispatchRuntime dispatch)
在 System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
在 System.ServiceModel.ServiceHostBase.InitializeRuntime()
在 System.ServiceModel.ServiceHostBase.OnBeginOpen()
在 System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
在 System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
在 System.ServiceModel.Channels.CommunicationObject.Open()
在 Liuliu.Wcf.Hosting.Program.OpenHosting(IEnumerable`1 hosts) 位置 D:\我的文檔\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Hosting\Program.cs:行號 52
在 Liuliu.Wcf.Hosting.Program.Main(String[] args) 位置 D:\我的文檔\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Hosting\Program.cs:行號 21
InnerException:
由異常信息我們可以很明確的看到,System.Linq.Expressions.Expression類不支持序列化操作,以Expression作為查詢條件的參數傳遞是行不通的。
此路不通,我們只能另辟蹊徑了
以“Expression”,“序列化”,“Lambada”等為關鍵字百度Google了一把,終於還是收獲不少,找到了Expression Tree Serializer 這根救命稻草
有路了就要往下走,后面還會遇到什么問題,且聽下回分解……
最后,把本文的源代碼發上來以供參考,注意:為了保持現場,這個源碼包的程序並不能運行
