【IT168技术文档】
LINQ 这个东东,出来有一段时间了,最近稍微看看了,发现 .Net 3.0 和 LINQ 还是挺有意思的。
我个人认为,DLINQ 的最初起因是 Sql Server 2005 支持用 .Net 写存储过程,但是用 .Net 写的存储过程却需要用字符串的方式写 SQL 语句,从而丧失了存储过程本身的语法、语义的编译时检测的好处,而 DLINQ 正好可以弥补这个缺陷,当然,这只是我的推测而已,而且不管起因是什么,对于使用者来说都没有什么关系,只要好用就可以了 :)
首先,LINQ 对集合的处理能力很好,至少,我们可以用比较熟悉的 SQL 语法,对于集合进行一些复杂的操作,比如 Group By、Join 什么的,总是比自己写代码完成要更方便,更安全,而且可读性也不错。
上述代码输出:1. using System; 2. using System.Query; 3. using System.Collections.Generic; 4. 5. class app { 6. static void Main() { 7. string[] names = { "Burke", "Connor", "Frank", 8. "Everett", "Albert", "George", 9. "Harris", "David" }; 10. 11. IEnumerable<string> expr = from s in names 12. where s.Length == 5 13. orderby s 14. select s.ToUpper(); 15. 16. foreach (string item in expr) 17. Console.WriteLine(item); 18. } 19. }
DLINQ 对数据库的操作也算直观,而且也支持 ORM 方式,ORM 部分的 Object 的定义方式比较复杂,应该是为了对于 Update 提供完善的支持,从而只对修改过的字段进行 Update。不过在我看来,意义不大,一般而言,就算是一条记录全部 Updata 开销也不大,而且,可以使用继承的方式来拆分某些大字段(Text 之类),以使之不被 Update:BURKE DAVID FRANK
1. using org.hanzify.llf.Data; 2. 3. [DbTable("B")] 4. public class A : DbItem 5. { 6. // dbtype: varchar(20) 7. public string Name; 8. } 9. 10. public class B : A 11. { 12. // dbtype: Text 13. public string Content; 14. 15. public A GetA() 16. { 17. A a = new A(); 18. a.m_Id = m_Id; 19. a.Name = Name; 20. return a; 21. } 22. } 23. 24. public class Program 25. { 26. public static void Main() 27. { 28. DbItemList<B> bl = new DbItemList<B>(CK.K["Name"] = "test"); 29. B b = bl[0]; 30. b.Name = "test 1"; 31. b.GetA().Save(); 32. } 33. }
当然,LINQ 正式推出的时候,肯定会附带一个代码生成器,用以从数据库结构生成相应的 class,而这些 class 如果完全不需要修改的话,倒也问题不大就是了。
LINQ 本身,和 lambda 表达式有很大的关系。本来,如果 lambda 表达式只是匿名方法的更简化写法的话,意义倒也不大,不过,在 .Net 3.0 中,对于 lambda 表达式提供了一种表达式树的处理方式:
以上代码的运行结果是: n LT 51. Expression<Func<int, bool>> filter = n => n < 5; 2. 3. BinaryExpression body = (BinaryExpression)filter.Body; 4. ParameterExpression left = (ParameterExpression)body.Left; 5. ConstantExpression right = (ConstantExpression)body.Right; 6. 7. Console.WriteLine("{0} {1} {2}", 8. left.Name, body.NodeType, right.Value);
最神奇的地方是,n < 5 本来应该返回一个 bool 值,但是现在,返回给我们的却是一颗分析树,而对于这个分析结果怎么再处理,就交给我们来决定了。换句话说,C# 帮我们做语法检查和语法分析,把分析结果给我们,从而使我们可以创建内嵌于 C# 内部的子语言,可以用于创建DSL——领域特定语言了。
说到这里,不妨简单说一下 db4o 这个对象数据库系统。它的原生查询方式很有意思:
如上所示,db4o 用一个匿名方法来完成查询条件的设置,这种方式包含 IDE 的智能提示,完善的语法检查等优点,而且,因为它本身只是 .Net (或 Java)的一个类库,所以,没有其他数据库的进程间通讯的开销,另外,它是对象数据库,所以也没有 O/R Mapping 的开销,这都使得这种所谓的原生查询得以很好的实现。1. IList<Student> students = database.Query<Student> ( 2. delegate(Student student){ 3. return student.Age < 20 4. && student.Name.Contains("f"); 5. });
不过,问题出在索引上。如上的例子,我们可以理解,这是数据库对于表中所有数据进行遍历,并且回调这个代理函数,对于需要进程间通讯的数据库,这种开销是不可接受的,不过对于 db4o 来说,只是函数调用的开销而已。但是,对于有索引的字段,数据库是应该使用二分法查找的,但是在这种原生查询中,数据库事实上不知道用户要查询哪个字段,以及使用哪个值进行什么样的比较,所以也无法利用二分法提供给这个代理函数期望的值,而只能完全遍历。
所以,db4o 提供了另一种语法,类似 Sql 子语句或者说是有些类似 ORM 的方式来进行有索引字段的查询,使用这种方式的话,就可以使用到索引了:
不过,这种方式又丧失了原生查询中,IDE 的智能提示和语法检查之类的优点,而且,一种数据库提供两种语法的查询,而且相差这么大,是有些让人郁闷的。1. Query query = database.Query(); 2. query.Constrain(typeof(Student)); 3. query.Descend("age").Constrain(20).Smaller(); 4. IList students = query.Execute();
对于我的 DbEntry.Net,我对于查询最大的遗憾就是,没有办法象原生查询那样直接写变量,而要把字段名写成字符串,不过,即使在像 db4o 这种对象数据库中,对于非原生查询的字段名,也是使用的字符串,其实也就是没有什么好的办法就是了。
不过,在 .Net 3.0 里,有了表达式树,对于类原生查询的语法,也可以延迟计算,那么就可以突破这个限制了。
回到 LINQ。在 LINQ 中,对于数据库的查询,可以使用完全的类 SQL 语法:
而其中的 from ... where ... select 语法到 lambda 语法的转换,是编译器在做的,我讨厌这个限定。1. // DataContext takes a connection string 2. DataContext db = new DataContext("c:\\northwind\\northwnd.mdf"); 3. // Get a typed table to run queries 4. Table<Customer> Customers = db.GetTable<Customer>(); 5. // Query for customers from London 6. var q = 7. from c in Customers 8. where c.City == "London" 9. select c; 10. foreach (var cust in q) 11. Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
首先,from、select、orderby 根本不应该成为 c# 或 vb.net 的关键字;其次,这使得,除了 SQL 语句,我们定义的其他 DSL,都要使用比较丑陋的 lambda 语法。SQL 只是 DSL 的一种,我觉得,无限夸大它在 C# 中的作用并不是一个好现象。
事实上,我觉得可以做一个类似 xml 中 CDATA 的语法来进行 DSL 的扩展:
这只是随便写的,可能并不是无歧意的,不过,我要表明的是,进行类似这样的语法构造后,定义 DSL 将是一件轻松而愉快的工作,而且,C# 将是一种 DSL 的创造者,而不是只和 SQL 绑定的。虽然通用的 DSL 仍然需要某些大的国际组织进行标准化,不过,第三方可开发的 DSL,将是一个很好的推动力。(DSL 也并不一定是文本格式的,比如 UML,不过,大部分还是文本格式为主)1. // DataContext takes a connection string 2. DataContext db = new DataContext("c:\\northwind\\northwnd.mdf"); 3. // Get a typed table to run queries 4. Table<Customer> Customers = db.GetTable<Customer>(); 5. // Query for customers from London 6. var q = <![DSL.SQL[ 7. from c in Customers 8. where c.City == "London" 9. select c]]>; 10. foreach (var cust in q) 11. Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);
既然 C# 3.0 已经是 SQL 绑定的,对于通用 DSL 定义,我们是否应该寄希望于 C# 4.0?可是,在 C# 3.0 中,from、select、orderby 已经成为 C# 的关键字,不可能在新版本中取消,那么结果会是什么?SQL 是唯一一种特化 DSL?
好的,LINQ 是直观的,不过,有时候,直观不一定是好事。举一个常见的例子吧,一个高级查询页面,用户可以选择填写“标题”、或者填写“内容”,或者两者都填写,以便决定进行什么样的查询,使用 DbEntry.Net 的查询方式是这样的:
1. using org.hanzify.llf.Data; 2. 3. public class DbAccess 4. { 5. public DbItemList<News> Query(string Title, string Content) 6. { 7. IWhereClause iwc = null; 8. if ( Title != null && Title != "" ) 9. { 10. iwc &= CK.K["Title"] ^ "%" + Title + "%"; 11. } 12. if ( Content != null && Content != "" ) 13. { 14. iwc &= CK.K["Content"] ^ "%" + Title + "%"; 15. } 16. if ( iwc == null ) 17. { 18. throw new Exception("条件不能都是空的!"); 19. } 20. return new DbItemList<News>(iwc); 21. } 22. }