理解和实现 Entity Framework 中关系模型的初学者教程






4.71/5 (18投票s)
在本文中,我们将尝试了解如何使用 Entity Framework 来建模具有一对多和多对多关系的数据表。
引言
在本文中,我们将尝试了解如何使用 Entity Framework 来建模具有一对多和多对多关系的数据表。我们将尝试了解 Entity Framework 在后台为我们做了什么,以及如何使用 Entity Framework 高效地处理具有关系的数据表。
背景
每当我们尝试根据应用程序需求来建模数据库时,我们都会发现表之间会存在某种关系。可能存在一种情况,即一个表中的数据与另一个表中的数据相关。在本文中,我们将尝试了解表之间的这种关系,并了解如何在存在此类关系的情况下使用 Entity Framework 来处理数据库。
什么是“一对多”关系
假设表 A 中的每一行都与表 B 中的多行相关。(或者可以这样理解:表 B 中的每一行在表 A 中都只有一个父行)。这是通过在表 B 中设置一个外键来实现的,该外键引用表 A 的主键。这种关系称为“一对多”关系。
为了说明这一点,让我们在示例数据库中创建两个简单的表。第一个表是 Rooms,它包含任何办公室各种房间的信息。第二个表是 Assets,它包含任何组织的各种资产的信息。现在,每项资产都将放置在一个房间中,因此对于每项资产,我们需要一种方法将其与房间关联起来。这是通过在 Assets 表和 Rooms 表之间创建“一对多”关系来实现的,其中 Assets 表包含一个引用 Rooms 表中房间的外键。

什么是“多对多”关系
现在也可能出现这样的情况:一个表中的实体引用另一个表中的多个实体,同时另一个表中的实体也可以引用第一个表中的多个实体。这种情况就需要表之间存在“多对多”关系。这通常是通过创建另一个表来实现的,该表将与两个表都建立外键关系,并仅跟踪原始表之间的关系。这个表本身将只包含建模关系所需的列,而这些列将构成该表的主键。
现在,为了说明上述概念,假设在同一个示例数据库中,我们将多个项目数据存储在 Projects 表中。现在,每个项目都可以从一组允许的房间中选择一个房间用于日常会议。同时,每个房间也可以被多个项目请求。为了建模这种关系,我们需要创建一个表来跟踪 Projects 和 Rooms。让我们通过向数据库添加 Projects 和 ProjectRooms(用于实现此关系的表)来了解如何做到这一点。

在上面的表中,Projects 表包含项目的详细信息,而 ProjectRooms 表则跟踪项目可以预订的房间,反之亦然。
使用代码
现在我们有了准备好关系的数据库,接下来我们需要了解如何使用 Entity Framework 在我们的应用程序中使用这些关系。让我们从向网站添加一个 ADO.NET entity data model 开始。

我们将从示例数据库生成此模型并为其创建实体。

目前,我们就使用这些实体,并在接下来的章节中尝试理解这些实体是如何生成的。
一对多关系 - 生成实体
从上面的图表中,我们可以看到 Entity Framework 足够智能,能够理解 Asset 和 Room 之间存在“一对多”关系。我们可以看到,在导航属性中,它创建了相关实体的属性,即 Room 实体包含一个属性来获取 Assets(复数,因为可能有多个关联的 Assets),而 Asset 实体包含一个属性来获取关联的 Room(单数,因为一个 Asset 可能只有一个 Room)。
现在,有了这些生成的实体,需要注意的重要一点是,我们可以通过原始实体关联的属性来执行(CRUD 操作)所有操作。下一节将展示如何做到这一点。
对相关表执行 CRUD 操作
现在,在这一节中,让我们尝试通过使用 Room 实体来对 Asset 实体执行所有 CRUD 操作。我们将使用 Room 实体,并利用其关联的 Assets 的导航属性来执行所有操作。
SELECT
让我们开始看看如何选择与任何房间关联的所有资产。在此示例应用程序中,房间数据会显示在下拉列表中,以便用户可以选择他们想要查看资产数据的房间。一旦用户选择任何房间,与之关联的房间就会被获取并显示给用户。
using (SampleDbEntities entities = new SampleDbEntities())
{
    // Let us get the selected rooms ID
    string selectedValue = drpRooms.SelectedValue;
    int roomIndex = Convert.ToInt32(selectedValue);
    // Now let us fetch the selected room entity
    Room selectedRoom = entities.Rooms.SingleOrDefault<Room>(room => room.RoomID == roomIndex);
    // Now let us utilize the relationship to extract the assets associated with this room
    GridAssets.DataSource = selectedRoom.Assets;
    GridAssets.DataBind();
}
然后将显示与所选房间关联的 Assets。

注意:上面的代码片段(以及后续的代码片段)仅显示了使用当前选定的房间获取 Assets 数据的部分。应用程序中用于向用户显示房间的其他部分并未在此处显示。因此,要完全理解应用程序,请查看示例项目。
INSERT
现在让我们看看如何执行插入操作,使用选定的 Room 添加 Asset。
using (SampleDbEntities entities = new SampleDbEntities())
{
    // Let us get the selected rooms ID
    string selectedValue = drpRooms.SelectedValue;
    int roomIndex = Convert.ToInt32(selectedValue);
    // Now let us fetch the selected room entity
    Room selectedRoom = entities.Rooms.SingleOrDefault<Room>(room => room.RoomID == roomIndex);
    // Lets create the new asset to add to db
    Asset asset = new Asset();
    asset.AssetName = txtAddAsset.Text;
    
    // Now let us utilize the relationship to Add the asset into db with proper relation with selected room
    selectedRoom.Assets.Add(asset);
    // Persist the changes in the database
    entities.SaveChanges();
}
用户现在可以简单地选择一个 Room,然后向该房间添加一个 Asset。

更新
类似地,我们可以通过先选择一个 Room,获取关联的 Assets,然后更新任何 Asset 来 更新 Asset 的详细信息。
using (SampleDbEntities entities = new SampleDbEntities())
{
    // Let us get the selected asset ID
    GridViewRow row = GridAssets.SelectedRow;
    int assetIndex = Convert.ToInt32(row.Cells[1].Text);
    // Let us get the selected rooms ID
    string selectedValue = drpRooms.SelectedValue;
    int roomIndex = Convert.ToInt32(selectedValue);
    // Now let us fetch the selected room entity
    Room selectedRoom = entities.Rooms.SingleOrDefault<Room>(room => room.RoomID == roomIndex);
    // get the asset that needs to be updated
    Asset asset = selectedRoom.Assets.SingleOrDefault<Asset>(ast => ast.AssetID == assetIndex);
    asset.AssetName = TextBox1.Text;
    asset.RoomID = Convert.ToInt32(drpNewRoom.SelectedValue);
    entities.SaveChanges();
}

这里需要注意的重要一点是,可以更改任何资产的关联 Room,Entity Framework 将负责更新所有相关表。
删除
删除 Asset 也可以通过简单地获取给定 Room 的所选 Asset 并将其从 Assets 集合中删除来完成。Entity Framework 将负责将其从相应的表中删除并更新关系。
using (SampleDbEntities entities = new SampleDbEntities())
{
    // Let us get the selected asset ID
    GridViewRow row = GridAssets.SelectedRow;
    int assetIndex = Convert.ToInt32(row.Cells[1].Text);
    // Let us get the selected rooms ID
    string selectedValue = drpRooms.SelectedValue;
    int roomIndex = Convert.ToInt32(selectedValue);
    // Now let us fetch the selected room entity
    Room selectedRoom = entities.Rooms.SingleOrDefault<Room>(room => room.RoomID == roomIndex);
    // get the asset that needs to be updated
    Asset asset = selectedRoom.Assets.SingleOrDefault<Asset>(ast => ast.AssetID == assetIndex);
    entities.DeleteObject(asset);
    entities.SaveChanges();
}
对于上述所有操作,我们都使用了选定的 Room 实体,然后对关联的 Assets 执行了所有操作。Entity Framework 负责更新相应的表。
注意:上述所有代码片段仅显示了实际在代码中使用关系的部分。应用程序中用于向用户显示房间的其他部分并未在此处显示。因此,要完全理解应用程序,请查看示例项目。
多对多关系 - 生成实体
从数据库生成实体时,缺少了一些东西。ProjectRoom 表没有生成相应的实体。这是为什么?如果我们仔细查看实体,我们会发现 Entity Framework 做了一件非常聪明的事情。它理解 ProjectRooms 表是为了建模多对多关系而创建的,因此它通过在相应实体中创建导航属性来创建多对多关系,从而使应用程序代码无需处理仅用于建模关系的表。
简单来说,Entity Framework 读取了 ProjectRooms 表,并在 Rooms(复数,因为会有许多关联的 Project)中创建了一个 Project 属性,并在 Project 实体中创建了一个 Rooms 属性(再次使用复数来反映一个房间关联了多个项目)。
现在,在本节中,让我们尝试通过利用 Entity Framework 创建的导航属性来对这些相关实体执行一些操作。
对相关表执行操作
让我们创建一个页面,管理员可以在其中简单地更改分配给项目的房间。

根据选定的 Project 选择关联的 Rooms
using (SampleDbEntities entities = new SampleDbEntities())
{
    // Let us get the selected rooms ID
    string selectedValue = drpProjects.SelectedValue;
    int projectIndex = Convert.ToInt32(selectedValue);
    // Now let us fetch the selected room entity
    Project selectedProject = entities.Projects.SingleOrDefault<Project>(prj => prj.ProjectID == projectIndex);
    // Now let us utilize the relationship to extract the assets associated with this room
    GridRooms.DataSource = selectedProject.Rooms;
    GridRooms.DataBind();
}
将特定 Room 添加到选定的 Project
currentPrj.Rooms.Add(entities.Rooms.Where(room => room.RoomName == item.Text).First());
从选定的 Project 中移除特定 Room
currentPrj.Rooms.Remove(entities.Rooms.Where(room => room.RoomName == item.Text).First());
对于上述所有操作,我们都使用了选定的 Project 实体,然后对关联的 Rooms 执行了所有操作。Entity Framework 负责更新相应的表。
注意:上述所有代码片段仅显示了实际在代码中使用关系的部分。应用程序中用于向用户显示房间的其他部分并未在此处显示。因此,要完全理解应用程序,请查看示例项目。
因此,我们了解了如何使用 Entity Framework 来建模和使用“一对多”和“多对多”关系。在结束之前,有一件重要的事情需要理解,那就是 延迟加载。Entity Framework 默认不会加载所有关联的实体。例如,当我们获取 Room 数据时,会创建一个 Assets 的导航属性,但这些 Assets 数据不会被加载,除非它们被请求,即延迟加载。多对多关系也是如此。
值得关注的点:
在这篇短文中,我尝试解释如何使用 Entity Framework 来建模和使用“一对多”和“多对多”关系。项目本身包含大量代码,因此我尽量使本文中的代码片段简短并与讨论主题相关。为了更好地理解这些内容,我建议查看相关的示例代码文件。本文是从初学者的角度编写的。希望它能带来启发。
历史
- 2013年2月4日: 初稿。


