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

时间:2022-08-26 00:10:32
本篇是Linq to SQL How do I 的第二篇,难度系数100 ,定位为进阶级。

 

内容

对象之间的关系

 

对象之间的关系

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

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

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

2.       使用join子句

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

走进Linq-Linq to SQL How do I(2)走进Linq-Linq to SQL How do I(2)博客类映射(Blog)
首先获取 Post Blog Table<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 的标准中
走进Linq-Linq to SQL How do I(2)
推荐的方式是使用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-Linq to SQL How do I(2)
大家看到,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子句里面的东西的位置是一一对应的:
走进Linq-Linq to SQL How do I(2)
 

如上图,用框子框起来的四部分,橙黄色的两个,蓝色的两个,位置都是一一对应的,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语句就是这样的了:
走进Linq-Linq to SQL How do I(2)
看到上面说了那么多,也许有人会问:一般的ORM框架都能在对象之间建立关系来反映数据库之间的关系,而操作的时候这种关联是自动的,难道Linq to SQL必须让程序员手动的使用这种join的方式来处理对象之间的关系么?答案是否定的。

 

一对多关系(One-to-Many)

在上面的映射中,Blog还有一个属性:Posts,我们并没有做什么处理,实际上这个应该是根据BlogId属性关联到PostBlogId属性,进行关联的。

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

///   <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 命名空间下。

下面来看看例子:

 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类里添加:

// 注意,用的是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>属性就可以了,下面用实例做个演示:

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

走进Linq-Linq to SQL How do I(2)走进Linq-Linq to SQL How do I(2)用户类映射(User)

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

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

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

看看操作:
走进Linq-Linq to SQL How do I(2)

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

 

后记

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