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对象和xpression<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 ] = @p0 ) AND ( [ 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 ] = @p0 ) OR ( [ 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 ] = @p0 ) AND ( [ 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的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值