【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)的时候引发了异常:
捕捉到 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 这根救命稻草
有路了就要往下走,后面还会遇到什么问题,且听下回分解……
最后,把本文的源代码发上来以供参考,注意:为了保持现场,这个源码包的程序并不能运行
作者:郭明锋
Q群:MVC EF技术交流(5008599) OSharp开发框架交流(85895249)
出处:https://www.cnblogs.com/guomingfeng
声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。