接上文 【Expression 序列化】WCF的简单使用及其Expression Lambada的序列化问题初步解决方案(一)
上文留下了一个问题没有处理,但最后也找到了相应的解决方案,下面就来说下问题的解决
Expression Tree Serializer 提供的解决方案是 把Expression表达式树转换为XElement类型的XML数据,传输到服务端,再反转换还原成原来的Expression表达式
所以,客户端与服务端之间传送的数据是XElement类型的数据了,从而避开了Expression类型不能序列化的问题
我们先来了解一下Expression Tree Serializer的使用,下载 Expression Tree Serializer 源代码进行编译,只需要用到其中的ExpressionSerialization.dll
新建一个控制台项目,添加ExpressionSerialization引用。先来试用一下ExpressionSerialization对Expression的处理
把Main方法修改为如下代码:
1 static void Main(string[] args) 2 { 3 var sources = new[] { "abc", "abd", "bcd", "acd", "cdb" }; 4 Expression<Func<string, bool>> predicate = m => m.StartsWith("a"); 5 Console.WriteLine("使用原始Expression表达式查询数据:"); 6 Console.WriteLine(predicate); 7 sources.Where(predicate.Compile()).ToList().ForEach(s => Console.Write(s + " ")); 8 Console.WriteLine(); 9 var serializer = new ExpressionSerializer(); 10 var xmlPredicate = serializer.Serialize(predicate); 11 var newPredicate = serializer.Deserialize<Func<string, bool>>(xmlPredicate); 12 Console.WriteLine("使用新Expression表达式查询数据:"); 13 Console.WriteLine(newPredicate); 14 sources.Where(newPredicate.Compile()).ToList().ForEach(s => Console.Write(s + " ")); 15 Console.ReadLine(); 16 }
注意:如提示无法生成请将控制台项目的“目标框架”由原来的“.NET Framework 4 Client Profie”修改为“.NET Framework 4”
运行控制台项目,得到如下结果,与预期一致:
有了成功的第一步,就敢大踏步往下走了,马上对上一文中的代码进行手术
对Services与Client项目分别添加ExpressionSerialization引用
WCF服务契约代码修改为如下:
1 [ServiceContract] 2 public interface IAccountContract 3 { 4 [OperationContract] 5 Member GetMember(XElement xmlPredicate); 6 }
WCF服务实现代码修改如下,为了在客户端显示服务端发生的异常信息,在服务实现类添加[ServiceBehavior(IncludeExceptionDetailInFaults = true)]特性:
1 [ServiceBehavior(IncludeExceptionDetailInFaults = true)] 2 public class AccountService : IAccountContract 3 { 4 private readonly static List<Member> DataSource = new List<Member> 5 { 6 new Member {MemberID = 3, UserName = "zhangsan", Email = "zhangsan@abc.com"}, 7 new Member {MemberID = 4, UserName = "lisi", Email = "lisi@abc.com"}, 8 new Member {MemberID = 5, UserName = "wangwu", Email = "wangwu@abc.com"}, 9 new Member {MemberID = 6, UserName = "zhaoliu", Email = "zhaoliu@abc.com"} 10 }; 11 12 public Member GetMember(XElement xmlPredicate) 13 { 14 var serializer = new ExpressionSerializer(); 15 var predicate = serializer.Deserialize<Func<Member, bool>>(xmlPredicate); 16 return DataSource.SingleOrDefault(predicate.Compile()); 17 } 18 }
客户端代码修改如下:
1 static void Main(string[] args) 2 { 3 Console.WriteLine("按任意建执行客户端调用:"); 4 Console.ReadLine(); 5 try 6 { 7 Expression<Func<Member, bool>> predicate = m => m.UserName == "zhangsan"; 8 var serializer = new ExpressionSerializer(); 9 var xmlPredicate = serializer.Serialize(predicate); 10 var result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate)); 11 Console.WriteLine(result.Email); 12 } 13 catch (Exception e) 14 { 15 Console.WriteLine(e); 16 } 17 Console.ReadLine(); 18 }
再次注意:如提示无法生成请将控制台项目的“目标框架”由原来的“.NET Framework 4 Client Profie”修改为“.NET Framework 4”
我们再次信心满满的修改多项目启动,运行……服务端与客户端都如愿正常启动了:
但是,客户端按任意键执行调用的时候……客户端发生了如下异常:

捕捉到 System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail> Message=Could not find a matching type 参数名: Liuliu.Wcf.IContract.Member Source=mscorlib Action=http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault StackTrace: Server stack trace: 在 System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter) 在 System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs) 在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 在 System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown at [0]: 在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) 在 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) 在 Liuliu.Wcf.IContract.IAccountContract.GetMember(XElement xmlPredicate) 在 Liuliu.Wcf.Client.Program.<>c__DisplayClass1.<Main>b__0(IAccountContract wcf) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Client\Program.cs:行号 25 在 Liuliu.Wcf.IContract.Helper.WcfHelper.InvokeService[TContract,TReturn](Func`2 func) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.IContract\Helper\WcfHelper.cs:行号 30 在 Liuliu.Wcf.Client.Program.Main(String[] args) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Client\Program.cs:行号 25 InnerException:
Liuliu.Wcf.IContract.Member类无法识别?!从客户端传过去的是数据是XElement的XML数据,因而ExpressionSerializer在进行反序列化操作的时候不知道有Liuliu.Wcf.IContract.Member这个类,知道大概也是只知其名而已
那就手动让ExpressionSerializer去详细了解Liuliu.Wcf.IContract.Member吧,万般查找发现了KnownTypeExpressionXmlConverter这个类,看名就知道,就是干这个使的。
修改服务实现代码如下:
1 public Member GetMember(XElement xmlPredicate) 2 { 3 var assemblies = new List<Assembly> {typeof(Member).Assembly, typeof(ExpressionType).Assembly, typeof(IQueryable).Assembly}; 4 var resolver = new TypeResolver(assemblies, new[] {typeof(Member)}); 5 var knownTypeConverter = new KnownTypeExpressionXmlConverter(resolver); 6 var serializer = new ExpressionSerializer(resolver, new CustomExpressionXmlConverter[] { knownTypeConverter }); 7 var predicate = serializer.Deserialize<Func<Member, bool>>(xmlPredicate); 8 return DataSource.SingleOrDefault(predicate.Compile()); 9 }
客户端代码修改如下:
1 static void Main(string[] args) 2 { 3 Console.WriteLine("按任意建执行客户端调用:"); 4 Console.ReadLine(); 5 try 6 { 7 Expression<Func<Member, bool>> predicate = m => m.UserName == "zhangsan"; 8 var assemblies = new List<Assembly> { typeof(Member).Assembly, typeof(ExpressionType).Assembly, typeof(IQueryable).Assembly }; 9 var resolver = new TypeResolver(assemblies, new[] { typeof(Member) }); 10 var knownTypeConverter = new KnownTypeExpressionXmlConverter(resolver); 11 var serializer = new ExpressionSerializer(resolver, new CustomExpressionXmlConverter[] { knownTypeConverter }); 12 var xmlPredicate = serializer.Serialize(predicate); 13 var result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate)); 14 Console.WriteLine(result.Email); 15 } 16 catch (Exception e) 17 { 18 Console.WriteLine(e); 19 } 20 Console.ReadLine(); 21 }
再次运行,报如下异常:

捕捉到 System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail> Message=序列不包含任何元素 Source=mscorlib Action=http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault StackTrace: Server stack trace: 在 System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter) 在 System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs) 在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 在 System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown at [0]: 在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) 在 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) 在 Liuliu.Wcf.IContract.IAccountContract.GetMember(XElement xmlPredicate) 在 Liuliu.Wcf.Client.Program.<>c__DisplayClass2.<Main>b__1(IAccountContract wcf) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Client\Program.cs:行号 29 在 Liuliu.Wcf.IContract.Helper.WcfHelper.InvokeService[TContract,TReturn](Func`2 func) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.IContract\Helper\WcfHelper.cs:行号 30 在 Liuliu.Wcf.Client.Program.Main(String[] args) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Client\Program.cs:行号 29 InnerException:
又查找了半天,没发现原因,想想会不会是数据的发送与接收不一致了,加了个在客户端发送前与服务端接收后都把相应的XElement数据写入文本文件中,一对比,居然真的不一样,吭爹啊……
2.12KB VS 2.23KB

<LambdaExpression NodeType="Lambda" Name="" TailCall="false" CanReduce="false"> <Type> <Type Name="System.Func`2"> <Type Name="Liuliu.Wcf.IContract.Member" /> <Type Name="System.Boolean" /> </Type> </Type> <Parameters> <ParameterExpression NodeType="Parameter" Name="m" IsByRef="false" CanReduce="false"> <Type> <Type Name="Liuliu.Wcf.IContract.Member" /> </Type> </ParameterExpression> </Parameters> <Body> <BinaryExpression CanReduce="false" IsLifted="false" IsLiftedToNull="false" NodeType="Equal"> <Right> <ConstantExpression NodeType="Constant" CanReduce="false"> <Type> <Type Name="System.String" /> </Type> <Value>zhangsan</Value> </ConstantExpression> </Right> <Left> <MemberExpression NodeType="MemberAccess" CanReduce="false"> <Member MemberType="Property" PropertyName="UserName"> <DeclaringType> <Type Name="Liuliu.Wcf.IContract.Member" /> </DeclaringType> <IndexParameters /> </Member> <Expression> <ParameterExpression NodeType="Parameter" Name="m" IsByRef="false" CanReduce="false"> <Type> <Type Name="Liuliu.Wcf.IContract.Member" /> </Type> </ParameterExpression> </Expression> <Type> <Type Name="System.String" /> </Type> </MemberExpression> </Left> <Method MemberType="Method" MethodName="op_Equality"> <DeclaringType> <Type Name="System.String" /> </DeclaringType> <Parameters> <Type> <Type Name="System.String" /> </Type> <Type> <Type Name="System.String" /> </Type> </Parameters> <GenericArgTypes /> </Method> <Conversion /> <Type> <Type Name="System.Boolean" /> </Type> </BinaryExpression> </Body> <ReturnType> <Type Name="System.Boolean" /> </ReturnType> </LambdaExpression>

<LambdaExpression NodeType="Lambda" Name="" TailCall="false" CanReduce="false" xmlns=""> <Type> <Type Name="System.Func`2"> <Type Name="Liuliu.Wcf.IContract.Member"></Type> <Type Name="System.Boolean"></Type> </Type> </Type> <Parameters> <ParameterExpression NodeType="Parameter" Name="m" IsByRef="false" CanReduce="false"> <Type> <Type Name="Liuliu.Wcf.IContract.Member"></Type> </Type> </ParameterExpression> </Parameters> <Body> <BinaryExpression CanReduce="false" IsLifted="false" IsLiftedToNull="false" NodeType="Equal"> <Right> <ConstantExpression NodeType="Constant" CanReduce="false"> <Type> <Type Name="System.String"></Type> </Type> <Value>zhangsan</Value> </ConstantExpression> </Right> <Left> <MemberExpression NodeType="MemberAccess" CanReduce="false"> <Member MemberType="Property" PropertyName="UserName"> <DeclaringType> <Type Name="Liuliu.Wcf.IContract.Member"></Type> </DeclaringType> <IndexParameters></IndexParameters> </Member> <Expression> <ParameterExpression NodeType="Parameter" Name="m" IsByRef="false" CanReduce="false"> <Type> <Type Name="Liuliu.Wcf.IContract.Member"></Type> </Type> </ParameterExpression> </Expression> <Type> <Type Name="System.String"></Type> </Type> </MemberExpression> </Left> <Method MemberType="Method" MethodName="op_Equality"> <DeclaringType> <Type Name="System.String"></Type> </DeclaringType> <Parameters> <Type> <Type Name="System.String"></Type> </Type> <Type> <Type Name="System.String"></Type> </Type> </Parameters> <GenericArgTypes></GenericArgTypes> </Method> <Conversion></Conversion> <Type> <Type Name="System.Boolean"></Type> </Type> </BinaryExpression> </Body> <ReturnType> <Type Name="System.Boolean"></Type> </ReturnType> </LambdaExpression>
对比以上数据可发现,
所有的空标签发送时表示为<empty />的形式,而在接收后表示为<empty></empty>的形式,而ExpressionSerialization并没有对这种情况进行处理
找到的问题所在,就能把问题解决掉
将ExpressionSerialization工程的ExpressionSerializer(Deserialize).cs 文件中的“if (xml.IsEmpty)”语句替换为“if (xml.IsEmpty || !xml.Elements().Any())”,共有3处,然后重新编译ExpressionSerialization,添加引用
再次运行程序,终于得到期待已久的结果了:
现在可以高歌:红军不怕远征难,万水千山只等闲……
目前为此基本上解决了Expression序列化的问题了,可是……某日突然报了这么个异常:

捕捉到 System.ServiceModel.FaultException<System.ServiceModel.ExceptionDetail> Message=值不能为 null。 参数名: typeName Source=mscorlib Action=http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault StackTrace: Server stack trace: 在 System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter) 在 System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs) 在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 在 System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown at [0]: 在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) 在 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) 在 Liuliu.Wcf.IContract.IAccountContract.GetMember(XElement xmlPredicate) 在 Liuliu.Wcf.Client.Program.<>c__DisplayClass4.<Main>b__2(IAccountContract wcf) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Client\Program.cs:行号 37 在 Liuliu.Wcf.IContract.Helper.WcfHelper.InvokeService[TContract,TReturn](Func`2 func) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.IContract\Helper\WcfHelper.cs:行号 30 在 Liuliu.Wcf.Client.Program.Main(String[] args) 位置 D:\我的文档\Visual Studio 2010\Projects\LambadaSerializeDemo\Liuliu.Wcf.Client\Program.cs:行号 37 InnerException:
而发生异常的场景是这样的,在客户端代码添加了一个输入字符串作为查询条件的一部分(17行):
客户端代码:
1 static void Main(string[] args) 2 { 3 Console.WriteLine("按任意建执行客户端调用:"); 4 Console.ReadLine(); 5 try 6 { 7 Expression<Func<Member, bool>> predicate = m => m.UserName == "zhangsan"; 8 Console.WriteLine(predicate); 9 var assemblies = new List<Assembly> { typeof(Member).Assembly, typeof(ExpressionType).Assembly, typeof(IQueryable).Assembly }; 10 var resolver = new TypeResolver(assemblies, new[] { typeof(Member) }); 11 var knownTypeConverter = new KnownTypeExpressionXmlConverter(resolver); 12 var serializer = new ExpressionSerializer(resolver, new CustomExpressionXmlConverter[] { knownTypeConverter }); 13 var xmlPredicate = serializer.Serialize(predicate); 14 var result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate)); 15 Console.WriteLine(result.Email); 16 17 var input = Console.ReadLine(); 18 if (!string.IsNullOrEmpty(input)) 19 { 20 predicate = m => m.UserName == input; 21 Console.WriteLine(predicate); 22 xmlPredicate = serializer.Serialize(predicate); 23 result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate)); 24 Console.WriteLine(result.Email); 25 } 26 } 27 catch (Exception e) 28 { 29 Console.WriteLine(e); 30 } 31 Console.ReadLine(); 32 }
这样一个很正常的需求,居然又引出了一个大问题,详情请听下回分解^_^
本文源代码下载:LambadaSerializeDemo02.rar