接上文 【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”
我們再次信心滿滿的修改多項目啟動,運行……服務端與客戶端都如願正常啟動了:

但是,客戶端按任意鍵執行調用的時候……客戶端發生了如下異常:
View Code
捕捉到 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 }
再次運行,報如下異常:
View Code
捕捉到 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
客戶端發送的XElement數據
<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>
服務端接收的XElement數據
<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序列化的問題了,可是……某日突然報了這么個異常:
View Code
捕捉到 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
