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


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

  上文最后留下了一個問題,引起這個問題的操作是把原來通過硬編碼字符串來設置的Expression參數改為接收用戶輸入。這是個非常正常的需求,可以說如果這個問題不解決,上文的Expression序列化的方法是無法應用到實際項目中的。下面來分析異常引起的原因。

  首先,來查看一下接收輸入來組裝的Expression與硬編碼的方式生成有什么不同:

 1 private static void Method02()
 2 {
 3     Expression<Func<Member, bool>> predicate = m => m.UserName == "zhangsan";
 4     Console.WriteLine("硬編碼生成的表達式:\n"+predicate);
 5     Console.WriteLine("------------------------華麗的分割線--------------------------");
 6 
 7     var input = Console.ReadLine();
 8     predicate = m => m.UserName == input;
 9     Console.WriteLine("接收輸入生成的表達式:\n"+predicate);
10 }

 

執行程序,輸入“zhangsan”,運行的結果:

  可以看出,接收輸入生成的Expression表達式確實長得很奇異,這也解釋了為什么報異常的原因。因為“Liuliu.TestConsole.Program+<>c__DisplayClass0”這個類是客戶端運行時生成的,而服務端在對傳過去的XElement進行反序列化時,無法識別出這個類型,自然就報錯了。

  怎么解決呢?通過上文中提到的KnownTypeExpressionXmlConverter類把這個類傳給服務端讓它變成已知類型?顯然這是做不到的,因為Liuliu.TestConsole.Program+<>c__DisplayClass0這個類是運行時生成的,在運行之前根本不存在。看來此路不通。

  看來只有一條路了,那就是讓輸入參數input與客戶端分離,解除對客戶端程序的依賴。NuGet上面正好有一個叫“Dynamic Expression API”的開源組件能解決這個問題,添加引用到項目中,把上面的測試代碼修改如下:

 1 private static void Method02()
 2 {
 3     Expression<Func<Member, bool>> predicate = m => m.UserName == "zhangsan";
 4     Console.WriteLine("硬編碼生成的表達式:\n"+predicate);
 5     Console.WriteLine("------------------------華麗的分割線--------------------------");
 6 
 7     var input = Console.ReadLine();
 8     predicate = m => m.UserName == input;
 9     Console.WriteLine("接收輸入生成的表達式:\n"+predicate);
10     Console.WriteLine("------------------------華麗的分割線--------------------------");
11 
12     input = Console.ReadLine();
13     predicate = System.Linq.Dynamic.DynamicExpression.ParseLambda<Member, bool>("UserName=@0", input);
14     Console.WriteLine("Dynamic Expression API生成的表達式:\n" + predicate);
15 }

 

由13行可以看到,Dynamic Expression API 是使用字符串拼接的方式來生成Expression的,雖然看起來似乎是歷史的倒退,但確實解除了input對客戶端的依賴,看運行結果:

可以看到通過Dynamic Expression API生成的Expression與硬編碼生成的完全一致,回到了原生的形式,這樣,理論上把問題解決了。

  回到我們的WCF,把WCF客戶端代碼修改如下,只需要把原來的20行修改為21行:

 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             predicate = System.Linq.Dynamic.DynamicExpression.ParseLambda<Member, bool>("UserName=@0", input);
22             Console.WriteLine(predicate);
23             xmlPredicate = serializer.Serialize(predicate);
24             result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate));
25             Console.WriteLine(result.Email);
26         }
27     }
28     catch (Exception e)
29     {
30         Console.WriteLine(e);
31     }
32     Console.ReadLine();
33 }

 

運行結果與預期一致:

我們的演示項目經過幾次的大手術,已經變得面目全非了,作為一個完美主義的程序員,這是不可容忍的。所以,對項目進行重構是必然的步驟。

1.客戶端Client與服務實現Services中都存在 ExpressionSerializer 對象實例化的相同代碼,而客戶端與服務實現的一個相同點就是都引用了服務契約Contracts

所以,應該把這部分重復代碼重構進Contracts中。

2.為了避免每個使用到Dynamic Expression API的地方都要對其進行引用,也應該把Dynamic Expression API封裝到Contracts中。

我們把以上兩個重構點封裝成一個SerializeHelper靜態類,使用的時候就直接調用即可。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Linq.Expressions;
 5 using System.Reflection;
 6 using System.Xml.Linq;
 7 
 8 using ExpressionSerialization;
 9 
10 
11 namespace Liuliu.Wcf.IContract.Helper
12 {
13     public static class SerializeHelper
14     {
15 
16         public static Expression<Func<T, TS>> CreateExpression<T, TS>(string expression, params object[] values)
17         {
18             if (expression == null)
19             {
20                 throw new ArgumentNullException("expression");
21             }
22             return System.Linq.Dynamic.DynamicExpression.ParseLambda<T, TS>(expression, values);
23         }
24 
25         public static XElement SerializeExpression(Expression predicate, IEnumerable<Type> knownTypes = null)
26         {
27             if (predicate == null)
28             {
29                 throw new ArgumentNullException("predicate");
30             }
31             var serializer = CreateSerializer(knownTypes);
32             return serializer.Serialize(predicate);
33         }
34 
35         public static XElement SerializeExpression<T, TS>(Expression<Func<T, TS>> predicate)
36         {
37             if (predicate == null)
38             {
39                 throw new ArgumentNullException("predicate");
40             }
41             var knownTypes = new List<Type> { typeof(T) };
42             var serializer = CreateSerializer(knownTypes);
43             return serializer.Serialize(predicate);
44         }
45 
46         public static Expression DeserializeExpression(XElement xmlExpression)
47         {
48             if (xmlExpression == null)
49             {
50                 throw new ArgumentNullException("xmlExpression");
51             }
52             var serializer = CreateSerializer();
53             return serializer.Deserialize(xmlExpression);
54         }
55 
56         public static Expression<Func<T, TS>> DeserializeExpression<T, TS>(XElement xmlExpression)
57         {
58             if (xmlExpression == null)
59             {
60                 throw new ArgumentNullException("xmlExpression");
61             }
62             var knownTypes = new List<Type> { typeof(T) };
63             var serializer = CreateSerializer(knownTypes);
64             return serializer.Deserialize<Func<T, TS>>(xmlExpression);
65         }
66 
67         public static Expression<Func<T, TS>> DeserializeExpression<T, TS>(XElement xmlExpression, IEnumerable<Type> knownTypes)
68         {
69             if (xmlExpression == null)
70             {
71                 throw new ArgumentNullException("xmlExpression");
72             }
73             var serializer = CreateSerializer(knownTypes);
74             return serializer.Deserialize<Func<T, TS>>(xmlExpression);
75         }
76 
77         private static ExpressionSerializer CreateSerializer(IEnumerable<Type> knownTypes = null)
78         {
79             if (knownTypes == null || !knownTypes.Any())
80             {
81                 return new ExpressionSerializer();
82             }
83             var assemblies = new List<Assembly> { typeof(ExpressionType).Assembly, typeof(IQueryable).Assembly };
84             knownTypes.ToList().ForEach(type => assemblies.Add(type.Assembly));
85             var resolver = new TypeResolver(assemblies, knownTypes);
86             var knownTypeConverter = new KnownTypeExpressionXmlConverter(resolver);
87             var serializer = new ExpressionSerializer(resolver, new CustomExpressionXmlConverter[] { knownTypeConverter });
88             return serializer;
89         }
90     }
91 }

 

服務實現代碼重構:

1 public Member GetMember(XElement xmlPredicate)
2 {
3     var predicate = SerializeHelper.DeserializeExpression<Member, bool>(xmlPredicate);
4     return DataSource.SingleOrDefault(predicate.Compile());
5 }

 

客戶端代碼重構:

 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 xmlPredicate = SerializeHelper.SerializeExpression(predicate);
10         var result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate));
11         Console.WriteLine(result.Email);
12 
13         var input = Console.ReadLine();
14         if (!string.IsNullOrEmpty(input))
15         {
16             predicate = SerializeHelper.CreateExpression<Member, bool>("UserName=@0", input);
17             xmlPredicate = SerializeHelper.SerializeExpression(predicate);
18             result = WcfHelper.InvokeService<IAccountContract, Member>(wcf => wcf.GetMember(xmlPredicate));
19             Console.WriteLine(result.Email);
20         }
21     }
22     catch (Exception e)
23     {
24         Console.WriteLine(e);
25     }
26     Console.ReadLine();
27 }

 

服務端代碼無變化。重構之后,一切都變得井然有序,生活多么美好。

至此,Expression表達式的遠程傳輸中序列化的問題得到了比較圓滿的解決,但是使用Dynamic Expression API生成表達式的步驟失去了原來Lambada表達式的優勢,返祖了。

所以,這只是一個比較初級的解決方案,希望能有更優良的解決方案來保持Lambada表達式的優勢。

第一次寫博客,結構比較凌亂,基本上是按我解決問題的思路流水下來的,不夠清晰,敬請大家諒解……

最后,奉上本文涉及的源代碼:

LambadaSerializeDemo03(重構前).rar

LambadaSerializeDemo03(重構后).rar


免責聲明!

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



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