代码改变世界

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

2008-08-05 11:07  横刀天笑  阅读(3041)  评论(12编辑  收藏
本篇是Linq to SQL How do I的第二篇,难度系数100,定位为进阶级。

 

内容

对象之间的关系

 

对象之间的关系

既然是对象-关系映射,各个表之间肯定不是独立存在的(如果都是独立存在的,也没有必要用关系数据库了),那么就必然涉及到几个表之间的联合了。

Linq to SQLSQL语句一样,支持两种方式的联合:

1.       利用where子句,对两个表进行查找

2.       使用join子句

我们还是用例子来说明吧,现在要对blogsposts进行查询,传入一篇文章的id的时候,找出这篇文章相关信息的同时还要找出这篇文章属于哪个博客,接着上篇的例子,我们首先得给Blog类加上映射:

博客类映射(Blog)
首先获取PostBlogTable<TEntity>的对象,然后施加操作:
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子句:
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语句贴下来:

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,32,4这种对应关系。这又是为什么呢?因为这种查询表达式最后要被转换成方法调用,而扩展方法Join的原型是:

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对应着这里的outerKeySelectorposts就是这里的innerpost.BlogId对应着这里的innerKeySelectorequals就是这里的resultSelector。实际上,如果你使用VisualStudio2008编写代码的时候,如果不按照这个顺序敲入代码,vs是不会给你智能感知的。

大家也许发现上面的join生成的SQL语句只有inner join(inner join也称之为等值连接,只返回两个表联结字段相等的行),在我们这个需求里,这样使用是合理的:查找一篇文章,如果该文章对应的博客不存在的话,这篇文章也不应该显示。但是,如果我们期望使用outer join咋办呢?Linq为我们提供了一个DefaultIfEmpty()的扩展方法:

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,我们并没有做什么处理,实际上这个应该是根据BlogId属性关联到PostBlogId属性,进行关联的。

为此我们要修改一下这个Blog的映射:

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

下面来看看例子:

 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 <= 0return;
    
foreach (var post in item.Posts)
    {
        Console.WriteLine(post.Title);
    }
}

我们只要对blog进行查询就ok了,和她关联的Post自动的会映射过来的。

上面的例子只表明了在Blog类里,如何关联到该博客的所有Post,如果我在Post里想关联到该Post属于哪个博客呢?

Post类里添加:

//注意,用的是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默认是不支持多对多的关系的,所以也没有针对多对多关系的AttributeQuery,不过网上有很多多对多关系的解决方案,在这里我就不做介绍了,我给个连接:

http://blogs.msdn.com/mitsu/archive/2007/06/21/how-to-implement-a-many-to-many-relationship-using-linq-to-sql.aspx

这位大哥的方案是一个通用的,你可以下载他的代码应用到你的项目当中,有时间我会把这个翻译一下。

 

一对一的关系(one-to-one)

一对一的关系Linq to SQL是支持的,你只要给关系的两边都加上EntitySet<TEntity>属性就可以了,下面用实例做个演示:

在我们的实例中,每个用户对应着一个博客,每个博客也只对应着一个用户,这是个一对一的关系:

用户类映射(User)

Blog类前面已经出现过,我们就不列出全部代码,将Blog类修改如下:

[Column]
public int UserId { getset; }
[Association(ThisKey
="UserId",OtherKey="Id",IsUnique=true)]
public EntitySet<User> User { getset; }

就是添加一个EntitySet<User> User的属性而已。

看看操作:

通过查询结果可以看出,他们之间的关系建立了。

 

后记

本来这一篇我准备了好几个内容,但是在上篇评论里,有人说我的一篇太长了,确实,如果太长了,没有多少人能有耐心看下去,而且看的时间太长对眼睛也不好,在办公室里也不好意思老顶着屏幕看博客吧,boss看了也不好啊,所以这篇就光说一个关联了,那看来这个How do I原计划的三篇是不能完成了。其实文章有点长,看起来应该很快,没有什么难度的内容,而且我是以讲话的风格写的,本系列的内容我的计划是把Linq讲的透透彻彻的,从表面上如何使用,到后面是怎么实现的都说一遍,所以每一篇都是整个系列的一个元组。