理解和实现 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日: 初稿。