zoukankan      html  css  js  c++  java
  • Linq To Sql进阶系列(七)动态查询续及CLR与SQL在某些细节上的差别

    在上面一篇文章Linq To Sql进阶系列(六)中,我们提到了使用object的动态查询。本文在上文的基础上,再做更加深入的引申。同时修正上文中一些不妥的地方。

    1, object的动态查询续
    首先要做的事情,就是将Find的函数改成扩展方法。扩展方法只能放在静态类里,而且它的第一个参数必须带this关键字。在上文中,作者留下了一个迷题。当需要or条件时,又该如何做呢?本文也将这个问题给出回答。但是对于动态Like的条件,笔者依然还没有找到一个较好的方法。为了增加or条件,函数的声明也再一次被改动。如下:
        public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj, bool isAnd) where TEntity : class
    在上文中,我们还碰到了System.Nullable<int>此类类型不支持的问题。其实这个地方主要原因在于我们构造right端的Expression Tree时,没有给它参数。那么这个问题通过Expression right = Expression.Constant(p.GetValue(obj, null), p.PropertyType); 可以得到修复。那整个函数修改后,如下:
        public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj, bool isAnd) where TEntity : class
        
    {
            
    if (source == null)
                
    throw new ArgumentNullException("Source can't be null!!");
            
    //获得所有property的信息
            PropertyInfo[] properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            Expression condition 
    = null;
            
    //先构造了一个ParameterExpression对象,这里的c,就是Lambda表达中的参数。(c=>)  
            
    //本变量被移出了foreach循环
            ParameterExpression param = Expression.Parameter(typeof(TEntity), "c");
            
    //遍历每个property
            foreach (PropertyInfo p in properties)
            
    {
                
    if (p != null)
                
    {
                    Type t 
    = p.PropertyType;
                    
    //只支持value型和string型的影射
                    if (t.IsValueType || t == typeof(string))
                    
    {
                        
    //如果不为null才算做条件
                        if (p.GetValue(obj, null!= null)
                        
    {
                            
    //SQL Server does not support comparison of TEXT, NTEXT, XML and IMAGE ,etc
                            ///Only support BigInt,Bit,Char,Decimal,Money,NChar,Real,
                            
    ///Int,VarChar,SmallMoney,SmallInt,NVarChar,NVarChar(MAX),VarChar(MAX)

                            Attribute attr = Attribute.GetCustomAttribute(p, typeof(ColumnAttribute));
                            
    if (attr != null)
                            
    {
                                
    string dbType = (attr as ColumnAttribute).DbType;
                                
    if (dbType.Contains("Text"|| dbType.Contains("NText")
                                    
    || dbType.Contains("Xml"|| dbType.Contains("Image")
                                    
    || dbType.Contains("Binary"|| dbType.Contains("DateTime")
                                    
    || dbType.Contains("sql_variant"|| dbType.Contains("rowversion")
                                    
    || dbType.Contains("UniqueIdentifier"|| dbType.Contains("VarBinary(MAX)"))
                                
    {
                                    
    continue;
                                }

                            }
     
                            
    //构造表达式的右边,值的一边
                            Expression right = Expression.Constant(p.GetValue(obj, null), p.PropertyType);
                            
    //构造表达式的左边,property一端。
                            Expression left = Expression.Property(param, p.Name);
                            
    //生成筛选表达式。即c.CustomerID == "Tom"
                            Expression filter = Expression.Equal(left, right);
                            
    if (condition == null)
                            
    {
                                condition 
    = filter;
                            }

                            
    else
                            
    {
                                
    if (isAnd)
                                    condition 
    = Expression.And(condition, filter);
                                
    else
                                    condition 
    = Expression.Or(condition, filter);
                            }

                        }

                    }

                }

            }

            
    if (condition != null)
            
    {
                Expression
    <Func<TEntity, bool>> pred = Expression.Lambda<Func<TEntity, bool>>(condition, param);
                
    return source.Where(pred);
            }
      
            
    return source;
            
        }

        
    在这里,首先检查输入的参数是否为null。扩展方法其实是按静态方法执行的。它和静态方法唯一不同的就是系统自动为其加了一个Attribute,而这个Attribute只能通过在第一个参数加this关键字才能获得。而后,在影射类型上,修改后的函数只支持数值型和string型。其原因就是像imager等并不支持条件查询。为了简化,我们只支持数值型和string型。这里最大的变化莫过于支持or条件了。调用Expression.And或Expression.Or就可以了。还有一个变化就是ParameterExpression对象和Expression<Func<TEntity, bool>>被移出了foreach循环。这样,提高了效率,只是在最后才去生成条件。
    而实际上,大家大多使用是and条件,那再重载一个方法。
        public static IQueryable<TEntity> Find<TEntity>(this IQueryable<TEntity> source, TEntity obj) where TEntity : class
        
    {
            
    return Find<TEntity>(source,obj,true);
        }
    我们再来测试一下
            Northwind db = new Northwind();
            db.Log = Console.Out;
           Customer cu = new Customer { City = "London", Country = "UK" }; 
           var q0 = db.Customers.Find(cu).ToList();
            var q1 = db.Customers.OrderBy(c=>c.Country).Find(cu, false).ToList();
            var q2 = Extension.Find(db.Customers.OrderBy(c => c.CustomerID), cu).ToList();
    大家可以看到,它们和系统定义方法一样使用,可以接在任何满足条件的语句后面。第三个例子直接就用的static方法的形式。从第三个例子,我们可以看出,extension methods和static methods差别其实不大。

    它们生成的sql为
    SELECT [t0].[CustomerID][t0].[CompanyName][t0].[ContactName][t0].[ContactTitle][t0].[Address
    ][t0].[City][t0].[Region][t0].[PostalCode][t0].[Country][t0].[Phone][t0].[Fax]
    FROM [dbo].[Customers] AS [t0]
    WHERE ([t0].[City] = @p0AND ([t0].[Country] = @p1)
    -- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [London]
    --
     @p1: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [UK]

    SELECT [t0].[CustomerID][t0].[CompanyName][t0].[ContactName][t0].[ContactTitle][t0].[Address
    ][t0].[City][t0].[Region][t0].[PostalCode][t0].[Country][t0].[Phone][t0].[Fax]
    FROM [dbo].[Customers] AS [t0]
    WHERE ([t0].[City] = @p0OR ([t0].[Country] = @p1)
    ORDER BY [t0].[Country]
    -- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [London]
    --
     @p1: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [UK]

    SELECT [t0].[CustomerID][t0].[CompanyName][t0].[ContactName][t0].[ContactTitle][t0].[Address
    ][t0].[City][t0].[Region][t0].[PostalCode][t0].[Country][t0].[Phone][t0].[Fax]
    FROM [dbo].[Customers] AS [t0]
    WHERE ([t0].[City] = @p0AND ([t0].[Country] = @p1)
    ORDER BY [t0].[CustomerID]
    -- @p0: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [London]
    --
     @p1: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [UK]


    2,限定字段在某集合中
    这有点像in操作。比如where city in ('London', 'BeiJing') 也可以写成 where city = 'London' or city = 'BeiJing'。既然谈到or条件的动态构造了,那就也来构造下这个吧。看上去有点多此一举。但是,至少是个很好的学习机会。这个和上面不同的是,它条件字段是唯一的,变化的是该字段的值。那用一string将字段名成传入,并用一集合将字段值传入函数。
    该函数完整的定义入下:

        
    public static IQueryable<TEntity> WhereOr<TEntity, OrType>(this IQueryable<TEntity> source, string propertyName, IEnumerable<OrType> values)
        
    {
            
    if (source == null)
                
    throw new ArgumentNullException("Source can't be null!!");
            ParameterExpression param 
    = Expression.Parameter(typeof(TEntity), "p");
            Expression left 
    = Expression.Property(param, propertyName);
            Expression condition 
    = null;
            
    foreach (OrType value in values)
            
    {
                Expression filter 
    = Expression.Equal(left, Expression.Constant(value));
                
    if (condition == null)
                    condition 
    = filter;
                
    else
                    condition 
    = Expression.Or(condition,filter);
            }

            
    if (condition != null)
                
    return source.Where((Expression<Func<TEntity, bool>>)Expression.Lambda(condition, param));

            
    return source;
        }

    使用时,
           var q3 = db.Customers.WhereOr("City", new List<string> { "London", "BeiJing" }).ToList();
    并不在多做解释。

    3, CLR与SQL在某些细节上的差别
    在上文中,有一朋友提出,其值不为null才做为条件,让函数有局限性。既然提了,那笔者就再引申下。CLR与SQL中,对待null值是不同的。CLR认为两个null值是相等的,而SQL并不这么认为。比如,下面的条件就是成立的。
            if (null == null)
                throw new Exception("CLR treat Null is the same!!");
    但在Sql中只能判断是不是null值,而不能对两个字段的null值直接比较。
    比如下面的语句
            var q6 = db.Employees.Where(c => c.Region == null).ToList();
    翻译为:
    SELECT [t0].[EmployeeID][t0].[LastName][t0].[FirstName][t0].[Title][t0].
    [TitleOfCourtesy][t0].[BirthDate][t0].[HireDate][t0].[Address][t0].[City
    ][t0].[Region][t0].[PostalCode][t0].[Country][t0].[HomePhone][t0].[Ext
    ension
    ][t0].[Photo][t0].[Notes][t0].[ReportsTo][t0].[PhotoPath]
    FROM [dbo].[Employees] AS [t0]
    WHERE [t0].[Region] IS NULL


    Linq To Sql是通过Ado.Net于Sql打交道的。也就是说Linq To Sql是建立在CLR基础上的。这点细小的差别让Linq To Sql不知道该与谁保持平行。 Where条件中,有 == 和Equal两个方法,它们在Linq To Sql中是不一样的。Equal认为null是相等的。但是sql又不能用=来判断,所以Equal方法翻译的sql语句就有些长。请大家自己仔细比较下面两个语句的sql差别
            var q5 = (from e in db.Employees
                      from o in db.Orders
                      where e.Region == o.ShipRegion
                      select new { e.Region, o }).ToList();
            var q6 = (from e in db.Employees
                      from o in db.Orders
                      where Equals(e.Region, o.ShipRegion)
                      select new { e.Region, o }).ToList();

    CLR和SQL在数值精度上的差别,也常让CLR抛OverFlow异常.这个很好判断,如果Ado.Net抛这个异常了,那Linq To Sql肯定要抛,所以并不是Linq To Sql的问题。
    本文所提到代码,请到此下载完整版本

    相关文章:
    C# 3.0 入门系列(一)
    C# 3.0入门系列(二)
    C# 3.0入门系列(三)
    C# 3.0入门系列(四)-之Select操作
    C#3.0入门系列(五)-之Where操作
    C#3.0入门系列(六)-之OrderBy操作
    C#3.0入门系列(七)--之OR工具介绍
    C#3.0入门系列(八)-之GroupBy操作
    C#3.0入门系列(九)-之GroupBy操作
    C#3.0入门系列(十)-之Join操作
    C#3.0入门系列(十一)-之In, Like操作
    C#3.0入门系列(十二)-Lambda表达式中Lifting
    C# 3.0与Linq To Sql的学习方法--浅谈

    Linq To Sql进阶系列(一)-从映射讲起
    Linq To Sql进阶系列(二)M:M关系
    Linq To Sql进阶系列(三)CUD和Log
    Linq To Sql进阶系列(四)User Define Function篇
    Linq To Sql进阶系列(五)Store Procedure篇
    Linq To Sql进阶系列(六)用object的动态查询与保存log篇

    TrackBack:http://www.cnblogs.com/126/archive/2007/09/23/902920.html

  • 相关阅读:
    通过C#的HttpClient模拟form表单请求
    通过Mysql连接ASP.Net Core2.0(Code First模式)
    每天一点产品思考(1):微博是否可以尝试取消点赞数展示?
    Google Analytics 学习笔记四 —— GA的Channels划分规则
    Google Analytics 学习笔记三 —— GA常用术语
    Google Analytics 学习笔记二 —— GA部署
    Google Analytics 学习笔记一 —— GA简介
    java基础知识总结(一)
    java源码解析之String类(二)
    java源码解析之String类(一)
  • 原文地址:https://www.cnblogs.com/hdjjun/p/1327051.html
Copyright © 2011-2022 走看看