到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(完结篇)
前 言
各种懒惰,各种拖沓,终究是要动笔写终结篇了,在这个系列的前几篇文章里我们主要学习linq的基础语法以及他对内存数据的操作等,那么本篇文章我们将讨论学习最为大家所熟悉的,也是最受争议的 Linq To SQL,再次强调,如果你到目前为止认为LinqToSql就是linq的话,有以下几种方式可共君选择:1.把这个系列的前面几篇文章给读了。2.到菜市场卖块豆腐给撞了。3.(MM可以忽略跳过哈)把屁股洗干净,让大家把你菊花给爆了。
用 意
Linq To Sql 相对现在来说,不可否认它已经过时了,伴随着vs2010和Entity Fromwork 4的出现,linq to sql 退出历史舞台是必然的,因为EF4比之更强大更完善。但是linq to sql 并不是一无是处,有很多东西它与EF4是相通的,简单的了解linq to sql并无害处,并且还可以对EF4有一定的过渡帮助。
由于这个主题能讲的内容非常多,篇幅关系不能全部说完,在这里只能简单地向大家分享个大概,敬请谅解。
目 录
什么是Linq To Sql
摘自MSDN:LINQ to SQL 是 .NET Framework 3.5 版的一个组件,提供了用于将关系数据作为对象管理的运行时基础结构。 在 LINQ to SQL 中,关系数据库的数据模型映射到用开发人员所用的编程语言表示的对象模型。 当应用程序运行时,LINQ to SQL 会将对象模型中的语言集成查询转换为 SQL,然后将它们发送到数据库进行执行。当数据库返回结果时,LINQ to SQL 会将它们转换回您可以用您自己的编程语言处理的对象。简单的理解就是我们对数据进行实体化操作,例如我们可以吧每章表作为一个数据实体封装操作。
生成实体
在linq to sql中,实体对象时一个非常重要的环节,他是对数据表,视图等对象的映射,没有实体就谈不上linq to sql了。也许有些老手会反对为什么是生成实体而不是手写实体,生成实体会产生冗余代码。个人认为对于初学者来说,我们很多手头上的项目通常总是先有库表后有代码,那么我们会针对库表进行编写实体,这真的是个体力活没有任何捷径可言,一张一张表的写实体非常痛苦。所以干脆让大家直接生成,即省事又方便,而且也可以学到怎样编写比较专业的实体。
既然是生成实体,那么这肯定需要一些而外的工具了,在这里MS自VS2008起就给我们提供了这么一个工具SqlMetal.exe命令工具,可为 LINQ to SQL 组件生成代码和映射。那么接下来我们演示如何生成实体
1.假设我们有一张用户表,如图:
2.打开VS2008命令行工具,如图:
3.输入命令,生成数据实体。注意:生成实体文件分别有2种,一种是*.cs文件和*.xml映射文件的组合方式,另一种则是*.dbml文件,只能二选一,切记!!
首先我们先生成第一种:*.cs和*.xml组合方式,如图:
4.根据命令指定的位置,我们可以看到对应的生成文件,如图:
5.将生成的文件放入我们的项目中,如图:
注意,在这里我们要选中“linqToSqlMap.xml”,在属性对话框里设置始终复制到输出目录里,如图:
6.接下来,我们看看类文件,生成了那些实体代码,如图:
xml 映射配置文件
从上图看,生成的代码貌似有点多,但是这要比我们自己手写代码更专业。实体文件主要分为两部分,一是数据库上下文关联类 LinqToSqlDemo,二是对应表的实体类 Users 。到这里我们对第一种组合方式的实体生成就已完成。
接下来我们看看要是我们使用的是生成*.dbml文件又会什么样的场景呢。
1.同上,输入命令生成文件,如图:
2.查看生成文件,如图:
3.将生成的DBML文件放入项目里,我们可以看到,生成的只有一个文件,但是当添加到项目里时,项目会自动生成layout和designer两个文件,如图:
4.有意思东西来了,右键点击dbml文件,选择视图设计器,我们可以在编辑框中得到实体映射编辑视图
5.我们看一下这个时候在*.designer.cs文件里生成了哪些内容
可以看到,生成的实体文件和上一种方式生成的实体文件区别不大,由于没有了XML映射配置,所以这里采用的是特性映射配置,在Users实体类中我们可以看到附加了一些如Table,Column的特性标记。
到此我们对实体生成的做法有了一个基本的认识,接下来我们看看linq 是怎么通过实体进行增删改查的
LinqToSql 增删改查
1.DataContext 实例
既然要对数据进行CURD,那么我们就需通过对数据库上下文关联类的实例进行操作之,即DataContext派生类,如上面生成的LinqToSqlDemo派生类。
由于DataContext 具有多个重载构造函数,在这里针对先前的2种实体生成方式对具体的2个构造函数进行描述,其他的就不逐个介绍少了。详情可以查阅MSDN
如果使用的是*.cs 与 *.xml组合方式的实体映射,那么在构造实例应该如下
如果使用的是*.dbml文件的实体映射,那么就简单了,直接提供数据库连接字符串就可以,因为在派生类的内部已经指定使用特性映射配置。见下图
2.查询数据
// 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型
dataContext.Log = Console.Out; var users = from usr in dataContext.Users select usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1}", usr.UserName, usr.Email); } Console.Read();
输出结果:
如果我们带上where 条件,查询的操作又是如何的呢
// 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 dataContext.Log = Console.Out; var users = from usr in dataContext.Users where usr.UserName == "张三" select usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1}", usr.UserName, usr.Email); } Console.Read();
输出结果:(这里我们可以看到sql使用了参数化查询)
3.关联查询
往往在实际项目中我们会涉及到几个表的关联查询,那么LinqToSql有时怎样支持的呢。
假设多了一张用户详细表,他与用户表的关系如下:
生成实体(*.dbml):
在生成的实体代码*.designer.cs文件中我们会看到,Users 实体类多了一个EntityRef<UserDetails> _UserDetails私有字段,而在UserDetails实体类中对了一个EntityRef<Users> _Users私有字段,泛型类EntityRef<T>是关键,他是实体之间关联关系处理主要对象。篇幅关系详情请查阅MSDN点击这里
查询:
// 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 dataContext.Log = Console.Out; var users = from usr in dataContext.Users select usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1},年龄{2},住址:{3}", usr.UserName, usr.Email, usr.UserDetails.Age, usr.UserDetails.Address); } Console.Read();
输出结果:
从结果我们可以看到,首先是把用户表给查了,然后根据linq延迟加载的特性,只有真正使用时才执行,因此当需要查看用户详细信息时才会去执行用户详细查询,这样就带来了很大弊端,如果数据量大时那么这样的查询开销就大了,大大降低了程序的效率。那么这个问题是否可以解决呢?答案是肯定的,请看下面代码:
// DataLoadOption数据导入操作对象,它可以告诉linq在执行查询是否延迟 // 查询对象的子对象 var loadOption = new DataLoadOptions(); // 设置数据导入对象关联关系 loadOption.LoadWith<Users>(usr => usr.UserDetails); dataContext.LoadOptions = loadOption; var users = from usr in dataContext.Usersselect usr; foreach (var usr in users) { Console.WriteLine("用户名:{0},Email:{1},年龄:{2},住址:{3}", usr.UserName, usr.Email, usr.UserDetails.Age, usr.UserDetails.Address); } Console.Read();
输出结果:
从log我们可以看到这样就是一条语句查出来数据结果集。注意,这里演示的是2表关系的查询,如果当我们再多出一个表,而这表是与用户详细表形成关联关系的时候那么,DataLoadoption 就没法解决了一次性查出,而又回到类似上一个样例分次查出来。例如:
假设新增一张表(UserDetails2):
关系如下:
生成*.dbml:
查询:
// 假设我们已构造了DataContext对象实例dataContext // 属性log是实例内部的操作日志输出,它属于Stream类型 dataContext.Log = Console.Out; // DataLoadOption数据导入操作对象,它可以告诉linq在执行查询是否延迟 // 查询对象的子对象 var loadOption = new DataLoadOptions(); // 设置数据导入对象关联关系 loadOption.LoadWith<Users>(usr => usr.UserDetails);
// 加入对表UserDetails2的关联 loadOption.LoadWith<UserDetails>(dtl => dtl.UserDetails2); dataContext.LoadOptions = loadOption; var users = from usr in dataContext.Users select usr; foreach (var usr in users) { Console.Write("用户名:{0},Email:{1},年龄:{2},住址:{3}", usr.UserName, usr.Email, usr.UserDetails.Age, usr.UserDetails.Address); foreach (var dtl in usr.UserDetails.UserDetails2) { Console.Write(",性别:{0}", dtl.Sex); } Console.Write("\r\n"); } Console.Read();
查询结果:
这里我们可以看到,在第一个层级使用了一次查询,但是往下一个层级查询UserDetails2表时又变回了分开查询,效率就大打折扣了。
4.数据新增
通过执行Table<TEneity>类的方法 InsertOnSubmit 新增数据
var dataContext = new LinqToSqlDemo(CONN_STR); // 新增用户 dataContext.Users.InsertOnSubmit( new Users { UserId = 4, UserName = "赵六", Email = "wangw@xxx.com", UserDetails = new UserDetails { Address = "xxx大道", Age = 25, UserId = 4 } }); // 注意,这里是必须的,提交修改 dataContext.SubmitChanges();
5.数据删除
通过执行Table<TEntity>类的方法 DeleteOnSubmit 进行删除数据
var dataContext = new LinqToSqlDemo(CONN_STR); var user = (from usr in dataContext.Users where usr.UserName == "张三" select usr).SingleOrDefault(); dataContext.Users.Attach(user); // 上传数据 dataContext.Users.DeleteOnSubmit(user);
6.数据更新
在对实体更新完毕后直接调用DataContext的SubmitChanges方法即可。
var dataContext = new LinqToSqlDemo(CONN_STR); var user = (from usr in dataContext.Users where usr.UserName == "张三" select usr).SingleOrDefault(); user.UserName = "张三三"; dataContext.Users.Attach(user, false); // 更新数据 dataContext.SubmitChanges();
注意,在数据删除和更新的时候我们可以看到都执行了Table<TEntity>类的Attach方法,它是允许反序列化的实体与 DataContext 的新实例关联,以便可以从数据库更新或删除这些实体,篇幅关系,详情可以查阅MSDN。
拓 展
通过上面我们对LinqToSql有了一个最基本的知识,由于篇幅关系,没法在一篇文章里逐一详谈,下面是一些进阶内容,希望对大家有所帮助
Linq To Sql 白皮书(英文版,非常详细,想在这方面有深入研究的就花一点时间啃吧)
优缺杂谈
总的来说linq To sql 还是不错的,把讨厌的sql命令从我们项目里移除出去,从实体出发,更贴近OOP。不过有时候生成的代码虽然专业,但是冗余的还是有不少,同时对linq To Sql 生成的SQL语句确实不太感冒效率并不是很高,在查询时还会带上不需要的字段,因此如果是小项目快速开发还行,大项目还是请各位看官多斟酌斟酌吧。
总 结
到这,关于linq的这个系列算是结束了,通过这个系列大家应该对linq有了一个最基本的认识,因此光光掌握这个系列所结束的知识在实战应用中是不够的,需要各位看官在往后的自我学习中去查阅更多的相关知识做更多的实践,园子里对linq的介绍文章有很多,大家都不妨去阅读一下。正所谓师傅领进门,修行靠个人,希望大家都学习linq,掌握linq,个个都是linq高手,以便鄙人向大家学习,三人行必有我师焉。最后谢谢您的阅读,有说得不对之处请指正,谢谢。
索 引
到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(上篇)
到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(中篇)
到现在为止你还未触碰LINQ,那进来吧 —— LINQ入门(下篇)