走进Linq-Linq to SQL How do I(1)

How Do I第一篇,难度系数50,定位为入门级。

 

上一篇对Linq to SQL做了一个大致的介绍,从这一篇起,将对要完成一项Linq to SQLCase要做的一些事情,主要从细节上做一些讲解。不会很深入,但是却又是必须的。为了使讲解不落于泛泛而谈,我们首先来构建实例:

还是以博客园的系统为例子,既然称Linq to SQL为一个ORM框架,ORM,对象-关系 映射,既然O在前关系在后,说明O为重,关系是根据O得来的,那么我们就先新建一些Entity Object吧。在一个博客系统里最常见的就是User,Blog,Post

一个用户有且仅有一个博客,而一个博客可以有零篇或者多篇博客文章。根据这个描述我们来建立Entity

 

Step 1 建立实体对象

 

User
Blog
Code
我们先不要谈论这个设计是否合理,也不要谈论这三个实体是贫血模型还是充血模型。

对象建立好了,我们来建设数据库表吧:users表,blogs表,posts(为什么表都用复数?Rails里学过来的,ActiveRecord模式里,表里每条记录都对应着一个对象,所以表用复数表示,这是一种约定)

 

Step 2:创建数据库表

Users表:



Blogs表:

Posts表:

我们注意到,用户类里的LeaveTime离开时间在数据库里并没有对应的字段,因为这个是用来临时记路用户离开离开博客园的时间的,这样可以用来做一些在线统计,所以无需持久化。

 

Step 3:建立映射

对象也有了,关系也有了,那剩下的是什么?对,就是映射,我们怎样将对象映射到数据库表上?

我们从最简单的Post开始吧:

/// <summary>
    
/// 博客文章类
    
/// 这个类和数据库里的posts表对应,由于
    
/// 类名和表名不一样,所以需要显式的指明Table特性的Name属性
    
/// 如果是一致的则无需指明了
    
/// </summary>
    [Table(Name="posts")]
    
public class Post
    {
        
/// <summary>
        
/// 文章Id
        
/// 这个对应posts表里的postid
        
/// 这个字段还是一个主键,所以Column特性的IsPrimaryKey属性为true
        
/// </summary>
        [Column(Name="postid",IsPrimaryKey=true)]
        
public int Id { getset; }
        
/// <summary>
        
/// 文章从属的博客
        
/// 这个对应着表里的blogid,名字一样所以只需要加个Column特性就可以了
        
/// 下面几个也是一样的意思
        
/// </summary>
        [Column]
        
public int BlogId { getset; }
        
/// <summary>
        
/// 标题
        
/// </summary>
        [Column]
        
public string Title { getset; }
        
/// <summary>
        
/// 内容
        
/// </summary>
        [Column]
        
public string Body { getset; }
        
/// <summary>
        
/// 发表时间
        
/// </summary>
        [Column]
        
public DateTime CreateDate { getset; }
}
仔细看看注释里面的说明,很简单吧,在类上面加Table特性,属性上加Column特性。

映射建完了,我们可以施加一些操作了。

在上一篇文章里介绍了,Linq to SQL的入口点是DataContext类,这个类主要做这么几件事情:

将我们用C#写的这个查询翻译成SQL语句,当然也并不是他全权负责翻译工作。

执行查询

连接的管理,这样我们就不用写啥Connection了啊,也不用担心数据库的连接和关闭的问题。

 

Step 4:执行查询

那我们就首先实例化一个DataContext类吧(为了好测试,使用一个控制台程序)

DataContext dbContext = new DataContext(ConfigurationManager.ConnectionStrings["CnBlogs"].ConnectionString);
从这里可以看出,DataContext需要一个连接字符串,在DataContext里,我们打交道最多的就是GetTable<TEntity>()方法,这里的TEntity就是我们上面的那个Post了,带有映射的实体,这个方法返回一个Table<TEntity>对象:
Table<TEntity> posts = dbContext.GetTable<Post>();
实际上,Table<TEntity>实现了IEnumerable<TEntity>接口,那这里实际上是返回了一个IEnumerable<Post>系列,那我们可以对posts进行遍历了:
foreach(var post in posts)
       Console.WriteLine(post.Title);
实际上,如果光返回一个Table<TEntity>,而不在上面施加任何的操作,遍历的时候是返回整个表的数据,在上一篇我还提到,为了跟踪,我们最好将dbContext.Log给显示出来。如果你添加了下面这行代码:
dbContext.Log = Console.Out;
(对于这行代码,如果你使用的是控制台程序,那么输出就会显示在命令窗口里,如果你用的是WinForm程序调试,那么输出会显示在VSoutput窗口里)

那你将会从控制台里得到下面的东东:


这就是Linq为你生成的SQL语句,是不在是比你写的还标准啊,呵呵。

当然,我们常常做的肯定不是像上面这样的全查询,我们还要附加一些条件的。

上一篇还提到过:由于Table<TEntity>实现了IEnumerable<TEntity>接口,那么所有的查询表达式都可以在这里使用了,这样你先前在Linq to Objects里学到东西现在又有了新的用途了:

比如我们可以对BlogId做筛选:

var posts = from post in dbContext.GetTable<Post>()
         
where post.BlogId == 2
         select post;
(PS:我在写Linq语法的时候,因为Linq的风格太像SQL语句了,所以一些地方比如 ==我老写成=,而&&经常写成”and”,希望大家不要烦我这样的低级错误,呵呵)

看看这次生成的SQL语句:

可以看到生成的SQL语句对blogid做了筛选,而且值得庆祝的是,Linq to SQL为我们生成的SQL语句还使用的是参数,她并不是仅仅将数值和语句组合在一起。关于使用参数而不是拼凑起来的SQL语句的好处在这里啰嗦两句,主要有两点:

这样可以有效的SQL注入攻击,这个hack手段曾经让很多网站吃尽了苦头。

使用参数可以利用Sql ServerSQL语句的缓存和预编译作用,因为使用参数的SQL语句可能多次用到,而拼凑起来的SQL语句却和特定的查询有关。

这样的筛选还不能体现出Linq的智能了,请再看这个:

var posts = from post in dbContext.GetTable<Post>()
         
where post.Title.StartsWith("y")
         select post;

面对这样的一个Linq查询,Linq to SQL居然知道将它翻译成LIKE子句,从上图倒数第二行最后中括号里面的东西可以看出,Linq to SQL还知道将传入的参数设置为y%,我不得不惊叹:太强大了。

但是不是所有的C#的方法都可以使用呢?答案是否定的。比如:

var posts = from post in dbContext.GetTable<Post>()
         
where post.Title.StartsWith("y")
         select post.CreateDate.ToString(
"yyyy-MM-dd");
我想对最后查出来的文章的创建时间做个格式化,居然报错了,说不支持这种“翻译”。那看来每次我们做查询的时候是要先try一下。

上面只是做了筛选的实例,你还可以去尝试一下排序和分组,这个和Linq to Objects里的用法是一样的,你可以到前面的文章里复习一下。

 

Step 5:插入对象

本系列的文章到现在为止都是在介绍查询,难道Linq to SQL只能做查询么?不能向数据库添加数据?,肯定不是的:

Table<Post> posts = dbContext.GetTable<Post>();
//先实例化一个新的要插入的对象
Post post = new Post();
post.BlogId 
= 2;
post.Title 
= "test";
post.Body 
= "test,test,test,test";
post.CreateDate 
= DateTime.Now;
//调用Table<TEntity>的InsertOnSubmit方法
posts.InsertOnSubmit(post);
//把改变提交到数据库,这个时候才真正执行了
dbContext.SubmitChanges();
//提交修改后,你就可以查询新插入的post的Id了
Console.WriteLine(post.Id);
生成的SQL语句:

一个简单的不能再简单的insert into语句,加上一条返回新插入记录的标识值的语句。

如果你按照本文所说的一步步往下来,在执行上面的插入的时候肯定会碰到异常,这是因为,Id对应的数据表字段postid是该表的主键,你不应该在插入的时候赋值,有人说我实例化对象的时候确实没有给postId属性赋值啊,但是.net会在后台为我们将Id赋值为0,所以你要对映射对象做一下修改:

[Column(Name="postid",IsPrimaryKey=true,IsDbGenerated=true)]
public int Id { getset; }
不仅仅数据表的主键要加这个属性,如果对于那些在数据库里设置了默认值的,你并不像用程序插入的时候,你也得使用,比如这里的CreateDate

 

Step 6:更新数据

可以插入肯定就可以更新了,下面就来看看如何更新呢:

做更新的时候,你首先得从数据库查询出该对象,然后对该对象的属性进行修改,最后更新到数据库:

var posts = from post in dbContext.GetTable<Post>()
        
where post.BlogId == 2
        select post;
foreach (var post in posts)
     post.BlogId 
= 5;

dbContext.SubmitChanges();
Linq to SQL也会自动的帮你生成Update语句了。

有了插入,更新,查询就差一个Delete CURD就全了,对于delete更简单了,你只要调用Table<TEntity>DeleteOnSubmit方法就行了,这里就不再详述。

 

后记

这篇文章主要关注How Do I上面,对一个简单的单表CURD做一个比较全面的介绍,下一篇会更深入一些,将会涉及到多表的连接,两个表之间的关系怎样反应到映射上来,还将探讨一下Linq to SQL中的延迟计算的问题。本篇定位为入门级,只期望给一些初学者或者未接触过Linq的朋友一些提示。

Tag标签: Linq,Linq to SQL
posted @ 2008-08-01 09:07 横刀天笑 阅读(1983) 评论(25)  编辑 收藏 网摘 所属分类: 走进Linq

  回复  引用  查看    
#1楼2008-08-01 09:21 | Gray Zhang      
建议楼主详细说说DLINQ这该死的更新的问题,通常情况下,没有rowversion字段的话attach是会出错的,再者,在分布式的环境下这个update就更难用了
  回复  引用    
#2楼2008-08-01 09:28 | 井辰[未注册用户]
不错!!期待下一篇关于表连接和复杂查询.
另外,希望篇幅可以短一些,因为--我懒...

  回复  引用    
#3楼2008-08-01 09:42 | 5254341[未注册用户]
看完,入门
  回复  引用    
#4楼2008-08-01 10:00 | paul-li[未注册用户]
支持!期待!
建议:做份源码下载

  回复  引用    
#5楼2008-08-01 10:04 | flying_liu[未注册用户]
不错,很好,期待更复杂的多表操作.
  回复  引用  查看    
#6楼2008-08-01 10:15 | 姜敏      
请教LZ
lingq中的运算符=>是什么意思,怎么来用呢?

  回复  引用  查看    
#7楼2008-08-01 10:16 | 姜敏      
string[] numbers = { "0042", "010", "9", "27" };
int[] nums = numbers.Select(s => Int32.Parse(s)).OrderBy(s => s).ToArray();
能否帮忙解释下上面两条语句的意思呢?刚接触lingq,没太看明白.

  回复  引用  查看    
#8楼[楼主]2008-08-01 10:31 | 横刀天笑      
@Gray Zhang
这一篇定位在入门级,在后面两篇文章中我会涉及到的,谢谢你的建议

@井辰 写完后我也发现太长了
@5254341 呵呵,谢谢支持
@paul-li 我也想放源码,不过既然文中把所有源码都贴出来了,还是自己输入吧,这样印象更深刻点,你也许发现,我所有的文章都不提供源码下载,意图就是如此
@flying_liu 谢谢支持

@姜敏 这个Lambda表达式,请看我这篇文章:
http://www.cnblogs.com/yuyijq/archive/2008/07/16/1244736.html" target="_new">http://www.cnblogs.com/yuyijq/archive/2008/07/16/1244736.html
上面两句的意思是:将字符串数组转型为整形数组,然后排序,你可以看我的走进Linq系列的前几篇文章

  回复  引用  查看    
#9楼2008-08-01 10:44 | Gray Zhang      
@姜敏
按=>的方向看,比如
i => i++
就是"进去i,出来i++",也就是
int SomeMethod(int i)
{
return i++;
}

  回复  引用  查看    
#10楼2008-08-01 10:47 | 姜敏      
@Gray Zhang

---引用------
按=>的方向看,比如
i => i++
就是"进去i,出来i++",也就是

----------
这个比喻真的是清晰无比啊,谢谢了.

  回复  引用  查看    
#11楼[楼主]2008-08-01 10:51 | 横刀天笑      
@Gray Zhang
嗯,果然清晰啊,不过建议@姜敏还是打开IL看一看后面发生的东西,这样印象就更深刻了

  回复  引用  查看    
#12楼2008-08-01 10:54 | 真见      
期待 周末话题。
  回复  引用  查看    
#13楼[楼主]2008-08-01 11:00 | 横刀天笑      
@真见
呵呵,正在策划ing

  回复  引用  查看    
#14楼2008-08-01 11:00 | Caling Xie      
在上一篇文章里介绍了,Linq to SQL的入口点是DataContext类,这个类主要做这么几件事情:

将我们用C#写的这个查询翻译成SQL语句,当然也并不是他全权负责翻译工作。
----------------------------------------------------

有点疑问。
翻译Sql语句的工作 是用 相应的Provider 来做的吧
Linq to Sql 的 Provider 是 System.Data.Linq.SqlClient Namespace下面的 SqlProvider 类来实现的对吧.

  回复  引用  查看    
#15楼2008-08-01 11:05 | 小生      
不錯﹐一直關注
  回复  引用  查看    
#16楼[楼主]2008-08-01 11:10 | 横刀天笑      
@Caling Xie
嗯,你说的对,所以我后面加了个“当然也并不是他全权负责翻译工作”,DataContext只是个入口点


@小生 谢谢你的关注

  回复  引用  查看    
#17楼2008-08-01 11:31 | 非主流程序员      
那事务怎么办?在更新,删除的时候我需要用事务啊?怎吗没看见事务代码呢?难道linq自己封装了么?
  回复  引用  查看    
#18楼2008-08-01 11:46 | 姜敏      
int[] nums = new int[] { 6, 2, 7, 1, 9, 3 };
IEnumerable<int> numsLessThanFour = nums
.Where(i => i < 4)
.OrderBy(i => i);
foreach (int num in nums)
Console.WriteLine(num);
这个语句运行后为什么输出的结果是6, 2, 7, 1, 9, 3 呢?没有任何变化.
i => i < 4,这个表达式,我是否可以这样理解,当数字6输入的时候这个表达式返回值是否是false,此时应该不会输出才对啊.为什么结果却输出了呢?i => i < 4这个表达式究竟返回值是什么呢?望指点下.

  回复  引用  查看    
#19楼2008-08-01 11:59 | Gray Zhang      
@非主流程序员
LINQ的SubmitChanges有个事务管理,如果还需要上层事务,可以通过DataContext实例的Connection得到DbConnection再使用Open和BeginTransaction完成,再简单的有TransactionScope

  回复  引用  查看    
#20楼2008-08-01 12:00 | Gray Zhang      
@姜敏
foreach (int num in nums)
应该是foreach (int num in numsLessThanFour)
这种小错误不应该犯哦

  回复  引用  查看    
#21楼2008-08-01 12:20 | Caling Xie      
--引用--------------------------------------------------
横刀天笑: @Caling Xie
嗯,你说的对,所以我后面加了个“当然也并不是他全权负责翻译工作”,DataContext只是个入口点

--------------------------------------------------------
恩,共同学习.

  回复  引用  查看    
#22楼[楼主]2008-08-01 13:05 | 横刀天笑      
@非主流程序员
事务Linq是支持的,而且你还可以使用.net自己的事务,这个在后续的篇幅里我会进行介绍的,敬请留意


@姜敏
如@Gray Zhang所说,你遍历的对象搞错了,呵呵

  回复  引用  查看    
#23楼2008-08-01 21:52 | airwolf2026      
俺第一次看俺同事用linq的时候,他当时是把数据库表拖到一个后缀叫啥...来着的....当时就觉得啊?要这样用,那不是麻烦死了...万一改表....
哈哈.还好有楼主这样的文章.(*^__^*) 嘻嘻……

  回复  引用  查看    
#24楼[楼主]2008-08-01 23:27 | 横刀天笑      
@airwolf2026
呵呵,那个也挺简便的,后面我也会讨论的,你可以利用那个设计器,然后自己改改,手动自动相结合

  回复  引用  查看    
#25楼2008-08-02 08:24 | Gray Zhang      
@airwolf2026
用dbml自动化设计也还可以的,就是生成的东西不是很好看罢了,改了表就去刷新一下那个dbml,自动更新的,有额外的东西就写partial类

发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

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

0 1257842




相关文章:

相关链接: