内容 l 对象之间的关系 |
对象之间的关系
既然是对象-关系映射,各个表之间肯定不是独立存在的(如果都是独立存在的,也没有必要用关系数据库了),那么就必然涉及到几个表之间的联合了。
Linq to SQL和SQL语句一样,支持两种方式的联合:
1. 利用where子句,对两个表进行查找
2. 使用join子句
我们还是用例子来说明吧,现在要对blogs和posts进行查询,传入一篇文章的id的时候,找出这篇文章相关信息的同时还要找出这篇文章属于哪个博客,接着上篇的例子,我们首先得给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);
推荐的方式是使用join子句:
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);
大家看到,Linq to SQL使用inner join子句。
但是Linq to SQL在使用join的时候并不是像SQL那样宽松,把上面的SQL语句贴下来:
在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的原型是:
(
this IEnumerable < TOuter > outer,
IEnumerable < TInner > inner,
Func < TOuter, TKey > outerKeySelector,
Func < TInner, TKey > innerKeySelector,
Func < TOuter, TInner, TResult > resultSelector
)
大家也许发现上面的join生成的SQL语句只有inner join(inner join也称之为等值连接,只返回两个表联结字段相等的行),在我们这个需求里,这样使用是合理的:查找一篇文章,如果该文章对应的博客不存在的话,这篇文章也不应该显示。但是,如果我们期望使用outer join咋办呢?Linq为我们提供了一个DefaultIfEmpty()的扩展方法:
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
};
看到上面说了那么多,也许有人会问:一般的ORM框架都能在对象之间建立关系来反映数据库之间的关系,而操作的时候这种关联是自动的,难道Linq to SQL必须让程序员手动的使用这种join的方式来处理对象之间的关系么?答案是否定的。
一对多关系(One-to-Many)
在上面的映射中,Blog还有一个属性:Posts,我们并没有做什么处理,实际上这个应该是根据Blog的Id属性关联到Post的BlogId属性,进行关联的。
为此我们要修改一下这个Blog的映射:
/// 一个博客有零篇或多篇文章,
/// </summary>
[Association(OtherKey = " BlogId " )]
public EntitySet < Post > Posts { get ; set ; }
下面来看看例子:
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类里添加:
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,不过网上有很多多对多关系的解决方案,在这里我就不做介绍了,我给个连接:
这位大哥的方案是一个通用的,你可以下载他的代码应用到你的项目当中,有时间我会把这个翻译一下。
一对一的关系(one-to-one)
一对一的关系Linq to SQL是支持的,你只要给关系的两边都加上EntitySet<TEntity>属性就可以了,下面用实例做个演示:
在我们的实例中,每个用户对应着一个博客,每个博客也只对应着一个用户,这是个一对一的关系:
Blog类前面已经出现过,我们就不列出全部代码,将Blog类修改如下:
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讲的透透彻彻的,从表面上如何使用,到后面是怎么实现的都说一遍,所以每一篇都是整个系列的一个元组。