Entity Framework简介






4.72/5 (48投票s)
探讨 EF 和数据库关联模式。
Entity Framework 入门,或,我初探 Entity Framework
我想通过一些基本的数据库模式来学习 Entity Framework
- 单例表
- 一对一关联
- 一对多关联
- 多对多关联
看看 Entity Framework 如何处理这些关系模式。
要求
要使用 Entity Framework 及相关向导,您必须安装 VS2008 SP1 和 .NET 3.5 SP1。
你将学到什么
有关创建使用 Entity Framework 的项目的基本信息
- 处理简单表(PersonName)
- 简单的编辑和数据绑定
- 处理一对一关系(Person 拥有 PersonName)
- 处理一对多关系(Person 拥有 PersonName 集合)
- 通过关联表处理多对多关系(Persons 和 Locations)
- 一个或多个用户可以关联零个或多个地点
您将学不到什么
- 任何与最佳实践有关的内容,因为我一无所知
- 任何与性能有关的内容,同样因为我一无所知
- 您如何在 n 层环境中使用 Entity Framework(与代理有关,但我仍然一窍不通)
数据模型
实际上,我将分步创建数据模型。完成后,我们应该对 Entity Framework 如何处理一些基本数据库架构模式有很好的理解。
PersonName 表
首先,我们将从一个简单的 PersonName 表开始,该表包含 ID、FirstName 和 LastName 字段。此表将用于演示 Entity Framework 与简单表的用法。
Person 表
Person 表将包含 ID、Gender 和 PersonNameID 字段,并将演示一种简单的一对一关系,其中一个人引用一个 PersonName。目的是探讨 Entity Framework 如何处理“包含”或“连接”两个表。
重新审视 PersonName 表
稍后我将修改 PersonName 表,添加一个 PersonID 字段以建立一对多关系,以便支持一个人可以拥有多个名称的概念:婚前姓、真名、别名等。换句话说,我将创建一个主从关系,并了解它在 Entity Framework 中的工作方式。
Location 表
Location 表将由 ID、StreetNumber、StreetName 和 Zip 字段组成。还将有一个 PersonLocationAssociation 表来建立 Person 和 Location 之间的多对多关联。例如,多个人可以“居住在”一个地点。一个办公室有许多人“在”该地点工作。初始关联表将只包含 ID、PersonID 和 LocationID 字段。
处理简单表
创建模型
那么,首先,让我们创建一个简单的 PersonName 表的窗体。在您喜欢的数据库(Entity Framework 支持的数据库,哈哈)中,创建一个新数据库并添加 PersonName 表
ID | integer ,主键,非空,标识 |
FirstName | nvarchar(64) ,非空 |
LastName | nvarchar(64) ,非空 |
请记住,ID 是一个标识值。我们将要创建的所有表中的 ID 字段都被指定为标识值。
创建 EntityFramework WinForms 应用程序
- 启动 Visual Studio 2008 并创建一个名为 PersonName 的 WinForms 应用程序。
- 在解决方案资源管理器中,右键单击 PersonName 项目,然后选择“添加/新建项...”
- 在模板列表中找到 ADO.NET Entity Data Model 向导,然后单击它(如果看不到,则表示您没有安装 VS2008 SP1 和/或 .NET 3.5 SP1)。
- 在“PersonName.edmx”下方的文本框中更改名称。这是实体数据模型(EDM)元数据文件。
- 单击“确定”。
- 您将想要“从数据库生成”,所以再次单击“下一步”。
- 从数据库服务器列表中选择,或创建新连接。在进行下一步之前测试连接。
接下来的两个步骤非常重要
- 在“将实体连接设置另存为 App.Config 中的”复选框下方的文本框中,您首先要确保此框已选中(默认情况下是选中的)。
- 其次,您需要输入一个有意义且大小写正确的名称,因为这将实际使用此名称生成上下文类。因此,键入“PersonNameEntities”。
最后
- 单击“下一步”。
- 从表中选择 PersonName 表。
- 单击“完成”。
将向您的项目添加大量引用;生成代码后,将显示数据模型的图(仅显示 PersonName 表)。
创建用户界面
现在,让我们创建一个简单的用户界面来编辑 PersonName 表。
- 在窗体设计器中,将一个
DataGridView
添加到窗体,并将其命名为“dgvPersonName
”。 - 双击窗体以生成
Load
事件处理程序。 - 在代码文件的顶部添加以下
using
语句using System.Data.Objects; using System.Data.Objects.DataClasses;
- 将以下字段添加到类中
protected PersonNameEntities personNameContext;
- 在窗体的构造函数中,在
InitializeComponent()
调用之后实例化personNameContext
字段public Form1() { InitializeComponent(); personNameContext = new PersonNameEntities(); }
- 并且,对于窗体的
Load
事件处理程序,添加几行代码使其如下所示private void Form1_Load(object sender, EventArgs e) { ObjectQuery<PersonName> personNameQuery = personNameContext.PersonName; dgvPersonName.DataSource = personNameQuery; dgvPersonName.Columns["ID"].Visible = false; }
我们隐藏了 ID,因为这是一个由数据库分配的标识字段。
- 在窗体设计器中,添加一个“保存”按钮,并创建一个调用
personNameContext
的SaveChanges()
的Click
事件处理程序。private void btnSave_Click(object sender, EventArgs e) { personNameContext.SaveChanges(); }
运行程序!
您应该会看到类似上面的内容,您可以在其中填写姓名、编辑、删除等,并将更改保存到数据库。
好的,这非常巧妙,因为我甚至不需要编写任何 SQL 语句,尽管这是一个两层应用程序。
那么,发生了什么?
PersonNameEntities
是自动生成的,并派生自 ObjectContext
。这个 ObjectContext
类是什么?嗯,MSDN 的描述可能最贴切
“提供将实体数据作为对象进行查询和处理的功能。”
和
ObjectContext
类是与数据作为实体类型对象(在实体数据模型 (EDM) 中定义)进行交互的主要类。ObjectContext
类的实例封装了以下内容:
- 数据库连接,形式为
EntityConnection
对象。 - 描述模型的元数据,形式为
MetadataWorkspace
对象。 - 一个
ObjectStateManager
对象,它管理缓存中持久化的对象。
自动生成的子类 PersonNameEntities
具有有用的方法和属性
AddToPersonName(PersonName personName)
您可以使用它以编程方式将 PersonName
添加到上下文管理的集合中。我发现有点奇怪的是,没有“RemovePersonName”方法,尽管基类 ObjectContext
确实有一个 DeleteObject(object entity)
方法用于以编程方式标记实体以供删除。
还有一个 PersonName
的 getter 属性,我们在这一行中使用过
ObjectQuery<PersonName> personNameQuery = personNameContext.PersonName;
ObjectQuery
类表示一个类型化的查询,其类型是泛型参数中指定的类。值得注意的是,数据直到对象实际在 foreach
语句中使用、分配给列表集合,或者您显式调用 Execute
方法时才加载。另外值得注意的是,ObjectQuery
类实现了 IListSource
,因此它可以作为列表控件(如网格)的数据源,这也是我们在下一行中所做的
dgvPersonName.DataSource = personNameQuery;
最后,表字段在 PersonName
类(这也是为我们生成的)中公开为属性,因此您可以利用强类型和字段名在代码中访问字段。
处理一对一关联
在本节中,我将研究一对一关联是如何工作的。这不是一种常见的数据库关联模式,但值得研究,因为它与视图在 Entity Framework 中的工作方式有相似之处。
创建模型
通过执行此 SQL 语句删除 PersonName 表中的数据(或使用上面的应用程序删除所有行!)
delete from PersonName
现在,让我们创建一个 Person 表,该表与 PersonName 表具有一对一关系
ID | integer ,主键,标识,非空 |
PersonNameID | integer ,外键指向 PersonName.ID,非空 |
性别 | char(1) ,非空 |
这有点牵强,但可以满足我们的目的。让我们直接用 SQL 添加一个名字
insert into personname (firstname, lastname) values ('Marc', 'Clifton')
select * from personname
使用从 PersonName 表返回的 ID,填充 Person 表
insert into person (gender, personnameid) values ('M', 12)
将“12”替换为正确的 ID。
创建 EntityFramework WinForms 应用程序
按照上述步骤创建一个名为 PersonPersonName
的新 WinForms 应用程序。但这次,当您选择表时,请同时选择 Person 和 PersonName 表。添加代码以如上所述初始化上下文。
创建用户界面
向窗体添加一个网格视图。由于这是一对一关联,我们希望能够在网格的单行上编辑姓名和性别,并希望 Entity Framework 处理单独的 CRUD 操作(至少,这是想法)。
与之前一样,双击窗体以创建 Load
事件处理程序。
现在,我们可以通过几种不同的方式加载数据。例如,我们可以提取两个表的内容,然后在 LINQ 中进行连接。这显然不是首选的连接方式,因为您获取两个表中的所有内容,然后在客户端进行连接,而不是让数据库进行连接并只返回合格的行。
private void Form1_Load(object sender, EventArgs e)
{
ObjectQuery<Person> person = ppnContext.Person;
ObjectQuery<PersonName> personName = ppnContext.PersonName;
var ppnQuery = from p in person
join pn in personName
on p.PersonName.ID equals pn.ID
select new { p.Gender, pn.FirstName, pn.LastName };
dgvPersonPersonName.DataSource = ppnQuery;
}
您也可以这样写
private void Form1_Load(object sender, EventArgs e)
{
var ppnQuery=ppnContext.Person.Join(
ppnContext.PersonName,
person => person.PersonName,
personName => personName,
(person, personName) => new
{
Gender=person.Gender,
LastName=personName.LastName,
FirstName=personName.FirstName});
dgvPersonPersonName.DataSource = ppnQuery;
}
生成的 SQL 是什么?
现在,数据库是在为您进行连接,还是这仅仅分解为与第一个示例类似的东西?我一无所知!Scott Gu 在他的博客上发布了一个调试器可视化工具;然而,我使用的是 Vista-64,它似乎不起作用。另一个人也评论了这个问题。由于我使用的是 SQL 2005 Express,我没有分析器。当我使用 SQL 2005 或 SQL 2008 进行设置时,我将更新文章,提供有关服务器执行的 SQL 语句的更多信息。
注意只读的 DataGridView
请注意,DataGridView
控件处于只读状态
由于结果集是通过连接两个表构建的,我们无法再使用控件的编辑功能进行编辑(更改、插入或删除)。我在其他框架中成功地解决了这个问题,并且对 Entity Framework 没有提供智能功能来提供可编辑的数据集感到失望,当数据集是从连接语句构建时。
删除行
因此,让我们修改我们的 UI,以便我们可以“手动”添加、编辑和删除行。首先,我们将添加一个 BindingSource
,以便我们可以获取当前选定的行,并修改查询,以便我们获得比匿名类型更具体的东西,这意味着我们需要为 Person-PersonName 记录定义一个类
public class PPN
{
public string Gender { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person PersonObject { get; set; }
public PersonName PersonNameObject {get; set;}
}
查询被修改为返回一个 IQueryable<PPN>
集合,将该集合分配给一个 BindingSource
,后者又分配给 DataGridView
控件。
private void Form1_Load(object sender, EventArgs e)
{
IQueryable<PPN> ppnQuery=ppnContext.Person.Join(
ppnContext.PersonName,
person => person.PersonName,
personName => personName,
(person, personName) => new PPN()
{
Gender=person.Gender,
LastName=personName.LastName,
FirstName = personName.FirstName,
PersonObject=person,
PersonNameObject=personName
}).AsQueryable();
bsppn.DataSource = ppnQuery;
dgvPersonPersonName.DataSource = bsppn;
dgvPersonPersonName.Columns["PersonObject"].Visible = false;
dgvPersonPersonName.Columns["PersonNameObject"].Visible = false;
}
现在,当用户单击“删除”按钮(我们已将其添加到 UI)时,我们可以删除选定的记录
private void btnDelete_Click(object sender, EventArgs e)
{
if (bsppn.Current != null)
{
PPN current = bsppn.Current as PPN;
ppnContext.DeleteObject(current.PersonObject);
ppnContext.DeleteObject(current.PersonNameObject);
ppnContext.SaveChanges();
Form1_Load(null, EventArgs.Empty);
}
}
上面的代码获取绑定源中的 PPN
记录,删除 Person
和 PersonName
记录,然后保存更改。窗体数据被重新加载。这**不是**最佳实践,但我这样实现是为了让您看到 DataGridView
中反映的更改。
如果您考虑上面处理程序中发生的情况,这似乎本来会更自然
ppnContext.DeleteObject(current.PersonObject);
ppnContext.DeleteObject(current.PersonObject.PersonName);
然而,PersonObject.PersonName
是 null
。类似地,人们可能会认为删除 current.PersonObject
就足够了,也许期望 Entity Framework 会执行级联删除。但是,由于 PersonObject.PersonName
是 null
,我们可以想象级联删除可能不起作用。事实上,这给了我们一个线索,我们并没有真正以最佳实践来使用 Entity Framework——看起来 Entity Framework 在后台仍然独立地获取两个表的记录,并在客户端将它们连接起来。
为什么不使用视图?
此时,您可能已经问过:“为什么不使用视图?”答案(目前)是我希望保持事物尽可能“简单”(即使这意味着“愚蠢”)。如果我使用视图,那么对于该视图的 CRUD 操作使用存储过程也可能是最佳实践。相反,我想尽可能多地探索 Entity Framework 的功能。
稍后,我将研究真正的主从关系以及 Include(string path)
方法,而不是 Join<>
操作。
添加新记录
由于 DataGridView
控件处于只读状态,因此我创建了单独的控件来添加新记录
代码足够简单(同样,不一定代表最佳实践)
private void btnAdd_Click(object sender, EventArgs e)
{
PersonName pn = new PersonName();
pn.FirstName = tbFirstName.Text;
pn.LastName = tbLastName.Text;
Person p = new Person();
p.Gender = tbGender.Text;
p.PersonName = pn;
ppnContext.AddToPerson(p);
ppnContext.SaveChanges();
Form1_Load(null, EventArgs.Empty);
}
请注意,我正在将 PersonName
实例分配给 p.PersonName
,因为 Entity Framework 已经知道了关联,所以我不必处理主键和外键。
编辑现有行
让我们将一些简单的数据绑定添加到 TextBox
控件(此时,我已经重构了窗体的 Load
事件,因此绑定只发生一次)
private void Form1_Load(object sender, EventArgs e)
{
LoadData();
tbGender.DataBindings.Add(new Binding("Text", bsppn, "Gender"));
tbFirstName.DataBindings.Add(new Binding("Text", bsppn, "FirstName"));
tbLastName.DataBindings.Add(new Binding("Text", bsppn, "LastName"));
}
由于 DataGridView
控件处于只读状态,因此数据绑定是单向的。这意味着我们必须手动更新选定行的 Person
和 PersonName
对象
private void btnUpdate_Click(object sender, EventArgs e)
{
if (bsppn.Current != null)
{
PPN current = bsppn.Current as PPN;
current.PersonObject.Gender = tbGender.Text;
current.PersonNameObject.FirstName = tbFirstName.Text;
current.PersonNameObject.LastName = tbLastName.Text;
ppnContext.SaveChanges();
LoadData();
}
}
处理一对多关联
更常见的关联模式是一对多关联。在这里,我将研究在 Person
和 PersonName
之间创建这种模式,方法是反转外键字段,以便一个人可以拥有多个名字,例如婚前姓、别名等。
创建模型
修改模型,使 PersonName 具有一个 PersonID,它是 Person.ID 表的外键。从 Person 表中删除 PersonNameID 字段。
另外,创建一个将 PersonName.PersonID 与 Person.ID 关联的外键。
创建 EntityFramework WinForms 应用程序
与之前一样,创建一个 WinForms 应用程序,并使用 ADO.NET Data Model Wizard 添加 Person 和 PersonName 表的 Entity Framework 信息。
完成此操作后,请注意 Person.PersonName
属性如何成为一个集合!Entity Framework 已正确理解 Person
和 PersonName
之间的关系是一对多关系,并创建一个 PersonName
集合,允许我们将多个名称与一个人关联。
创建用户界面
在此示例中,我创建了两个 DataGridView
控件,一个用于主(Person)数据,另一个用于从(PersonName)数据。一个“保存”按钮保存上下文,并且一如既往,窗体 Load
事件用于加载数据集。注意,当我们运行应用程序时,两个网格都是可编辑的
Load
事件处理程序如下所示
private void Form1_Load(object sender, EventArgs e)
{
ObjectQuery<Person> personQuery = mpnContext.Person.Include("PersonName");
bsPerson.DataSource = personQuery;
dgvPerson.DataSource = bsPerson;
dgvPerson.Columns["ID"].Visible = false;
dgvPerson.Columns["PersonName"].Visible = false;
dgvPersonName.DataBindings.Add(new Binding("DataSource",
bsPerson, "PersonName"));
dgvPersonName.Columns["ID"].Visible = false;
dgvPersonName.Columns["Person"].Visible = false;
}
注意使用 Include(string path)
方法,该方法将数据加载到主从对象模型中,这意味着每个 Person
实例都有一个特定于该关联的 PersonName
对象集合。
一个 BindingSource
跟踪 Person DataGridView
中当前选定的行,并且此绑定源用于动态绑定 Person.PersonName
集合与从 DataGridView
控件。
现实检验
要在此主从模型中添加、编辑和删除行(但请注意,有关级联删除的内容),只需要这些,这确实非常巧妙。然而,在使用高度规范化的数据库时,假设主从关系可以由一个主表和一个从表表示是不现实的。更可能的是,主表和从表是视图。这使得处理主从数据集更加复杂,正如我们在上面看到的。
级联删除
如果我们尝试删除主记录并保存结果,则会发生以下异常(这假设您没有为外键的 OnDelete
规则指定“Cascade
”)
我们真正想要的是级联删除。我们“应该”为外键的 OnDelete
规则指定 Cascade
,然后这将生成正确的 EDM。因此,让我们手动编辑 EDM。
- 首先,在数据库架构中,将外键的
OnDelete
规则更改为Cascade
- 使用 Visual Studio 的 XML 编辑器打开 .edmx 文件
- 在 EDM 的 SSDL 部分找到
Association
标签,并修改End
标签,添加级联规则... <Association Name="FK_PersonName_Person"> <End Role="Person" Type="ManyPersonNameModel.Store.Person" Multiplicity="1"> <OnDelete Action="Cascade"></OnDelete> </End> <End Role="PersonName" Type="ManyPersonNameModel.Store.PersonName" Multiplicity="*" /> ...
- 在 EDM 的 CSDL 部分也进行同样的操作。
现在,我们可以删除主记录而不会产生错误,并且与主记录关联的从记录将被自动删除。
现实检验
完整性检查通常取决于应用程序运行的上下文。例如,普通用户在存在关联的从记录时可能不应该删除 Person 记录。然而,超级用户或高级管理员可能应该具备此功能。在数据库和 EDM 中实现级联规则无法捕获决定此事的上下文信息。是的,可以以编程方式完成。但是,当您设计企业架构应用程序时,您真的认为以编程方式完成此操作是最佳方法吗?就我个人而言,我认为不是。
处理多对多关联
在最后一个示例中,我们将研究多对多关联,即 PersonName 与新表 Location 以及 PersonNameLocationAssociation 表之间的关联。
创建模型
修改 PersonName,删除 PersonID 列,因为我们不想通过要求 Person 记录来混淆问题。
Location 表如下所示
ID | integer ,主键,标识 |
数字 | nvarchar(16) ,非空 |
街道 | nvarchar(64) ,非空 |
Zip | nvarchar(5) ,非空 |
PersonNameLocationAssocation 表定义为
PersonNameID | integer ,外键指向 PersonName.ID,级联删除 |
LocationID | integer ,外键指向 Location.ID,级联删除 |
复合主键由两个字段组成。
创建 Entity Framework WinForms 应用程序
现在这应该很常规了。创建一个 WinForms 应用程序,并使用 ADO.NET Entity Data Model 向导为 PersonName、Location 和 PersonNameLocationAssociation 表生成类。
请注意,向导生成的实体数据模型仅包含 PersonName 和 Location 表
这是关联表的一个特点:它只包含外键。稍后,我将添加另一个字段(关联类型),然后我们将再次查看 EDM。另请注意“导航属性”——我们可以链接到 PersonName 的 Location 方向,反之亦然,而无需显式通过关联表。
现实检验
这是一种可爱的快捷方式,但它真的实用吗?根据我的经验,关联表将具有关于关联的其他属性,可能描述关联类型、关联的开始和结束日期等。所以实际上,我不期望在现实世界中能够利用这种快捷方式。
用户界面
我为此创建的 UI 非常简单,允许您创建 PersonName
和 Location
记录,然后通过选择每个网格中的一行并单击“关联”按钮来关联它们
窗体 Load
事件处理程序应该很熟悉
private void Form1_Load(object sender, EventArgs e)
{
ObjectQuery<PersonName> personQuery = context.PersonName;
ObjectQuery<Location> locationQuery = context.Location;
bsPersonName.DataSource = personQuery;
bsLocation.DataSource = locationQuery;
dgvPersonName.DataSource = bsPersonName;
dgvLocation.DataSource = bsLocation;
dgvPersonName.Columns["ID"].Visible = false;
dgvPersonName.Columns["Location"].Visible = false;
dgvLocation.Columns["ID"].Visible = false;
dgvLocation.Columns["PersonName"].Visible = false;
}
关联按钮事件处理程序创建所选记录之间的关联
private void btnAssociate_Click(object sender, EventArgs e)
{
if ((bsLocation.Current != null) && (bsPersonName.Current != null))
{
PersonName pn = (PersonName)bsPersonName.Current;
Location loc = (Location)bsLocation.Current;
pn.Location.Add(loc);
loc.PersonName.Add(pn);
context.SaveChanges();
}
}
正如您所见,我使用了 BindingSource
来管理两个 DataGridView
控件中的货币(当前选定行)。
在进行几次关联后,我们可以通过查询关联表来验证它们是否已保存
所以,这很直接。
带有属性字段的关联表
在本文中,我最后想看的是,当您向关联添加一些属性字段时会发生什么。例如,让我们将 AssocType
字段(我真的很懒,所以我们将创建一个 nvarchar(64)
)添加到 PersonNameLocationAssociation 表中,这样我们就可以输入有关关联的一些信息,例如“居住在”、“工作在”等等。
删除项目中的 .edmx 文件,并使用 ADO.NET Entity Data Model 向导再次添加 PersonName、Location 和 PersonNameLocationAssociation 表(我看不到更新现有 EDM 的简单方法)。
请注意,现在关联表已显式包含在 EDM 中
为了建立关联,我们现在必须处理所有三个实体
private void btnAssociate_Click(object sender, EventArgs e)
{
if ((bsLocation.Current != null) && (bsPersonName.Current != null))
{
PersonName pn = (PersonName)bsPersonName.Current;
Location loc = (Location)bsLocation.Current;
PersonNameLocationAssociation assoc = new PersonNameLocationAssociation();
assoc.PersonName = pn;
assoc.Location = loc;
assoc.AssocType = "Lives At";
pn.PersonNameLocationAssociation.Add(assoc);
loc.PersonNameLocationAssociation.Add(assoc);
context.SaveChanges();
}
}
是的,我硬编码了 AssocType
值。但是,从上面的代码可以看出,处理显式关联表很容易。奇怪的是,我不需要将关联添加到上下文的集合中
context.AddToPersonNameLocationAssociation(assoc);
因此,一定有一些幕后魔法在起作用。
杂项评论
生成的代码包括工厂方法,例如
public static PersonName CreatePersonName(int id, string firstName, string lastName)
但请注意,这些方法包含表 ID,这是没有意义的(或者所谓的),因为 ID,至少在我使用的模型中,是标识字段,所以它们是由服务器分配的,而不是客户端。我确实读到了一些关于这些值暂时用于关系的信息,但似乎不必要。
关联表的工厂方法似乎特别无用,因为它需要关联表 ID 的值(同样,在我看来,它们是标识字段,由服务器分配)
public static PersonNameLocationAssociation
CreatePersonNameLocationAssociation(
int personNameID,
int locationID,
string assocType)
未来文章中可能还有其他有趣的方面
- 客户端验证
- 客户端错误处理服务器错误
- 缓存
- n 层体系结构
- 系统性能
- SQL 语句性能
- 断开连接状态(智能客户端)
- 安全/加密
- 应用程序上下文
- 等等。
基本上,在企业架构中需要处理的常见事物,所有这些都触及数据服务体系结构,在这种情况下是 Entity Framework。
结论
因此,这是关于 Entity Framework 与不同数据库关联模式的介绍。由于我刚接触 Entity Framework,我希望我没有犯任何重大错误,但如果我犯了,请留下评论,我会更新文章。Entity Framework 对于基本操作似乎非常容易使用,希望微软会在未来的版本中解决我的一个痛点,即无法轻松更新连接的数据集。另一个让我困扰的问题是无法轻松查看生成的 SQL。当然,我觉得我只是触及了决定 Entity Framework 是否是一项可行技术皮毛,而上面的列表看起来相当艰巨。