接上文【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表達式的優勢。
第一次寫博客,結構比較凌亂,基本上是按我解決問題的思路流水下來的,不夠清晰,敬請大家諒解……
最后,奉上本文涉及的源代碼: