Linq To Sql进阶系列(二)M:M关系

Linq To Sql进阶系列(一) 一文中,我们谈到了数据库中的两种基本关系1:M 与1:1. 而现实世界中,还有一种M:M 的关系。比如,一个老师可以有多个学生,而一个学生也可以有多个老师。老师和学生的关系就是多对多的关系。这些关系在数据库中是如何反映的呢?
在 C#3.0入门系列(十)-之Join操作一文中,我们提到了M:M 的关系中的join操作。哦,原来,M:M 的关系在数据库中,依然是通过1:M 来体现。比如,在一个域内,一个User可以加入到多个Group中,一个Group也可以包含多个User。 User与Group并没有直接的关系,而是通过第三个表UserInGroup发生关系。User与 UserInGroup的关系为1:M,其关系键为UserId, 而Group与 UserInGroup的关系也为1:M, 其关系键为GroupId,这样,我们通过第三个表,让User与Group发生了关系,他们的关系为M:M.
这三个表的脚本如下:
ContractedBlock.gif ExpandedBlockStart.gif
 SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[User]'AND OBJECTPROPERTY(id, N'IsUserTable'= 1)
BEGIN
CREATE TABLE [dbo].[User](
[UserId] [nchar](10NOT NULL,
[UserName] [nchar](10NULL,
 
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
[UserId] ASC
ON [PRIMARY]
ON [PRIMARY]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[Group]'AND OBJECTPROPERTY(id, N'IsUserTable'= 1)
BEGIN
CREATE TABLE [dbo].[Group](
[GroupId] [nchar](10NOT NULL,
[GroupName] [nchar](10NULL,
 
CONSTRAINT [PK_Group] PRIMARY KEY CLUSTERED 
(
[GroupId] ASC
ON [PRIMARY]
ON [PRIMARY]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[UserInGroup]'AND OBJECTPROPERTY(id, N'IsUserTable'= 1)
BEGIN
CREATE TABLE [dbo].[UserInGroup](
[UserId] [nchar](10NOT NULL,
[GroupId] [nchar](10NOT NULL,
 
CONSTRAINT [PK_UserInGroup] PRIMARY KEY CLUSTERED 
(
[UserId] ASC,
[GroupId] ASC
ON [PRIMARY]
ON [PRIMARY]
END
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_UserInGroup_Group]'AND type = 'F')
ALTER TABLE [dbo].[UserInGroup]  WITH CHECK ADD  CONSTRAINT [FK_UserInGroup_Group] FOREIGN KEY([GroupId])
REFERENCES [dbo].[Group] ([GroupId])
GO
ALTER TABLE [dbo].[UserInGroup] CHECK CONSTRAINT [FK_UserInGroup_Group]
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[FK_UserInGroup_User]'AND type = 'F')
ALTER TABLE [dbo].[UserInGroup]  WITH CHECK ADD  CONSTRAINT [FK_UserInGroup_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([UserId])
GO
ALTER TABLE [dbo].[UserInGroup] CHECK CONSTRAINT [FK_UserInGroup_User]
他们的关系图如下:
Linq To Sql进阶系列(二)M:M关系_第1张图片

创建数据库后,添加一些数据供测试。
ContractedBlock.gif ExpandedBlockStart.gif
insert into [User] values('Tom Song','Tom Song')
insert into [User] values('Guoan Song','Guoan Song')

insert into [Group] values ('Csharp','Csharp Team')
insert into [Group] values ('Dlinq','Dlinq Team')

insert into UserInGroup values ('Tom Song','Csharp')
insert into UserInGroup values ('Tom Song','Dlinq')
insert into UserInGroup values ('Guoan Song','Csharp')
insert into UserInGroup values ('Guoan Song','Dlinq')

使用OR Designer将他们映射为实体。关于OR Designer请参考 C#3.0入门系列(七)--之OR工具介绍

现在的问题是:User和Group的实体对象中,可不可以直接引用对方呢?就像User.Groups 或Group.Users。我们可不可以去改变其映射,直接越过UserInGroup,让User和Group直接发生关系呢?回答是肯定的。但是,数据库中,依然是三个表。你不可能在数据库中越过表UserInGroup的。表UserInGroup起了一个纽带的作用,想把它抛弃掉,不太可能。那可不可以,在mapping code中,做些手脚,让Linq To Sql自己去找这个纽带呢?回答是,Linq To Object可以,但是, Linq To Sql不可以。

为什么Linq To Sql不可以?其原因就是Linq To Sql需要先翻译成Sql语句,而,这个映射,必须真实反映数据库里表的情况。也就说,这个映射,无法逾越UserInGroup实体。它必须要有UserInGroup。
那Linq To Object怎么样可以呢?把下面的代码,加入到你的工程中。
using  System.Data.Linq;
using  System.Data.Linq.Mapping;
using  System.Data;
using  System.Collections.Generic;
using  System.Reflection;
using  System.Linq;
using  System.Linq.Expressions;
using  System.Runtime.Serialization;
using  System.ComponentModel;
using  System;

namespace  ConsoleApplication1
ExpandedBlockStart.gifContractedBlock.gif
{
    
public partial class Group
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
public List<User> Users
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
return (from u in this.UserInGroups
                        select u.User).ToList();
            }

        }

    }

    
public partial class User
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
public List<Group> Groups
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
return (from g in this.UserInGroups
                        select g.Group
                            ).ToList();
            }

        }

    }

}


为了不破坏数据库的映射,我们把这部分改动放到partial  class中。这样,我们渴望实现user.Group。
我们来做个测试,看看是不是成功了呢。
ContractedBlock.gif ExpandedBlockStart.gif
    public static void Main(string[] arg)
ExpandedBlockStart.gifContractedBlock.gif    
{
        DataClasses1DataContext db 
= new DataClasses1DataContext();
        db.Log 
= Console.Out;
        var userSet 
= (from u in db.Users
                       select u).ToList();
        
foreach (var use in userSet)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
foreach (var group in use.Groups)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                Console.WriteLine(group.GroupName);
            }

        }


        var groupSet 
= (from u in db.Groups
                        select u).ToList();
        
foreach (var group in groupSet)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
foreach (var user in group.Users)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                Console.WriteLine(user.UserName);
            }

        }

    }
太好了。这正是我们想要的。那再来做个反面的测试吧。
         var  q  =  ( from  u  in  db.Users
                  
from  g  in  u.Groups
                  
select  new { u.UserName, g.GroupName }).ToList();
编译通过,在run-time时,出错。因为,Linq To Sql不知道该怎么去找这个关系。那么下面这个呢?
         var  q2  =  ( from  u1  in  ( from  u  in  db.Users
                  
select  u).ToList()
                  
from  g  in  u1.Groups
                  
select  new { u1.UserName, g.GroupName }).ToList();
run-time没有问题。纠起原因,其在里面已经取回了数据,是Linq To Object的范畴了。

还可以使用下面的这个映射code。
ContractedBlock.gif ExpandedBlockStart.gif
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.ComponentModel;
using System;

namespace ConsoleApplication1
ExpandedBlockStart.gifContractedBlock.gif
{
    
public partial class Group
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
public IEnumerable<User> Users
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
return from u in this.UserInGroups
                       select u.User;
            }

        }

    }

    
public partial class User
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
public IEnumerable<Group> Groups
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
return from g in this.UserInGroups
                       select g.Group;
            }

        }

    }

}

结合我们的测试例子,请大家仔细比较他们的区别哦。前面那个是一次性取出了。后面这个是用那个取那个。

 

TrackBack:http://www.cnblogs.com/126/archive/2007/07/31/836306.html

转载于:https://www.cnblogs.com/hdjjun/archive/2008/11/05/1327125.html

你可能感兴趣的:(Linq To Sql进阶系列(二)M:M关系)