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


 

接上文 【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


免責聲明!

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



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