作者:梁利锋 标签:DotNet

4512天前 (阅读:5180)
  LINQ 这个东东,出来有一段时间了,最近稍微看看了,发现 .Net 3.0 和 LINQ 还是挺有意思的。

  我个人认为,DLINQ 的最初起因是 Sql Server 2005 支持用 .Net 写存储过程,但是用 .Net 写的存储过程却需要用字符串的方式写 SQL 语句,从而丧失了存储过程本身的语法、语义的编译时检测的好处,而 DLINQ 正好可以弥补这个缺陷,当然,这只是我的推测而已,而且不管起因是什么,对于使用者来说都没有什么关系,只要好用就可以了 :)

  首先,LINQ 对集合的处理能力很好,至少,我们可以用比较熟悉的 SQL 语法,对于集合进行一些复杂的操作,比如 Group By、Join 什么的,总是比自己写代码完成要更方便,更安全,而且可读性也不错。

using System;
using System.Query;
using System.Collections.Generic;

class app {
  static void Main() {
    string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

    IEnumerable<string> expr = from s in names 
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();

    foreach (string item in expr)
      Console.WriteLine(item);
  }
}


  上述代码输出:
BURKE
DAVID
FRANK


  DLINQ 对数据库的操作也算直观,而且也支持 ORM 方式,ORM 部分的 Object 的定义方式比较复杂,应该是为了对于 Update 提供完善的支持,从而只对修改过的字段进行 Update。不过在我看来,意义不大,一般而言,就算是一条记录全部 Updata 开销也不大,而且,可以使用继承的方式来拆分某些大字段(Text 之类),以使之不被 Update:

using org.hanzify.llf.Data;

[DbTable("B")]
public class A : DbItem
{
	// dbtype: varchar(20)
	public string Name;
}

public class B : A
{
	// dbtype: Text
	public string Content;
	
	public A GetA()
	{
		A a = new A();
		a.m_Id = m_Id;
		a.Name = Name;
		return a;
	}
}

public class Program
{
	public static void Main()
	{
		DbItemList<B> bl = new DbItemList<B>(CK.K["Name"] = "test");
		B b = bl[0];
		b.Name = "test 1";
		b.GetA().Save();
	}
}


  当然,LINQ 正式推出的时候,肯定会附带一个代码生成器,用以从数据库结构生成相应的 class,而这些 class 如果完全不需要修改的话,倒也问题不大就是了。

  LINQ 本身,和 lambda 表达式有很大的关系。本来,如果 lambda 表达式只是匿名方法的更简化写法的话,意义倒也不大,不过,在 .Net 3.0 中,对于 lambda 表达式提供了一种表达式树的处理方式:

Expression<Func<int, bool>> filter = n => n < 5;

BinaryExpression    body  = (BinaryExpression)filter.Body;
ParameterExpression left  = (ParameterExpression)body.Left;
ConstantExpression  right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}", 
                  left.Name, body.NodeType, right.Value);


  以上代码的运行结果是: n LT 5

  最神奇的地方是,n < 5 本来应该返回一个 bool 值,但是现在,返回给我们的却是一颗分析树,而对于这个分析结果怎么再处理,就交给我们来决定了。换句话说,C# 帮我们做语法检查和语法分析,把分析结果给我们,从而使我们可以创建内嵌于 C# 内部的子语言,可以用于创建DSL——领域特定语言了。

  说到这里,不妨简单说一下 db4o 这个对象数据库系统。它的原生查询方式很有意思:

IList<Student> students = database.Query<Student> ( 
    delegate(Student student){ 
        return student.Age < 20 
        && student.Name.Contains("f"); 
    });


  如上所示,db4o 用一个匿名方法来完成查询条件的设置,这种方式包含 IDE 的智能提示,完善的语法检查等优点,而且,因为它本身只是 .Net (或 Java)的一个类库,所以,没有其他数据库的进程间通讯的开销,另外,它是对象数据库,所以也没有 O/R Mapping 的开销,这都使得这种所谓的原生查询得以很好的实现。

  不过,问题出在索引上。如上的例子,我们可以理解,这是数据库对于表中所有数据进行遍历,并且回调这个代理函数,对于需要进程间通讯的数据库,这种开销是不可接受的,不过对于 db4o 来说,只是函数调用的开销而已。但是,对于有索引的字段,数据库是应该使用二分法查找的,但是在这种原生查询中,数据库事实上不知道用户要查询哪个字段,以及使用哪个值进行什么样的比较,所以也无法利用二分法提供给这个代理函数期望的值,而只能完全遍历。

  所以,db4o 提供了另一种语法,类似 Sql 子语句或者说是有些类似 ORM 的方式来进行有索引字段的查询,使用这种方式的话,就可以使用到索引了:

Query query = database.Query();
query.Constrain(typeof(Student));
query.Descend("age").Constrain(20).Smaller();
IList students = query.Execute();


  不过,这种方式又丧失了原生查询中,IDE 的智能提示和语法检查之类的优点,而且,一种数据库提供两种语法的查询,而且相差这么大,是有些让人郁闷的。

  对于我的 DbEntry.Net,我对于查询最大的遗憾就是,没有办法象原生查询那样直接写变量,而要把字段名写成字符串,不过,即使在像 db4o 这种对象数据库中,对于非原生查询的字段名,也是使用的字符串,其实也就是没有什么好的办法就是了。

  不过,在 .Net 3.0 里,有了表达式树,对于类原生查询的语法,也可以延迟计算,那么就可以突破这个限制了。

  回到 LINQ。在 LINQ 中,对于数据库的查询,可以使用完全的类 SQL 语法:

// DataContext takes a connection string 
DataContext db = new	DataContext("c:\\northwind\\northwnd.mdf");
// Get a typed table to run queries
Table<Customer> Customers = db.GetTable<Customer>();
// Query for customers from London
var q =
	from c in Customers
	where c.City == "London"
	select c;
foreach (var cust in q)
	Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);


  而其中的 from ... where ... select 语法到 lambda 语法的转换,是编译器在做的,我讨厌这个限定。

  首先,from、select、orderby 根本不应该成为 c# 或 vb.net 的关键字;其次,这使得,除了 SQL 语句,我们定义的其他 DSL,都要使用比较丑陋的 lambda 语法。SQL 只是 DSL 的一种,我觉得,无限夸大它在 C# 中的作用并不是一个好现象。

  事实上,我觉得可以做一个类似 xml 中 CDATA 的语法来进行 DSL 的扩展:

// DataContext takes a connection string 
DataContext db = new	DataContext("c:\\northwind\\northwnd.mdf");
// Get a typed table to run queries
Table<Customer> Customers = db.GetTable<Customer>();
// Query for customers from London
var q = <![DSL.SQL[
	from c in Customers
	where c.City == "London"
	select c]]>;
foreach (var cust in q)
	Console.WriteLine("id = {0}, City = {1}", cust.CustomerID, cust.City);


  这只是随便写的,可能并不是无歧意的,不过,我要表明的是,进行类似这样的语法构造后,定义 DSL 将是一件轻松而愉快的工作,而且,C# 将是一种 DSL 的创造者,而不是只和 SQL 绑定的。虽然通用的 DSL 仍然需要某些大的国际组织进行标准化,不过,第三方可开发的 DSL,将是一个很好的推动力。(DSL 也并不一定是文本格式的,比如 UML,不过,大部分还是文本格式为主)

  既然 C# 3.0 已经是 SQL 绑定的,对于通用 DSL 定义,我们是否应该寄希望于 C# 4.0?可是,在 C# 3.0 中,from、select、orderby 已经成为 C# 的关键字,不可能在新版本中取消,那么结果会是什么?SQL 是唯一一种特化 DSL?

  好的,LINQ 是直观的,不过,有时候,直观不一定是好事。举一个常见的例子吧,一个高级查询页面,用户可以选择填写“标题”、或者填写“内容”,或者两者都填写,以便决定进行什么样的查询,使用 DbEntry.Net 的查询方式是这样的:

using org.hanzify.llf.Data;

public class DbAccess
{
    public DbItemList<News> Query(string Title, string Content)
    {
    	IWhereClause iwc = null;
    	if ( Title != null && Title != "" )
    	{
    		iwc &= CK.K["Title"] ^ "%" + Title + "%";
    	}
    	if ( Content != null && Content != "" )
    	{
    		iwc &= CK.K["Content"] ^ "%" + Title + "%";
    	}
    	if ( iwc == null )
    	{
    		throw new Exception("条件不能都是空的!");
    	}
    	return new DbItemList<News>(iwc);
    }
}


  使用 LINQ 该怎么做呢?事实上,如果使用 SQL 语法,是无法实现这种动态的条件定制的。当然,即使数据库原生的存储过程,对于这种查询,也是需要使用拼接字符串,最后 Exec 这个字符串的方式来做的。LINQ 不需要这样,在 LINQ 中,将这种查询定义为高级技巧,不能使用 SQL 语法,但是可以使用 lambda 语法,下面的例子摘自 LINQ 的例子 SampleQueries 中 Advanced 的 Dynamic Query - Where,和我上面的例子类型相同,不过,例子本身并不一样:

public void DLinq127() {
    ParameterExpression param = Expression.Parameter(typeof(Customer), "c");

    Expression left = Expression.Property(param, typeof(Customer).GetProperty("City"));
    Expression right = Expression.Constant("London");
    Expression filter = Expression.EQ(left, right);

    Expression where = QueryExpression.Where(db.Customers.ToQueryable().Expression, Expression.Lambda<Func<Customer, bool>>(filter, param));

    IQueryable<Customer> query = db.CreateQuery<Customer>(where);

    ObjectDumper.Write(query);
}


  高级吧?反正我觉得,还是类 SQL 语法好啊,对于这种场景,如果也能用类 SQL 语法解决才算完美。

  LINQ,新的体验和一些遗憾,我在思考,大概 LINQ team 也在思考,呵呵。

The genius store caledl, they're running out of yo(非注册用户) 2014-3-27 12:29:48

The genius store caledl, they're running out of you.