Spiga

LINQ to SQL快速上手 step by step

2009-04-22 00:03 by T2噬菌体, 7564 visits, 收藏, 编辑

前言

      最近接连遇到几个朋友问我同一个问题,就是关于.NET平台上ORM框架的选择。我想在这个讲求效率的时代,谁也不想手写SQL或存储过程去访问数据库了。大家都知道,在Java平台上,ORM这一块基本是Hibernate的天下。当然,相对轻量级的iBatis也有不错的表现。
      不过谈到.NET平台,ORM框架似乎相对混乱了点。很多朋友问我的时候,往往会这样问:NHibernate、NBear和Castle该选择哪个?而当我反问:为什么不适用微软自带的Linq to Sql呢?对方经常会迷茫和不解。
      我觉得这是个很奇怪的现象。依照我个人的实践,我认为当需要快速构建一个中小型项目时,Linq to Sql是一个很好的选择。你至少有以下理由可以选择它:

     i. 它是微软自己的产品,和.NET平台有着天生的适应性。如果你使用.NET Framework3.5和VS2008开发环境,它本身就集成在里面了,同时VS2008对于Linq to Sql给予了诸多方便的支持。使用它,你不仅在开发和部署时不用考虑第三方库,更可以尽情享受VS2008带来的种种方便。
      ii. 上手十分容易,使用十分轻松,通常,你不需要编写一行代码,也不用写任何XML配置,完全通过可视化拖拽就能完成ORM层的构建。
      iii. 功能丰富,使用便捷。当轻松构建好ORM层后,你就可以更轻松的操纵数据库了。Linq to Sql提供了丰富的功能,完全可以满足日常数据访问的需求。使用方法也非常简单、灵活。

      有这么好的理由,我真想不通为什么那么多人不愿去选择它。我想来想去,也许有两个重要原因,一是把LINQ和Linq to Sql混为一谈了,二是受前段时间“LINQ已死”的误导,觉得微软已经抛弃Linq to Sql了。关于这两点,我就不细说了,简略澄清一下:
      首先,LINQ是从.NET Framework3.0开始,.NET平台上引入的一种新式语言特性,狭义一点,你可以讲它理解成一种新式语法,主要是针对迭代数据操作的,所以,也许LINQ叫做“数据迭代引擎(Data Iterative Engine)”更合适,之所以不着样命名,我想微软可能不愿意让自己产品的简写为“DIE”吧。:-)而Linq to Sql是LINQ在数据库访问方面的一个应用框架,完全是两码事。
      其次,关于微软会不会放弃Linq to Sql,客观说,可能会,因为微软下一步主推的ORM解决方案是Entity Framework。不过这并不妨碍你学习使用Linq to Sql,原因如下:第一,考虑到兼容性问题,微软绝对不会把Linq to Sql从.NET Framework中拿掉的,所以你不用怕现在用Linq to Sql写的程序以后不能运行了。第二,即使微软不更新Linq to Sql了,但它现在的功能,已经足够满足我们日常需要了,相比重量级的Entity Framework,如果你只是做一个中小型项目,并且很注重开发效率和学习曲线,为什么不选择可爱的Linq to Sql呢?

      好的,上面废话说了不少,下面正式开始吧。本文不是一篇关于Linq to Sql的大百科,写本文的目的,是引导大家快速上手Linq to Sql,所以不可能面面俱到去讲解Linq to Sql。不过根据80-20定律,学会了本文的内容,基本就可以应付80%的工作了。另外,延续我一贯的风格,本文将通过案例的方式帮大家上手Linq to Sql。案例是一个“公告发布程序”。下面我们开始!

Step1:建立数据库

      在使用Linq to Sql前,我们要将相应的数据库建好。在这个Demo中,使用的数据库是SQL Server Express 2005。
      我们首先建立一个叫的数据库MyBulletin,及两个数据表:Category和Bulletin,分别表示公告的分类和公告,建立方法不再赘述。至于两个表的具体字段请参看图1。

01

图1、数据表结构

    其中Category和Bulletin存在一个一对多的关联,表示一个分类下可以有多条公告。

Step2:建立LINQ to SQL Classes文件

       数据建好后,我们需要建立LINQ to SQL Classes文件。这种文件是Linq to Sql框架的主要文件,后面自动生成的实体类和ORM代码都存在于这个文件中。
      我们打开VS2008,新建一个C# Class Labrary工程,名称为LinqToSqlDemo.Orm,建好后在工程上单击右键,选择“Add”->“New Item”,在文件类型中选择“LINQ to SQL Classes”,文件名命名为“DataClasses.dbml”,如图2所示。

02

图2、新建LINQ to SQL Classes文件

Step3:根据数据库表自动生成代码

      当建好LINQ to SQL Classes后,VS主界面中自动打开了这个文件。可以看到,这个文件实际是一个设计文件,目前还不包含任何代码和元素。下一步就是利用我们刚才的数据库自动生成ORM代码了。
      打开Server Explorer面板。一般它位于VS的左上角,如果没有,请到View菜单中去打开。然后在Date Connections上右键单击,选择“Add Connection”,从这里添加对数据库MyBulletin的连接。

03

图3、添加数据库连接

      如图3所示,在Server name中填入SQL Server数据库服务的名字,如果使用的是SQL Server Express 2005,服务名一般是“计算机名\SQLEXPRESS”的格式,然后在“Select or enter a database name”中选择MyBulletin,单击“OK”,就连上我们所需的数据库了。
      这时,展开这个连接下的Tables节点,应该能看到Category和Bulletin两个表,选中两个表,将它们拖到DataClasses.dbml的设计区,就会看到如图4所示的样子。

04

图4、设计视图

      然后,按Ctrl+S保存,OK!ORM构建完了!
      没有骗你,所有需要的代码已经生成完成,现在可以使用它们操作MyBulletin数据库了。不要怀疑,Linq to Sql使用起来就是这么轻松加愉快,不需要写一行代码,也不需要写一个XML字符,ORM就构建完了!
      我知道你没看到生成的东西可能不太放心,那么你可以在Solution Explorer里展开DataClasses.dbml节点,看到里面的DataClasses.designer.cs文件没,打开它,里面就是刚才自动生成的代码,我们的数据访问操作就靠这些代码了。另外顺表提一下,数据库的连接字符串放在工程根目录下的app.config文件里,这也是自动生成的,打开它,就可以看到连接字符串。以后如果要修改连接字符串,就修改这里。
      好了,ORM构建好了,下面我们看看怎么用。

Step4:使用Linq to Sql访问数据库

       我们首先新建一个工程。为了简单起见,我们就直接建立一个C# Console Application测试我们的ORM吧。将这个工程命名为LinqToSqlDemo.Test。当然,建好工程后,不要忘了添加对工程LinqToSqlDemo.Orm的引用,还要添加对“System.Data.Linq”命名空间的引用。
      然后,我们打开Program.cs文件,将其中的内容替换为如下测试代码。

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Text;

using LinqToSqlDemo.Orm;

namespace LinqToSqlDemo.Test
{
    class Program
    {
        private static DataClassesDataContext dataContext = new DataClassesDataContext();

        private static void Output()
        {
            //输出分类信息
            foreach (Category c in dataContext.Categories)
            {
                Console.WriteLine("分类" + c.ID + ":" + c.Name);
            }

            //输出体育新闻下的公告信息
            Category categorySport = dataContext.Categories.Single(c => c.Name == "体育新闻");
            foreach (Bulletin b in categorySport.Bulletins)
            {
                Console.WriteLine("标题:" + b.Title);
                Console.WriteLine("内容:" + b.Content);
                Console.WriteLine("发布日期:" + b.Date);
                Console.WriteLine("所属分类:" + b.Category1.Name);
            }
        }

        private static void TestInsert()
        {
            //生成分类实体类
            Category category1 = new Category()
            {
                Name = "国际要闻"
            };
            Category category2 = new Category()
            {
                Name = "体育新闻"
            };
            Category category3 = new Category()
            {
                Name = "财经快报"
            };

            //生成公告实体类
            Bulletin bulletin1 = new Bulletin()
            {
                Content = "曼联晋级冠军杯四强",
                Date = DateTime.Now,
                Title = "曼联晋级冠军杯四强"
            };
            Bulletin bulletin2 = new Bulletin()
            {
                Content = "18:00直播亚冠首尔VS山东,敬请期待!!!",
                Date = DateTime.Now,
                Title = "18:00直播亚冠首尔VS山东"
            };

            //将公告加入相应分类
            category2.Bulletins.Add(bulletin1);
            category2.Bulletins.Add(bulletin2);

            //加入数据库
            dataContext.Categories.InsertOnSubmit(category1);
            dataContext.Categories.InsertOnSubmit(category2);
            dataContext.Categories.InsertOnSubmit(category3);
            dataContext.SubmitChanges();
        }

        private static void TestDelete()
        {
            dataContext.Categories.DeleteOnSubmit(dataContext.Categories.Single(c => c.Name == "国际要闻"));
            dataContext.SubmitChanges();
        }

        private static void TestUpdate()
        {
            Category categoryFinance = dataContext.Categories.Single(c => c.Name == "财经快报");
            categoryFinance.Name = "财经新闻";
            dataContext.SubmitChanges();
        }

        static void Main(string[] args)
        {
            Console.WriteLine("==============================Linq to SQL 测试==============================");
            Console.WriteLine();

            Console.WriteLine("==============================测试Insert==============================");
            Console.WriteLine();
            TestInsert();
            Output();

            Console.WriteLine("==============================测试Delete==============================");
            Console.WriteLine();
            TestDelete();
            Output();

            Console.WriteLine("==============================测试Update==============================");
            Console.WriteLine();
            TestUpdate();
            Output();

            Console.ReadLine();
        }
    }
}

      一下子看不懂上述代码页没有关系,稍候我们会解释一下。现在,我们先来看运行结果:

06

图5、测试程序运行结果

      我们先来看看这段测试程序做了什么事。刚开始,数据库是空的,我们首先插入三个分类,并在“体育新闻”下插入两条公告,这是对Insert的测试。接着,我们删除了“国际要闻”分类,这是对Delete的测试。然后,我们将“财经快报”改为“财经新闻”,这是对Update测试。另外,整个过程的输出当然是对Select的测试。这样,数据库基本的操作都测试过了。从输出结果来看,我们的ORM组件运行很顺利,程序输出正确。

程序分析

      经过简单的四步,我们就完成了通过Linq to Sql操作数据库的过程。下面我们对测试代码进行一个简要的分析,帮助朋友们学会Linq to Sql操作数据库的基本方法。

取得数据库Gateway

      要操作数据库,我们首先要获得一个DataContext对象,这个对象相当于一个数据库的Gateway,所有的操作都是通过它进行的。这个对象的名字是“LINQ to SQL Classes文件名+‘DataContext’”,这里,就是DataClassesDataContext了。它和普通对象一样,直接实例化就行了。在Demo里我将它实例化为一个静态变量。
      取得DataContext对象后,每个数据表就会映射到其一个集合属性,例如Category表映射到dataContext.Categories,这是一个集合属性,每一个元素是一个实体类,代表此表中的一条记录。实体类名和表名相同。实体类的字段自然就映射到对应表的字段。
      还有一点需要注意,数据库中的一对多关系,在Linq to Sql生成代码时会自动表示到类结构中。并且,这种关联是双向的。例如,Category与Bulletin的一对多关系,到了类结构中,反映成如下形式:在Category类中,有一个名为Bulletins的集合属性,内容是所有属于此Category的Bulletin对象的引用。而在Bulletin类中,也会有个Category1属性(由于Category这个名字被我们用了,所以,这个关联属性自动加了个“1”),其内容是此Bulletin所属Category对象的引用。

Insert操作

      Insert用于向数据库添加记录。一般情况下,使用“DataContext.表映射集合.InsertOnSubmit(实体类)”的方式就可以完成Insert操作。不过这里要注意一点,由于Linq to Sql使用了“Unit of Work”模式,所以,对数据库的操作不会立即提交到数据库,而要调用DataContext的SubmitChanges方法,所有改动才会被提交到数据库。

Delete操作

      Delete操作用于从数据库中删除记录。表映射集合的DeleteOnSubmit方法可以实现这个操作。这个方法需要一个参数,就是要删除的实体类,这里不能直接传个ID去删除,要首先通过ID找到相应实体类,传给DeleteOnSubmit再删除。当然最后不要忘了SubmitChanges。

Update操作

      Update操作用于更新数据库中某已存在记录的信息。在Linq to Sql中,Update操作就是首先加载相应的实体类,修改其相应字段后,SubmitChanges就可以了。

Select操作

      Select操作用于从数据库中返回指定的记录。在Linq to Sql中,查询结果都是以实体类或实体类集合的方式返回的。其中实体类集合并不是List,如果想转为List,只需在返回结果上调用ToList方法即可。
      如果是查询单一记录,建议使用表映射集合的Single方法。至于查询参数,建议采用lambda表达式。如果你对lambda表达式不熟,可以参考这里http://msdn.microsoft.com/zh-cn/library/bb397687.aspx

其它相关示例代码

常用Select操作举例

      取得单个记录(ID为3的分类)

return dataContext.Categories.Single(c => c.ID == 3;

      取得全部记录(全部分类)

return dataContext.Categories;

      得部分记录(所属分类ID为3的公告,按ID降序排列)

return from b in dataContext.Bulletins
              where b.Category == 3
              orderby b.ID descending
              select b;

     取得部分记录并分页,最后转换为List(所属分类ID为3的公告并分页,pageSize为每页多少条记录,pageNo为第几页)

var bulletins = from b in dataContext.Bulletins
                       where b.Category == 3
                       orderby b.ID descending
                       select b;
return bulletins.Skip(pageSize * (pageNumber - 1)).Take(pageSize).ToList();

      好了,这篇文章就到这里了。希望能帮助大家快速上手Linq to Sql。关于Linq to Sql,还有许多丰富的功能和细节问题,篇幅原因不能详述,各位可以参考相关资料。

Creative Commons License

本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名张洋(包含链接),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系

Add your comment

30 条回复

  1. #1楼 Jeffrey Zhao      2009-04-22 00:11
    话说,搞一些Cheatsheet应该很好玩……
     回复 引用 查看   
  2. #2楼 Waitd Ding      2009-04-22 00:54
    居然没有沙发?
    楼主言辞貌似有些激动啊?
     回复 引用 查看   
  3. #3楼[楼主] T2噬菌体      2009-04-22 00:57
    @Waitd Ding
    呵呵,怎么会看出来激动啊。没有啊:-)
     回复 引用 查看   
  4. #4楼 孤星赏月      2009-04-22 08:24
    学习
     回复 引用 查看   
  5. #5楼 书生多命贱      2009-04-22 10:26
    楼主,有个问题想请教你一下,如果我要显示var query = from u in project select new { u.Id, u.CreateTime, userid = u.Users.Id};的时候,比如还有另外一个字段,是要根据userid的值用程序做相应处理然后显示出来的,应该怎么做呢?如果用var的话,做分层会 不会有问题??至少我现在new的返回类型是匿名的,谢谢
     回复 引用 查看   
  6. #6楼 书生多命贱      2009-04-22 10:48
    看了楼主别的文章,可能再写个通用的实体层,能解决我的问题,谢谢!!
     回复 引用 查看   
  7. #7楼 dreamskyyu      2009-04-22 11:07
    问一下,假设是有个表
    学号(主键nchar(10)) 姓名(nchar(5))
    3061521214 张三
    3061521215 李四
    怎么更新主键?假设把3061521214改为3061521219
    ?
     回复 引用 查看   
  8. #8楼[楼主] T2噬菌体      2009-04-22 11:24
    @书生多命贱
    一般来说,在Linq to Sql所构建的ORM层上面,会再建一个层,有点类似Facade,这个层提供一个简洁、统一的数据访问接口。而且,不建议将匿名类型直接返回给业务层,最好在Facade里做一下转换。
     回复 引用 查看   
  9. #9楼[楼主] T2噬菌体      2009-04-22 11:27
    @dreamskyyu
    首先获得3061521214的实体,将ID改为3061521219,然后SubmitChanges就可以了。
    顺带说一句,一般来说,不建议更新主键,如果你的主键需要更新,说明你的设计有问题,可能是使用了meaningful的主键,建议你改为使用meaningless主键。
     回复 引用 查看   
  10. #10楼 麦舒      2009-04-22 13:03
    楼主,在Linq to sql里,主键是不允许更改的。
     回复 引用 查看   
  11. #11楼 天啊321[未注册用户]2009-04-22 14:04
    Linq本来就以死,搞这个纯粹是浪费时间。时间就是金钱,我的朋友!
     回复 引用   
  12. #12楼 6ysnow[未注册用户]2009-04-22 14:52
    DeleteAllOnSubmit(??)
    这个函数的参数怎么填啊?
     回复 引用   
  13. #13楼[楼主] T2噬菌体      2009-04-22 18:24
    @麦舒
    哦。。。我还真不知道,因为我从来没有修改过主键。建议你使用meaningless的主键吧。主键最好还是不要修改
     回复 引用 查看   
  14. #14楼[楼主] T2噬菌体      2009-04-22 18:25
    @天啊321
    我一直都很珍惜时间,因为我的时间常常不够用,所以这个你可以放心。
    至于你对LINQ的观点,本人不予评论。
     回复 引用 查看   
  15. #15楼[楼主] T2噬菌体      2009-04-22 18:26
    @6ysnow
    传入一个实体类的ArrayList吧
     回复 引用 查看   
  16. #16楼 天涯行者      2009-04-22 19:10
    linq还没死了....
     回复 引用 查看   
  17. #17楼 Dove.Net      2009-04-23 12:25
    MS自己都宣布在后续版本中不支持LINQ TO SQL了。。。
     回复 引用 查看   
  18. #18楼[楼主] T2噬菌体      2009-04-23 13:48
    @Dove.Net
    微软原话是:
    We’re making significant investments in the Entity Framework such that as of .NET 4.0 the Entity Framework will be our recommended data access solution for LINQ to relational scenarios. We are listening to customers regarding LINQ to SQL and will continue to evolve the product based on feedback we receive from the community as well.

    大约的意思是:
    在.NET4.0中,Entity Framework 是以后LINQ技术应用于关系数据库存取的推荐解决方案,但以后也会根据从社区反馈的意见继续更新LINQ to SQL。
     回复 引用 查看   
  19. #19楼 code未登陆[未注册用户]2009-04-23 14:20
    linq 很好呀。
    -----------------
    linq to sql的更新和删除很是不爽呢
     回复 引用   
  20. #20楼 code未登陆[未注册用户]2009-04-23 14:20
    linq 很好呀。
    -----------------
    linq to sql的更新和删除很是不爽呢
     回复 引用   
  21. #21楼[楼主] T2噬菌体      2009-04-23 15:24
    @code未登陆
    关键是要符合规范去操作。如果做一些不符合约束规范的操作,可能会问题,如修改主键或多值依赖问题。
     回复 引用 查看   
  22. #22楼 NingDev      2009-04-24 09:22
    学习!
     回复 引用 查看   
  23. #23楼 yang天兵[未注册用户]2009-08-05 21:44
    好啊,你是我的偶像
     回复 引用   
  24. #24楼 snow man      2009-08-21 17:11
    太好了, 老大不去当微软的mvp实在太可惜了
     回复 引用 查看   
  25. #25楼 kevin.li      2009-10-30 09:54
    楼主,你好,我一直有个疑问,就是用lambda表达式进行分页操作,当表Bulletins中数据很大时,这种先获取全部,再Skip().take()的性能会如何呢。
     回复 引用 查看   
  26. #26楼 leeolevis      2009-11-24 08:47
    我想问:DataClassesDataContext视图里面的那个连接线是怎么来的?是主外键自动生成的吗?
     回复 引用 查看   
  27. #27楼 貂婵      2009-11-24 18:05
    private static DataClassesDataContext dataContext = new DataClassesDataContext();
    故意的吗~
    不知道是不是错误
     回复 引用 查看   
  28. #28楼 貂婵      2009-11-24 18:17
    抱歉,看错了~
     回复 引用 查看   
  29. #29楼 jianghua      2011-07-22 10:04
    写的不错哦
     回复 引用 查看   
  30. #30楼 绿tea      2012-01-10 11:13
    谢谢,先转了!
     回复 引用 查看   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

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

0 1440871 Qp180OVMDf4=