代码改变世界

走进Linq-Linq大观园

2008-07-18 11:26  横刀天笑  阅读(3553)  评论(27编辑  收藏

本系列文章
走进Linq-Linq横空出世篇
走进Linq-辉煌的背后
走进Linq-Linq大观园
走进Linq-Linq to Objects(上)基础篇


文章发布后大家有些人叫做,心里窃喜,不过压力也大增,我很想按照简洁明快的文风写下去,不过讲技术的文章很难不落于沉闷,所以我努力了。(题外话:这几天猛看幽默小说,想把文字写的幽默一点,开个玩笑,呵呵)

经过几天的闭关编程大师又有了一些新的觉悟了,不管对DSL还是命令式编程和函数式编程都有了新的理解。如是他又接着了漫长的云游。第一站当然就是那个曾经让他结下心结的那个刚毕业的coder.
大师:“嘿,这几日可好,还在发牢骚么?”
Coder:“不了,正好你来了,让你看看我的程序”,Coder将他的电脑屏幕转向大师,期盼的眼神表明他急切的期望得到大师的夸奖。
如是大师看到了如下一些代码:

/// <summary>
    
/// 一个通用的泛型委托,代表接受一个参数并有一个返回值的方法
    
/// </summary>
    
/// <typeparam name="TInput">输入参数类型</typeparam>
    
/// <typeparam name="TOutput">返回值类型</typeparam>
    
/// <param name="input">输入参数</param>
    
/// <returns>返回值</returns>
    public delegate TOutput MyDelegate<TInput,TOutput>(TInput input);
    
/// <summary>
    
/// 这个类是包含有对IEnumerable<T>接口的一系列扩展方法
    
/// 因为在.net里所有的集合类都实现了IEnumerable<T>接口
    
/// 所以对该接口的扩展将扩散到所有集合
    
/// </summary>
    public static class Extension
    {
        
public static IEnumerable<TInput> Where<TInput>(this IEnumerable<TInput> self, MyDelegate<TInput, bool> filter)
        {
            
foreach (TInput item in self)
                
if (filter(item))
                    
yield return item;
        }
        
public static IEnumerable<TOutput> Select<TInput, TOutput>(this IEnumerable<TInput> self, MyDelegate<TInput, TOutput> selector)
        { 
            
foreach(TInput item in self)
                
yield return selector(item);
        }
       
//下面有更多的SQL风格的移植
}

下面是我做的个小测试代码:
public class Program
{
        
public static void Main()
        {
            IList<Book> books = 
new List<Book> { 
             
new Book { Title = "Inside COM", ISBN = "123-456-789",Price=20 },
             
new Book { Title = "Inside C#", ISBN = "123-356-d89",Price=100 },
             
new Book { Title = "Linq", ISBN = "123-d56-d89", Price = 120 }
            };

            var result = books.Where(book => book.Title == 
"Linq").Select(book => new { 
                Key = book.Title,Value=book.Price
            });
        }
}
Coder一边展示着代码,一边念叨着,这里是因为使用了“扩展方法”所以可以这样写,这里使用了Lambda表达式,它可以简化匿名方法的写法,这里……
编程大师一边听着coder的讲解,一遍频频点头:“傻小子,不错啊,有点当年我的影子,按照你这样下去罗马也可以建成了,Linq也是可以写出来的呀。”
Coder听到大师的话兴奋异常,不过他从这句话里还是捕捉到了一个陌生的词汇:Linq。他用诧异的眼神看着大师,问道:啥是Linq,是谁家又创造了个新词汇?
大师笑着说,其实你刚才做的微软已经帮你做了,还给它起了一个非常洋气的名字:Linq,中文名字呢就叫做 语言集成查询。
.net 3.5发布的时候,微软新发布了几个dll,其中有一个就叫做System.Core.dll,在这个dll下对一些System命名空间做了进一步扩展。
在System.Core.dll下的System命名空间下你会发现有这么几个泛型的委托:
//无参,有一个返回值
public delegate TResult Func<TResult>();
//有一个参数和一个返回值,和你那个MyDelegate一样
public delegate TResult Func<T, TResult>(T arg);
//两个参数一个返回值
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
//三个参数一个返回值
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
//四个参数一个返回值
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);

有了这几个泛型的委托基本上都能应付了吧。
还是在这个dll下有个System.Linq命名空间,这是Linq的核心命名空间,这里面有个名为Enumerable的静态类,它里面的方法都是对IEnumerable<T>(这个接口可是Linq的中心人物啊)这个接口的扩展方法。看看,是不是在SQL里能用的这里都能找到了?
Select,Where,OrderBy, OrderByDescending,Average,Distinct
所以你现在很简单的就可以写出像下面这样的代码了:
result = books.Where(book=>book.Title.StartsWith("I"))
     .OrderBy(book=>book.Price)
     .Select(book=>
new{Key=book.Title,Value=book.Price});
编程大师接着说:如果就这样算了,我觉得Linq也不过尔尔,增加一些扩展方法而已,但是现在微软比我们想象的走的更远,现在你不仅仅能对程序中的一些集合对象做这样的查询了,你想想我们平时的程序可以归结为怎样一个等式?
还没等coder说出口大师就在键盘上敲下:
程序=代码+数据
编程大师如是接着说:那这些数据平时都来源于哪里?
Coder:程序中自己构造的一些集合对象,像我刚才的代码中那样,还有数据库,这个使我们平时用到最多的,还有XML存储,还有WebService,这个来源于远程的数据,还有什么RSS啦等等,很多了。
编程大师:嗯,是的。数据的来源非常广泛,就说我们平常用的三个吧,内存中的集合对象、XML存储和数据库。对于内存中的集合对象我们有语言自身的支持,XML我们有XML的一些API,比如XPath,对于数据库我们有ADO.net,可实际上从抽象层面我们对这些数据的操作都是相同的,你想不想屏蔽掉存储的细节,在高层有一个统一的API访问这些数据呢?至于数据存储在哪里对于你是透明的,也许它存在于你内存中,也许在万网的机房也许在美国西雅图,但是对于你来说这些都无需关心,你的代码都一样。
Coder:听起来是个很美妙的事情,这不会是在做梦吧。
大师:不是在做梦,今天你已经有了这些方法
.net 3.5里微软还发布了另外两个dll
System.Data.Linq.dll
System.Xml.Linq.dll
System.Data.Linq.dll里,对数据库的查询做了支持,不过目前微软提供的只支持Sql Server,感谢开源社区,现在有了DbLinq,它提供了对MySql,Oracle,Sql Server,PostgreSql,Sqlite的支持。
System.Xml.Linq.dll在更高层次对Xml的访问做了支持
这样你从微软这里获得了:
Linq to Objects 对内存中的集合的支持
Linq to Xml Xml的支持
Linq to SQL Sql Server的支持

 

这是一张从Linq in Action那本书里的截图,该图很好的在一个大的层次上揭示了Linq的视图。C#vb.net等一系列.net语言在一些语言特性和Linq对语言的扩展上对Linq家族提供了支持。未来我们将会实现Linq in EverywhereLinq将成为你的变成习惯。
C#Linq的语言层面支持
使用

result = books.Where(book=>book.Title.StartsWith("I"))
                .OrderBy(book
=>book.Price)
                .Select(book
=>new{Key=book.Title,Value=book.Price});
这种方式编写代码,虽然减少了不少工作量,并且编程风格也更加人性化,不过现在C#为我们提供了一些列非常有用的关键字。上面的代码现在你可以这样来编写:
result = from book in books
        
where book.Title.StartsWith("I")
        orderby book.Price
        select 
new { Key=book.Title,Value = book.Price};
from关键字后面跟着的是Func委托的输入参数,in关键字后面跟着一个IEnumerable类型,where,orderby,select对应着那些扩展方法,那些扩展方法接受的委托的输入参数就是from后面的book。实际上你可以把这些代码写成一行意思就更明显了:
result = from book in books where book.Title.StartsWith("I") orderby book.Price select new { Key=book.Title,Value = book.Price};
books集合里枚举一个book,如果这个book的标题是以”I”开头的就把它加入到返回集合中,并把返回集合按照book的价钱排序将上面的代码编译后的程序集用Reflector反编译,生成的代码是:
books.Where<Book>(delegate(Book book)
{
      
return book.Title.StartsWith("I");
}).OrderBy
<Book, float>(delegate(Book book)
{
      
return book.Price;
}).Select(
delegate(Book book)
{
      
return new { Key = book.Title, Value = book.Price };
});
看来这种方式写和扩展方法调用的方式没有什么差别,那为什么不呢。
废话那么多了,还是来几个HelloWorld式的程序吧

HelloWorld Linq
(下面所有程序的Book就是本系列文章中第一篇所出现的Book)
有一个Book集合,但是这个集合具体存储哪里我们并不清楚,也许在内存,也许在数据库,也许在XML存储,我们要做的就是把价格大于50的给揪出来,然后按照价格排序。
Linq to Objects(从内存中的集合里查找)

数据准备阶段

//这样的一个集合,存储在内存中
IList<Book> books = new List<Book> { 
    
new Book { Title = "Inside COM", ISBN = "123-456-789",Price=20 },
    
new Book { Title = "Inside C#", ISBN = "123-356-d89",Price=100 },
    
new Book { Title = "Linq", ISBN = "123-d56-d89", Price = 120 }
};
数据查询阶段
var result = from book in books
            
where book.Price > 50

            orderby book.Price
            select 
new { Key = book.Title, Value = book.Price };
foreach (var item in
 result)
                Console.WriteLine(
"Key:{0}-Value:{1}",item.Key,item.Value.ToString());
Linq to SQL(集合存储在Sql Server)

数据准备阶段
建立数据库表

输入数据

改写一下Book类,这个类是一个映射类,和数据库表做映射,更多内容后面会详细讲解

[Table]
public class Book
{
    
/// <summary>
    
/// 图书名称
     
/// </summary>
    [Column]
    
public string Title { getset; }
    
/// <summary>
    
/// 单价
     
/// </summary>
    [Column(DbType = "numeric(5, 2)")]
    
public float Price { getset; }
    
/// <summary>
    
/// 作者
     
/// </summary>
    [Column]
    
public string Author { getset; }
    
/// <summary>
    
/// ISBN号
     
/// </summary>
    [Column]
    
public string ISBN { getset; }
}
数据查询阶段
DataContext db = new DataContext("Data Source=localhost;Initial Catalog=db;User ID=sa;Password=sa");
            var result 
= from book in db.GetTable<Book>()
                         
where book.Price > 50
                         orderby book.Price
                         select 
new { Key = book.Title, Value = book.Price };

            
foreach (var item in result)
                Console.WriteLine(
"Key:{0}-Value:{1}",item.Key,item.Value.ToString());
最后程序运行的结果都是:

真所谓殊途同归啊,不管数据是如何存储的,查询的方式却99%一致。

总结
本篇旨在给大家一个对Linq的大局观认识,没有详细的深入,就算一个总览吧。精彩无需等待,祝大家编程愉快。