zoukankan      html  css  js  c++  java
  • Linq to Entity经验:表达式转换(修正版本)

          之前有篇文章(Linq to Entity经验:表达式转换)我分享过表达式转换的问题,当时以为问题解决了,但后来实际应用中发现其实没有解决,也并非完全没有解决,只不过不实用,问题如下:
     
          在讲问题之前,先来看看表达式转换的目的


          其实我们这么费劲的进行表达式转换,就是为了让我们在UI层写的表达式树条件能够最终转换成EntityFramework能够识别的表达式,之所以需要转换,那是因为EntityFramework只能识别EntityFramework自身的实体对象,如果我们是数据库优先的话,这些实体就是自动生成的,而我们的业务系统往往有独立于数据库的业务实体,这两者有些情况下相同,有些情况下非常不相同,这是我们需要转换的原因,比如TestExpression.Model.Courier是业务实体,My.FrameWork.Utilities.Test.Courier是数据库对象,看如下需求:


          UI层进行数据搜索数据的条件搜集:
          

    Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);

     

          要想进行数据库查询,就需要将上面的表达式树转换成下面EntityFramework能够识别的表达式树。注意,这两个表达式虽然对象名称一样,但命名空间不同。
      

    Expression<Func<My.FrameWork.Utilities.Test.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);

      
          问题:

          StartsWith的参数是字符串,我们知道这个字符串和其它大多数基元数据不一样,它不是值类型,而是引用类型,所以当外面的局部变量做为参数传递到表达式中时,在表达式树进行解析时还会发生MemberAccess,而上篇文章中只支持参数类型是ConstantExpression,而这里的MemberExpression在解析时就是报异常。,比如下面的表达式是可以转换的,直接传递"min",这就是一个ConstantExpression类型的参数:
      

    Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith("min");

     

          但如果这样写就不行:
      

      string name="min";
      Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(name);

         

          以及文章开关提到的表达式也不行:(下面的mes是一个class)

    Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);

           
         如何解决?这里分享下我解决问题的过程。
     
        1:既然参数是ConstantExpression的情况可行,那么能否将原本不是ConstantExpression的参数变更为ConstantExpression。
            有了这个想法,当时认为既然mes.Name是一个对象的属性,如果对这个属性值进行下字符串的深度复制是不是也就摆脱class的引用类型问题了,虽然摆脱了class的问题,但发现string本身就是一个引用类型,所以无论是否转换还是没能将非ConstantExpression转换成ConstantExpression的情况,也就是说下面的处理是徒劳无功的。

        public static class StringDeepClone
        {
            public static string GetStringDeepClone(this string source)
            {
                string result = string.Empty;
                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(ms, source );
                    source = null;
                    ms.Position = 0;
                    result = ((string)bf.Deserialize(ms));
                }
                return result;
            }
        }


        2:既然引用类型的问题是会发生MemberAccess,那么是否能够解析带局部变量的MemberExpression?
        

    Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);

            像这行代码,在整个表达式的上下文中是没有mes类型信息的,它是一个外部的局部变量,这样在解析时就会增大难度,后来我咨询过脑袋(http://cnblogs.com/ninputer),他给我提供了一个转换方法,后来由于代码不全就没继续研究了,代码的功能是可以解析这种带局部变量的MemberExpression。
        
        3:既然解析MemberExpression没能成功,那么是否能够从MemberExpression中直接抽取出值来呢?
             在网上搜索了一番,发现一老外提供了一个方法能够解决:
         

            private static object GetMemberExpressionValue(MemberExpression member)
            {
                var objectMember = Expression.Convert(member, typeof(object));
                var getterLambda = Expression.Lambda<Func<object>>(objectMember);
                var getter = getterLambda.Compile();
                return getter();
            }
            


            这个方法的思路就是将MemberExpression转换成可执行的Lambda表达式,通过执行Lambda表达式得到计算值。
        
         有了上面的方法,我们需要修改下原来的解析方法:主要是判断参数表达式的类型,如果是值类型就走以前的逻辑,如果是引用类型,我们需要从MemberExpression中抽取数值,有了数据我们的问题就解决了。
                        

                       case ExpressionType.Call:
                        {
                            var be = (MethodCallExpression)node;
                            var resultValue = string.Empty;
                            switch (be.Arguments[0].NodeType)
                            {
                                case ExpressionType.MemberAccess:
                                    var exprssion = GetMemberExpressionValue((MemberExpression)be.Arguments[0]);
                                    if (null != exprssion)
                                    {
                                        resultValue = exprssion.ToString();
                                    }
                                    break;
                                case ExpressionType.Constant:
                                    resultValue=((ConstantExpression)be.Arguments[0]).Value.ToString();
                                    break;
                            }
                            var expression = GetMethodExpression((MemberExpression)be.Object, resultValue, be.Method.Name, subst);
                            return expression.Body;
                        }

                        
          到此,表达式树的转换终于可以在实际项目应用了,这对我们动态搜集查询条件非常有帮助,尽管这个问题困扰我超过半个月时间,但最终还是得到解决了。表达式的转换比较麻烦,如果想让自己的表达式转换功能越来越强大,那么我们需要针对不同的情况编写对应的解决方案才行,没有完美只有最适合。
     

  • 相关阅读:
    go语言中通过http访问需要认证的api
    Mysql两个time类型计算时间相减
    gorm中数据库datetime类型的映射和time.Time的格式化
    最详细的六种装饰器写法,学不会你找我!
    深度学习中的四种激活函数
    看完这篇文章,相信我,你已经掌握正则表达式了!
    新手还在问学Python应该看什么书,老手已经进来下载了(附100本pdf电子书下载)
    Python轻松实现一个毕业生信息管理系统!
    Python一键搞定批量合成PDF
    网易云10万+音乐竟然能用Python一键下载!
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/2765514.html
Copyright © 2011-2022 走看看