• 【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

    如果您看完本篇文章感觉不错,请点击一下右下角的推荐来支持一下博主,谢谢!

    作者:郭明锋

    Q群:MVC EF技术交流(5008599)MVC EF 技术交流 OSharp开发框架交流(85895249)OSharp开发框架交流

    出处https://www.cnblogs.com/guomingfeng

    声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


  • 相关阅读:
    [mysql练习]多行结果合并问题练习
    【Python】Python多进程练习
    【mysql练习】转置,总计,分组
    【Mysql】HDFS文件上传流程
    [Jmeter][基础]Jmeter连接IMPALA
    【Linux】 -bash-4.2#问题和Cannot allocate memory
    微服务学习之路
    好的东西一定要收藏-持续更新
    Python日期的加减等操作
    NGINX动态增加模块,平滑升级
  • 原文地址:https://www.cnblogs.com/guomingfeng/p/2441664.html
Copyright © 2020-2023  润新知