【Expression 序列化】WCF的簡單使用及其Expression Lambada的序列化問題初步解決方案(一)


在園子里混跡多年,始終保持着“只看帖不回帖”的習慣,看了很多,學了很多,卻從不敢寫些東西貼出來,一來沒什么可寫的,二來水平不夠,怕誤人子弟……

最近在做一個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 這根救命稻草

有路了就要往下走,后面還會遇到什么問題,且聽下回分解……

 最后,把本文的源代碼發上來以供參考,注意:為了保持現場,這個源碼包的程序並不能運行

LambadaSerializeDemo01.rar




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM