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

  • 相关阅读:
    CodeForces 7B
    CodeForces 4D
    离散化
    线段树入门
    洛谷 P3951 小凯的疑惑(赛瓦维斯特定理)
    Codeforces 1295D Same GCDs (欧拉函数)
    Codeforces 1295C Obtain The String (二分)
    Codeforces 1295B Infinite Prefixes
    Codeforces 1295A Display The Number(思维)
    Codeforces 1294F Three Paths on a Tree(树的直径,思维)
  • 原文地址:https://www.cnblogs.com/hdjjun/p/1327051.html
Copyright © 2011-2022 走看看