兴国安邦

C# 3.0, Linq, Linq To Sql

博客园 首页 新随笔 联系 订阅 管理
  33 Posts :: 0 Stories :: 553 Comments :: 150 Trackbacks
在上面一篇文章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篇
posted on 2007-09-23 00:39 Tom Song 阅读(4416) 评论(24)  编辑 收藏 网摘 所属分类: C# 3.0Linq To Sql

Feedback

马甲也上了?
  回复  引用    

#2楼 2007-09-25 22:55 int08h[未注册用户]
我想写一个QueryBuilder,差不多是这样:
QueryBuilder<TEntity> AppendEntity(TEntity entity, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendEqual(System.Reflection.PropertyInfo property, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendEqual(string propertyName, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendGreater(string propertyName, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendGreater(System.Reflection.PropertyInfo property, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendGreaterOrEqual(string propertyName, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendGreaterOrEqual(System.Reflection.PropertyInfo property, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendLess(System.Reflection.PropertyInfo property, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendLess(string propertyName, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendLessOrEqual(string propertyName, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendLessOrEqual(System.Reflection.PropertyInfo property, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendNotEqual(string propertyName, object value, Int08H.Data.Linq.Core.QueryMode mode);

QueryBuilder<TEntity> AppendNotEqual(System.Reflection.PropertyInfo property, object value, Int08H.Data.Linq.Core.QueryMode mode);

不知道会不会有用,觉得挺方便的...

  回复  引用    

#3楼 2007-09-26 11:27 int08h[未注册用户]
我觉得代码有个问题,即
Expression<Func<TEntity, bool>> pred = Expression.Lambda<Func<TEntity, bool>>(condition, param);
这行没有判断condition == null的条件,当传入所有属性均为null的对象的时候,此处会抛出异常...

  回复  引用    

#4楼 2007-09-26 11:30 int08h[未注册用户]
另外,我认为应当将DateTime类型排队在查询之外,因为精确到毫秒级的DateTime型在查询中基本不可能相等...
还有,text和ntext这2种SQL类型不支持相等判定,也要排除...

  回复  引用    

#5楼[楼主] 2007-09-26 11:36 宋国安      
@int08h
是的,你说的对。检查太多了,也是个问题。我去更新代码。

  回复  引用  查看    

#6楼 2007-09-26 22:51 int08h[未注册用户]
嗯,我还是坚持不应该将DateTime型加入查询,至少我这里根本没有成功过...就算是刚刚插入数据库的对象,直接再应用此对象查询也无法得到,因为生成对象时和插入对象时也是有时间差的...
或者只取Date而忽略Time吧...

  回复  引用    

#7楼 2007-09-27 07:36 int08h[未注册用户]
不好意思,没有仔细看,已经排队了datetime了...
  回复  引用    

#8楼 2007-09-27 07:41 int08h[未注册用户]
我有个问题,楼主的代码在验证DbType的时候,会不会因为大小写不同的问题而通过了if检验呢?是不是ToUpper()后检验更合理...
另外,是不是将这些类型保存为一个public static readonly string[]后用迭代来检验会有更高的效率,可以避免多次分配内存

我想不能为什么MS要把DbType做成string,而不是enum类型...

  回复  引用    

#9楼[楼主] 2007-09-27 10:20 宋国安      
@int08h
其实,我个人的意见,是没有必要去检查。因为如果不支持的话,还不如就让它抛sqlexception异常好了。检查太多了,性能就不行了。

  回复  引用  查看    

#10楼[楼主] 2007-09-27 10:25 宋国安      
@int08h
关于你说的toupper的问题,其实没有必要,因为,这里面的值全部匹配,一个大小写的错误都没有。当然,你的方法可以防止他更改设计。

  回复  引用  查看    

#11楼 2007-09-27 10:49 int08h[未注册用户]
我觉得DbType的类型还是应当检查一下的,因为这个方法的查询条件的组合不受外部的控制,我们不能要求传入的类型中所有属性的dbtype都由SQL支持,此时如果抛出SqlException异常,外部也没有任何办法处理

说得不很明白,主要意见就是,我们写的方法应当在传入正确的参数类型对象后给予正确的回应,而不是“部分此类型对象将导致方法中断”

  回复  引用    

#12楼[楼主] 2007-09-27 12:31 宋国安      
@int08h
我觉得,用continue跳过有问题的条件,是不合适的。可能使用者并没有意识到自己的错误。还在那里怀疑,为什么没有取到他需要的数据。那只好throw exception了。到了sql端,依然还要检查一次。所以,这里的检查完全可以摸掉。函数抛出异常本来就是正常现象,只是早抛晚抛和在那个层面抛的问题。

  回复  引用  查看    

#13楼 2007-09-27 16:23 小菲[未注册用户]
在自动生成代码中如何加上"DataContract" 标记啊?
[Table(Name="dbo.sys_menu")]
[DataContract]
public partial class sys_menu : INotifyPropertyChanging, INotifyPropertyChanged
{
。。。 。。。
}

我现在想做成wcf结构,要序列化就必须加上[DataContract];但由于在开发阶段,每次自动生成的代码都把我手工加的"[DataContract]"去掉了;

大家有什么好的建议啊,谢谢

  回复  引用    

#14楼 2007-09-27 22:11 int08h[未注册用户]
如果类名不变的话,是不是可以自己再在一个.cs文件里写
[DataContract]
public partial class sys_menu : INotifyPropertyChanging, INotifyPropertyChanged{}

class里面不需要任何东西,这样就算加上了你要的属性了

  回复  引用    

#15楼 2007-09-27 23:51 小菲[未注册用户]
int08h,谢谢你,太聪明了,我都不知道活学活用啊
  回复  引用    

#16楼[楼主] 2007-09-28 09:41 宋国安      
@小菲
加[DataContract] attribute有工具的。sqlmetal和or designer都支持。sqlmetal,命令行如下:
sqlmetal xxx.dbml //serialization:Unidirectional /code:xx.cs

or designer中,在空白处,右击选属性,找到Serialization Mode 将起值设为Unidirectional 即可。

关于更改影射文件的问题,不要在它自动升成的代码上修改。选dbml文件,右击选view code,就会自动添加一个partial class文件。

  回复  引用  查看    

问一下,Like的表达式怎么动态构建?
  回复  引用    

#18楼[楼主] 2007-11-28 20:12 Tom Song      
@俗人落落
我还没有想到..想到了,再告诉你哦。如果,你先想到了,也要告诉我哦。呵呵。

  回复  引用  查看    

我用Equals生成的SQL和==怎么是一样的?
  回复  引用    

建议LZ重写这两篇文章,动态Ling已经解决了动态查询,不然很多人看了会误会的,觉得LING很难..但动态Ling不支持"C LIKE '%{@0}%' "
  回复  引用    

您好,您写的linq系列文章非常好,我用的SQL2000,麻烦您发一份数据库文件给我,谢谢,我的邮箱 lyzgm@126.com
  回复  引用    

#22楼 2008-10-30 20:56 忘了[未注册用户]
兄弟,能帮忙写段代码动态实现如下 LAMDBA 表达式吗?
Expression<Func<CubeCell, bool>> exp =
customer => ( customer.Orders.Single( order => order.OrderType == "TYPE_1" ).ProductNo == "PRODUCT_1" )
&& ( customer.Orders.Single( order => order.OrderType == "TYPE_2" ).ProductNo == "PRODUCT_2" )
...
&& ( customer.Orders.Single( order => order.OrderType == "TYPE_N" ).ProductNo == "PRODUCT_N" ))

其中:
1. N是可变的,意味着用静态语法无法实现;
2. 假设每家客户特定订单类型确实有且只有一份订单......总之业务逻辑就不要深究了。

  回复  引用    

鼠标右击Linq To Sql文件,选择view code,
为什么我在vs2008里面一直找不到这个view code

  回复  引用    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 902920




相关文章:

相关链接: