本篇是Linq to SQL How do I的第二篇,难度系数100,定位为进阶级。
对象之间的关系
既然是对象-关系映射,各个表之间肯定不是独立存在的(如果都是独立存在的,也没有必要用关系数据库了),那么就必然涉及到几个表之间的联合了。
Linq to SQL和SQL语句一样,支持两种方式的联合:
1. 利用where子句,对两个表进行查找
2. 使用join子句
我们还是用例子来说明吧,现在要对blogs和posts进行查询,传入一篇文章的id的时候,找出这篇文章相关信息的同时还要找出这篇文章属于哪个博客,接着上篇的例子,我们首先得给Blog类加上映射:
博客类映射(Blog)

Code
/**//// <summary>
/// 博客类
/// </summary>
[Table(Name="blogs")]
public class Blog
{
/**//// <summary>
/// 博客标识
/// </summary>
[Column(Name="blogid",IsPrimaryKey=true,IsDbGenerated=true)]
public int Id { get; set; }
/**//// <summary>
/// 用户标识,和用户相关联
/// </summary>
[Column]
public int UserId { get; set; }
/**//// <summary>
/// 博客的中文名
/// </summary>
[Column]
public string Name { get; set; }
/**//// <summary>
/// 创建时间
/// </summary>
[Column]
public DateTime CreateDate { get; set; }
/**//// <summary>
/// 一个博客有零篇或多篇文章,这个就先不管了
/// 不建立映射关系
/// </summary>
public IList<Post> Posts { get; set; }
}
首先获取Post和Blog的Table<TEntity>的对象,然后施加操作:

Code
Table<Blog> blogs = dbContext.GetTable<Blog>();
Table<Post> posts = dbContext.GetTable<Post>();
var result = from blog in blogs
from post in posts
where post.BlogId == blog.Id && post.Id == 2
select new {
BlogName = blog.Name,
PostTitle = post.Title,
PostBody = post.Body
};
foreach (var item in result)
Console.WriteLine(item.BlogName);
下面是Linq to SQL为我们生成的SQL语句,从上面可以看出来是用where做联结的条件来进行的,这种联结的方式是不被推荐的,存在于ANSI-82 SQL的标准中

附件:
您所在的用户组无法下载或查看附件推荐的方式是使用join子句:

Code
Table<Blog> blogs = dbContext.GetTable<Blog>();
Table<Post> posts = dbContext.GetTable<Post>();
var result = from blog in blogs
join post in posts on blog.Id equals post.BlogId
where post.Id == 2
select new {
BlogName = blog.Name,
PostTitle = post.Title,
PostBody = post.Body
};
foreach (var item in result)
Console.WriteLine(item.BlogName);
生成的SQL语句是:

附件:
您所在的用户组无法下载或查看附件大家看到,Linq to SQL使用inner join子句。
但是Linq to SQL在使用join的时候并不是像SQL那样宽松,把上面的SQL语句贴下来:

Code
SELECT [t0].[blogname] AS [BlogName],[t1].[Title] AS [PostTitle],[t1].[Body] AS [PostBody] FROM [blogs] AS [t0] INNER JOIN [posts] AS [t1] ON [t0].[blogid] = [t1].[BlogId] WHERE [t1].[postid] = @p0
在SQL语句里,ON子句的[t0].[blogid] = [t1].[BlogId]左右次序是没有关系的:[t0].[blogid] = [t1].[BlogId]或[t1].[BlogId] = [t0].[blogid]的结果是一样的,但是在Linq to SQL里并不是如此,她要求查询的对象和ON子句里面的东西的位置是一一对应的:

附件:
您所在的用户组无法下载或查看附件 如上图,用框子框起来的四部分,橙黄色的两个,蓝色的两个,位置都是一一对应的,blog排在第一位,那么blog.Id就要排在on的第三位,1,3和2,4这种对应关系。这又是为什么呢?因为这种查询表达式最后要被转换成方法调用,而扩展方法Join的原型是:

Code
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>
(
this IEnumerable<TOuter> outer,
IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector
)
大家看到没,第一个参数和第三个参数都是outer相关的,第二个参数和第四个参数都是inner相关的,最后一个是on条件时的条件运算符,如:equals。将上面的方法原型和上面那张图对应:blogs就是这里的outer,而blog.Id对应着这里的outerKeySelector,posts就是这里的inner,post.BlogId对应着这里的innerKeySelector,equals就是这里的resultSelector。实际上,如果你使用VisualStudio2008编写代码的时候,如果不按照这个顺序敲入代码,vs是不会给你智能感知的。
大家也许发现上面的join生成的SQL语句只有inner join(inner join也称之为等值连接,只返回两个表联结字段相等的行),在我们这个需求里,这样使用是合理的:查找一篇文章,如果该文章对应的博客不存在的话,这篇文章也不应该显示。但是,如果我们期望使用outer join咋办呢?Linq为我们提供了一个DefaultIfEmpty()的扩展方法:

Code
var result = from post in posts
join blog in blogs on post.BlogId equals blog.Id into joinblogs
from joinblog in joinblogs.DefaultIfEmpty()
where post.Id == 2
select new {
BlogName = joinblog.Name,
PostTitle = post.Title,
PostBody = post.Body
};
这样生成的SQL语句就是这样的了:

附件:
您所在的用户组无法下载或查看附件看到上面说了那么多,也许有人会问:一般的ORM框架都能在对象之间建立关系来反映数据库之间的关系,而操作的时候这种关联是自动的,难道Linq to SQL必须让程序员手动的使用这种join的方式来处理对象之间的关系么?答案是否定的。
一对多关系(One-to-Many)
在上面的映射中,Blog还有一个属性:Posts,我们并没有做什么处理,实际上这个应该是根据Blog的Id属性关联到Post的BlogId属性,进行关联的。
为此我们要修改一下这个Blog的映射:

Code
/// <summary>
/// 一个博客有零篇或多篇文章,
/// </summary>
[Association(OtherKey = "BlogId")]
public EntitySet<Post> Posts { get; set; }
使用Association建立映射关系,她有一个OtherKey属性,该属性指定为被关联的那个对象中做连接的那个属性(外键)(这里被关联的就是Post类,做连接的那个属性就是BlogId,她也是posts表的外键),还要注意的是,我们要把原来使用的IList<Post>换成EntitySet<Post>,EntitySet<TEntity>是Linq新提供的一个类,在System.Data.Linq命名空间下。
下面来看看例子:

Code
var result = from blog in dbContext.GetTable<Blog>()
where blog.Id == 1
select blog;
foreach (var item in result)
{
Console.WriteLine(item.Name);
if (item.Posts == null || item.Posts.Count <= 0) return;
foreach (var post in item.Posts)
{
Console.WriteLine(post.Title);
}
}
我们只要对blog进行查询就ok了,和她关联的Post自动的会映射过来的。
上面的例子只表明了在Blog类里,如何关联到该博客的所有Post,如果我在Post里想关联到该Post属于哪个博客呢?
在Post类里添加:

Code
//注意,用的是EntityRef<TEntity>
private EntityRef<Blog> _blog;
//从这里可以看出Storage属性的作用了吧,这里的ThisKey指定的是
//本类里面的BlogId,一个外键,并不是主键了哦。
[Association(Storage="_blog",ThisKey="BlogId")]
public Blog Blog
{
get { return _blog.Entity; }
set { _blog.Entity = value; }
}
这样就ok了,你查一个Post的时候会自动的把其所属的博客也给弄出来哦。而且这个EntityRef还是支持延迟加载的。
多对多的关系(many-to-many)
Linq to SQL默认是不支持多对多的关系的,所以也没有针对多对多关系的Attribute和Query,不过网上有很多多对多关系的解决方案,在这里我就不做介绍了,我给个连接:
http://blogs.msdn.com/mitsu/arch ... ng-linq-to-sql.aspx这位大哥的方案是一个通用的,你可以下载他的代码应用到你的项目当中,有时间我会把这个翻译一下。
一对一的关系(one-to-one)
一对一的关系Linq to SQL是支持的,你只要给关系的两边都加上EntitySet<TEntity>属性就可以了,下面用实例做个演示:
在我们的实例中,每个用户对应着一个博客,每个博客也只对应着一个用户,这是个一对一的关系:
用户类映射(User)

Code
/**//// <summary>
/// 用户类
/// </summary>
[Table(Name="users")]
public class User
{
/**//// <summary>
/// 用户标识
/// </summary>
[Column(Name="userid",IsPrimaryKey=true,IsDbGenerated=true)]
public int Id { get; set; }
[Column]
public int BlogId { get; set; }
/**//// <summary>
/// 该用户对应的博客,
/// 一个用户有且仅有一个博客
/// </summary>
[Association(ThisKey="BlogId",OtherKey="Id",IsUnique=true)]
public EntitySet<Blog> Blog { get; set; }
/**//// <summary>
/// 用户名
/// </summary>
[Column]
public string UserName { get; set; }
/**//// <summary>
/// 密码
/// </summary>
[Column]
public string Password { get; set; }
/**//// <summary>
/// 昵称
/// </summary>
[Column]
public string NickName { get; set; }
/**//// <summary>
/// 用户离开时间,临时存储用户离开时间的,数据库
/// 里并没有对应的字段,所以不做映射
/// </summary>
public DateTime LeaveTime { get; set; }
}
Blog类前面已经出现过,我们就不列出全部代码,将Blog类修改如下:

Code
[Column]
public int UserId { get; set; }
[Association(ThisKey="UserId",OtherKey="Id",IsUnique=true)]
public EntitySet<User> User { get; set; }
就是添加一个EntitySet<User> User的属性而已。
看看操作:

附件:
您所在的用户组无法下载或查看附件通过查询结果可以看出,他们之间的关系建立了。
后记
本来这一篇我准备了好几个内容,但是在上篇评论里,有人说我的一篇太长了,确实,如果太长了,没有多少人能有耐心看下去,而且看的时间太长对眼睛也不好,在办公室里也不好意思老顶着屏幕看博客吧,boss看了也不好啊,所以这篇就光说一个关联了,那看来这个How do I原计划的三篇是不能完成了。其实文章有点长,看起来应该很快,没有什么难度的内容,而且我是以讲话的风格写的,本系列的内容我的计划是把Linq讲的透透彻彻的,从表面上如何使用,到后面是怎么实现的都说一遍,所以每一篇都是整个系列的一个元组。
(文/yuyijq 出处/博客园)
您可能对 [Linq] 的这些文章也感兴趣: