65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ to SQL - 多对多关系

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (17投票s)

2009 年 8 月 18 日

CPOL

5分钟阅读

viewsIcon

89928

downloadIcon

960

在使用 LINQ to SQL 时,轻松支持多对多关系。

引言

多对多关系是两个实体之间的一种双向一对多关系,它由一个具有与每个实体的一对多关系的中间表定义。大多数开发人员会认为多对多关系是一种相当标准的数据库设计。然而,LINQ to SQL 对这些类型的关系没有提供原生支持。 PLINQO 使处理多对多关系变得非常简单。我们将通过展示如何手动添加对多对多关系的支持,来引导您完成 PLINQO 中的实现过程。

设置

我们将使用用户和角色之间常用的关系。一个用户可以有多个角色,一个角色可以包含多个用户。我们将使用一个包含构成此关系的三张表的示例数据库。下面是用户/角色关系的概览。

LINQ to SQL 将为每张表创建一个实体,并且在 User 和 Role 之间没有直接的链接,尽管它们之间实际上存在关系。要获取某个用户的角色,我们必须通过 UserRole 列表进行遍历,然后获取相关的 Role。

// db is an instance of the DataContext
var user = db.Users.FirstOrDefault(u => u.Id = 1);
var roles = new Roles();
foreach(var userRole in db.Users.UserRoles)
    roles.Add(userRole.Role);

这是我们不希望做的工作,也是我们期望 ORM 为我们完成的工作。我们只想找到某个用户的角色成员关系。为每个多对多关系都强制执行此过程既繁琐又令人沮丧,尤其因为这些关系很常见。我们应该能够直接看到用户的角色,因为这才是我们真正关心的实际关系。因此,我们需要找到一种处理方法。现在我们就来做这件事。

实现

使用 LINQ to SQL 设计器,我们将创建与表对应的 LINQ to SQL 实体。创建 DBML 后,我们需要进行更新。我们需要为 `UserRole` 实体中的每个关联添加 `DeleteOnNull` 属性。由于设计器不支持管理此属性,因此我们将不得不手动更新 DBML 以进行此更改。要直接更新 DBML,请右键单击 DBML 文件,选择“*打开方式*”,然后选择 XML 编辑器。进入 DBML 文件后,找到 `UserRole` 实体。我们需要为每个关联添加 `DeleteOnNull` 属性,并将其设置为 true

<Table Name="dbo.UserRole" Member="UserRoles">
  <Type Name="UserRole">
    <Column Name="UserId" Type="System.Int32" 
       DbType="Int NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
    <Column Name="RoleId" Type="System.Int32" 
       DbType="Int NOT NULL" IsPrimaryKey="true" CanBeNull="false" />
    <Association Name="Role_UserRole" Member="Role" 
       ThisKey="RoleId" OtherKey="Id" Type="Role" 
       IsForeignKey="true" DeleteOnNull="true" />
    <Association Name="User_UserRole" Member="User" 
       ThisKey="UserId" OtherKey="Id" Type="User" 
       IsForeignKey="true" DeleteOnNull="true" />
  </Type>
</Table>

将 `DeleteOnNull` 设置为 true 后,当关联设置为 null 时,就可以删除该对象。更新并保存 DBML 文件后,右键单击解决方案资源管理器中的 DBML 文件,然后选择“运行自定义工具”。这将使用更新重新创建 `UserRole` 实体。

我们希望能够管理与 User 相关的 Role 列表,而无需麻烦管理表示该关系的中间表。因此,我们需要一个 Role 列表,并能够处理向关系中添加和删除 Role。让我们先构建列表。我们将把以下 `Roles` 属性添加到 `User` 对象中来实现这一点。

private System.Data.Linq.EntitySet _roles;
public System.Data.Linq.EntitySet<role> Roles
{
    get
    {
        if (_roles == null)
        {
            _roles = new System.Data.Linq.EntitySet<role>(OnRolesAdd, OnRolesRemove);
            _roles.SetSource(UserRoles.Select(c => c.Role));
        }
        return _roles;
    }
    set
    {
        _roles.Assign(value);
    }
}

正如您所见,我们正在使用 `UserRole` 实体中的数据作为后台来管理关系。这抽象掉了多对多关系的所有底层细节。`Roles` 属性是一个 `EntitySet`,可以轻松地将其源设置为 `UserRole` 中定义的 `Roles` 关系,并像在 LINQ to SQL 中使用任何其他 `EntitySet` 一样使用该属性。创建此属性后,就可以编写以下代码行:

user.Roles

但是,我们还没有完成。我们必须能够添加和删除 Role 并相应地更新数据库。当实例化一个新的 `EntitySet<role>` 实例时,我们可以为其提供这些操作的处理程序。为了处理我们的情况,我们创建了 `OnRolesAdd` 和 `OnRolesRemove` 事件处理程序。

[System.Diagnostics.DebuggerNonUserCode]
private void OnRolesAdd(Role entity)
{
    this.UserRoles.Add(new UserRole { User = this, Role = entity });
    SendPropertyChanged(null);
}

[System.Diagnostics.DebuggerNonUserCode]
private void OnRolesRemove(Role entity)
{
    var userRole = this.UserRoles.FirstOrDefault(
        c => c.UserId == Id
        && c.RoleId == entity.Id);
    this.UserRoles.Remove(userRole);
    SendPropertyChanged(null);
}

由于我们在事件处理程序中管理着必要的操作,因此复杂的多对多关系操作将被抽象出来。当一个 `Role` 被添加到 Role 列表中时,我们还将向 `UserRole` 列表中添加一个 `UserRole` 对象,其中包含构成关系所需的 `User` 和 `Role` 对象。当一个 `Role` 被删除时,我们将在 `UserRole` 列表中找到匹配的对象并将其删除。现在我们已经创建了轻松处理多对多关系的 C# 代码,让我们来看看它的实际效果。

我们创建了两个测试,用于添加和删除 Role。让我们先看看添加。我们只需将 `Role` 添加到 `Roles` 列表中,然后执行 `SubmitChanges()`。

var role = db.Roles.First(r => r.Id == 1);
var user = db.Users.First(u => u.Id == 1);
user.Roles.Add(role);
db.SubmitChanges();

运行添加测试后,数据库中的数据如下所示:

现在,我们要从 `User` 中删除 `Role`。

using (var db = new SampleDataContext())
{
    var user = db.Users.First(u => u.Id == 1);
    var role = user.Roles.First(r => r.Id == 1);
    user.Roles.Remove(role);
    db.SubmitChanges();
}

再看一眼数据库,就会发现行和关系已被删除。

结论

添加对多对多关系的支持可能看起来不多。但是,上面讨论的属性和事件处理程序必须为遇到的每个多对多关系添加。手动执行此操作几次后,我相信您会厌倦处理这种情况。PLINQO 将检查数据库中的每个关系,并生成必要的属性和事件处理程序,以按照您最初期望 LINQ to SQL 处理多对多关系的方式来处理它们。这意味着您不必担心处理多对多关系所需的任何内容。 PLINQO 已经为您处理好了。

使用 PLINQO 时,您只需生成并享受使用支持多对多关系的 LINQ to SQL!

© . All rights reserved.