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;
                        }

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

  • 相关阅读:
    [LeetCode] Power of Three 判断3的次方数
    [LeetCode] 322. Coin Change 硬币找零
    [LeetCode] 321. Create Maximum Number 创建最大数
    ITK 3.20.1 VS2010 Configuration 配置
    VTK 5.10.1 VS2010 Configuration 配置
    FLTK 1.3.3 MinGW 4.9.1 Configuration 配置
    FLTK 1.1.10 VS2010 Configuration 配置
    Inheritance, Association, Aggregation, and Composition 类的继承,关联,聚合和组合的区别
    [LeetCode] Bulb Switcher 灯泡开关
    [LeetCode] Maximum Product of Word Lengths 单词长度的最大积
  • 原文地址:https://www.cnblogs.com/ASPNET2008/p/2765514.html
Copyright © 2011-2022 走看看