实体框架 - 快速指南


实体框架 - 概述

什么是实体框架?

实体框架于 2008 年首次发布,是 Microsoft .NET 应用程序和关系数据库之间交互的主要方式。实体框架是一种对象关系映射器 (ORM),它是一种简化软件中的对象与关系数据库的表和列之间的映射的工具。

  • Entity Framework (EF) 是 ADO.NET 的开源 ORM 框架,它是 .NET Framework 的一部分。

  • ORM 负责创建数据库连接和执行命令,以及获取查询结果并自动将这些结果具体化为应用程序对象。

  • ORM 还有助于跟踪这些对象的更改,并且根据指示,它还会为您将这些更改保留回数据库。

为什么选择实体框架?

实体框架是一种 ORM,ORM 旨在通过减少保留应用程序中使用的数据的冗余任务来提高开发人员的工作效率。

  • 实体框架可以生成在数据库中读取或写入数据所需的数据库命令并为您执行它们。

  • 如果您正在查询,则可以使用 LINQ toEntity 表达针对域对象的查询。

  • 实体框架将在数据库中执行相关查询,然后将结果具体化为域对象的实例,以便您在应用程序中工作。

市场上还有其他 ORM,例如 NHibernate 和 LLBLGen Pro。大多数 ORM 通常将域类型直接映射到数据库模式。

典型的 ORM

实体框架具有更细粒度的映射层,因此您可以自定义映射,例如,通过将单个实体映射到多个数据库表,甚至将多个实体映射到单个表。

EF 运行时元数据
  • 实体框架是微软推荐的新应用程序的数据访问技术。

  • ADO.NET似乎直接指的是数据集和数据表的技术。

  • 实体框架是所有前瞻性投资的所在地,这种情况已经存在多年了。

  • Microsoft 建议您使用 ADO.NET 上的实体框架或 LINQ to SQL 进行所有新开发。

概念模型

对于习惯以数据库为中心的开发的开发人员来说,实体框架最大的转变是它让您可以专注于您的业务领域。您希望您的应用程序在不受数据库功能限制的情况下执行什么操作?

  • 对于实体框架,焦点被称为概念模型。它是应用程序中对象的模型,而不是用于保存应用程序数据的数据库模型。

  • 您的概念模型可能恰好与您的数据库模式一致,也可能完全不同。

  • 您可以使用可视化设计器来定义概念模型,然后该模型可以生成您最终将在应用程序中使用的类。

  • 您可以只定义您的类并使用实体框架的一项称为“代码优先”的功能。然后实体框架将理解概念模型。

概念模型

无论哪种方式,实体框架都会解决如何从概念模型转移到数据库的问题。因此,您可以查询概念模型对象并直接使用它们。

特征

以下是实体框架的基本功能。该列表是根据最显着的功能以及有关实体框架的常见问题创建的。

  • 实体框架是微软的一个工具。
  • 实体框架正在作为开源产品开发。
  • 实体框架不再依赖于 .NET 发布周期。
  • 适用于具有有效实体框架提供程序的任何关系数据库。
  • 从 LINQ to Entities 生成 SQL 命令。
  • 实体框架将创建参数化查询。
  • 跟踪内存中对象的更改。
  • 允许插入、更新和删除命令生成。
  • 使用视觉模型或您自己的类。
  • 实体框架具有存储过程支持。

实体框架 - 架构

实体框架的体系结构从下到上由以下部分组成:

数据提供者

这些是特定于源的提供程序,它们在针对概念架构进行编程时抽象 ADO.NET 接口以连接到数据库。

它将常见的 SQL 语言(例如 LINQ)通过命令树转换为本机 SQL 表达式,并针对特定的 DBMS 系统执行它。

实体客户端

该层将实体层暴露给上层。实体客户端使开发人员能够使用实体 SQL 查询来处理行和列形式的实体,而无需生成表示概念模式的类。实体客户端显示实体框架层,这是核心功能。这些层称为实体数据模型。

实体数据模型
  • 存储包含 XML 格式的整个数据库模式。

  • 实体也是一个 XML 文件,定义实体和关系。

  • 映射是一个 XML 文件,它将概念层定义的实体和关系与逻辑层定义的实际关系和表进行映射。

  • 数据服务也体现在实体客户端中,提供集中式 API 来访问存储在实体、映射和存储层的元数据。

对象服务

对象服务层是对象上下文,它代表应用程序和数据源之间交互的会话。

  • 对象上下文的主要用途是执行不同的操作,例如添加、删除实体实例,以及借助查询将更改的状态保存回数据库。

  • 它是实体框架的ORM层,将数据结果表示为实体的对象实例。

  • 该服务允许开发人员通过使用 LINQ 和 Entity SQL 编写查询来使用一些丰富的 ORM 功能,例如主键映射、更改跟踪等。

实体框架 - 环境设置

实体框架 6 中有哪些新增功能?

Framework 具有复杂的 API,可让您对从建模到运行时Behave的所有内容进行精细控制。Entity Framework 5 的一部分位于 .NET 内部。它的另一部分位于使用 NuGet 分发的附加程序集中。

  • 实体框架的核心功能内置于.NET Framework 中。

  • Code First 支持允许实体框架使用类代替可视模型,并且 NuGet 包中提供了一种与 EF 交互的更轻量级 API。

  • 核心是提供查询、更改跟踪以及从查询到 SQL 查询以及从数据返回到对象的所有转换。

  • 您可以将 EF 5 NuGet 包与 .NET 4 和 .NET 4.5 一起使用。

  • 一大混乱点 - .NET 4.5 在核心实体框架 API 中添加了对枚举和空间数据的支持,这意味着如果您将 EF 5 与 .NET 4 一起使用,您将无法获得这些新功能。只有将 EF5 与 .NET 4.5 结合使用时才能获得它们。

框架6

现在让我们看一下 Entity Framework 6。Entity Framework 6 中 .NET 内部的核心 API 现在是 NuGet 包的一部分。

实体框架6

这意味着 -

  • 所有实体框架都位于这个由 NuGet 分发的程序集中

  • 您不会依赖 .NET 来提供特定功能,例如实体框架枚举支持和特殊数据支持。

  • 您将看到 EF6 的功能之一是它支持 .NET 4 的枚举和空间数据

要开始使用实体框架,您需要安装以下开发工具 -

  • Visual Studio 2013 或更高版本
  • SQL Server 2012 或更高版本
  • 来自 NuGet 包的实体框架更新

Microsoft 提供了 Visual Studio 的免费版本,其中也包含 SQL Server,可以从www.visualstudio.com下载。

安装

步骤 1 - 下载完成后,运行安装程序。将显示以下对话框。

视觉工作室安装程序

步骤 2 - 单击“安装”按钮,它将开始安装过程。

安装过程

步骤 3 - 安装过程成功完成后,您将看到以下对话框。如果需要,关闭此对话框并重新启动计算机。

设置完成

步骤 4 - 从开始菜单打开 Visual Studio,这将打开以下对话框。第一次准备需要一段时间。

视觉工作室

步骤 5 - 完成所有操作后,您将看到 Visual studio 的主窗口。

主窗口

让我们从文件→新建→项目创建一个新项目

新项目

步骤 1 - 选择控制台应用程序并单击“确定”按钮。

步骤 2 - 在解决方案资源管理器中,右键单击您的项目。

控制台应用程序

步骤 3 - 选择管理 NuGet 包,如上图所示,这将在 Visual Studio 中打开以下窗口。

视觉工作室1

步骤 4 - 搜索实体框架并按安装按钮安装最新版本。

预览

步骤 5 - 单击“确定”。安装完成后,您将在输出窗口中看到以下消息。

输出窗口

您现在可以开始申请了。

实体框架 - 数据库设置

在本教程中,我们将使用一个简单的大学数据库。大学数据库整体上可能要复杂得多,但出于演示和学习目的,我们使用该数据库最简单的形式。下图包含三个表。

  • 学生
  • 课程
  • 注册
数据库

每当使用术语数据库时,我们都会直接想到一件事,那就是具有某种关系的不同类型的表。表之间的关系分为三种类型,不同表之间的关系取决于相关列的定义方式。

  • 一对多关系
  • 多对多关系
  • 一对一的关系

一对多关系

一对多关系是最常见的关系类型。在这种类型的关系中,表 A 中的一行可以在表 B 中具有多个匹配行,但表 B 中的一行在表 A 中只能有一个匹配行。例如,在上图中,Student 和 Enrollment 表有一个一对多关系,每个学生可能有多个注册,但每个注册只属于一个学生。

多对多关系

在多对多关系中,表 A 中的一行可以在表 B 中具有多个匹配行,反之亦然。您可以通过定义第三个表(称为连接表)来创建这样的关系,该表的主键由表 A 和表 B 中的外键组成。例如,Student 和 Course 表具有多对多关系,定义如下:这些表中的每一个与 Enrollment 表之间存在一对多关系。

一对一的关系

在一对一关系中,表 A 中的一行在表 B 中只能有一个匹配行,反之亦然。如果两个相关列都是主键或具有唯一约束,则会创建一对一关系。

这种类型的关系并不常见,因为以这种方式相关的大多数信息都是全合表。您可以使用一对一的关系来 -

  • 将一个表分成许多列。
  • 出于安全原因隔离表的一部分。
  • 存储短暂的数据,只需删除表即可轻松删除。
  • 存储仅适用于主表子集的信息。

实体框架 - 数据模型

实体数据模型 (EDM) 是实体关系模型的扩展版本,它使用各种建模技术指定数据的概念模型。它还指一组描述数据结构的概念,无论其存储形式如何。

EDM 支持一组定义概念模型中属性的原始数据类型。我们需要考虑构成实体框架基础的 3 个核心部分,统称为实体数据模型。以下是EDM的三个核心部分。

  • 存储架构模型
  • 概念模型
  • 映射模型

存储架构模型

存储模型也称为存储架构定义层(SSDL),表示后端数据存储的示意图。

电火花加工

概念模型

概念模型也称为概念模式定义层 (CSDL),是真正的实体模型,我们根据它编写查询。

映射模型

映射层只是概念模型和存储模型之间的映射。

逻辑模式及其与物理模式的映射被表示为 EDM。

  • Visual Studio 还提供实体设计器,用于可视化创建 EDM 和映射规范。

  • 该工具的输出是指定架构和映射的 XML 文件 (*.edmx)。

  • Edmx 文件包含实体框架元数据工件。

模式定义语言

ADO.NET 实体框架使用基于 XML 的数据定义语言(称为架构定义语言 (SDL))来定义 EDM 架构。

  • SDL 定义的简单类型与其他基本类型类似,包括 String、Int32、Double、Decimal 和 DateTime 等。

  • 枚举定义了原始值和名称的映射,也被视为简单类型。

  • 仅从框架版本 5.0 开始支持枚举。

  • 复杂类型是从其他类型的聚合创建的。这些类型的属性的集合定义了实体类型。

数据模型主要有三个关键概念来描述数据结构 -

  • 实体类型
  • 协会类型
  • 财产

实体类型

实体类型是描述 EDM 中数据结构的基本构建块。

  • 在概念模型中,实体类型是根据属性构建的,并描述顶级概念的结构,例如业务应用程序中的学生和注册。

  • 实体代表特定对象,例如特定学生或注册。

  • 每个实体在实体集中都必须有唯一的实体键。实体集是特定实体类型的实例的集合。实体集(和关联集)在逻辑上分组在实体容器中。

  • 实体类型支持继承,即一种实体类型可以从另一种实体类型派生。

实体类型

协会类型

它是描述 EDM 中关系的另一个基本构建块。在概念模型中,关联表示两个实体类型(例如“学生”和“注册”)之间的关系。

  • 每个关联都有两个关联端,用于指定关联中涉及的实体类型。

  • 每个关联端还指定关联端重数,该关联端重数指示可以位于关联该端的实体的数量。

  • 关联端多重性的值可以为一 (1)、零或一 (0..1) 或多 (*)。

  • 位于关联一端的实体可以通过导航属性进行访问,或者通过外键(如果它们在实体类型上公开)进行访问。

财产

实体类型包含定义其结构和特征的属性。例如,学生实体类型可能具有学生 ID、姓名等属性。

属性可以包含原始数据(例如字符串、整数或布尔值)或结构化数据(例如复杂类型)。

实体框架-DbContext

实体框架使您能够使用称为实体的公共语言运行时 (CLR) 对象来查询、插入、更新和删除数据。实体框架将模型中定义的实体和关系映射到数据库。它还提供设施 -

  • 将从数据库返回的数据具体化为实体对象
  • 跟踪对对象所做的更改
  • 处理并发
  • 将对象更改传播回数据库
  • 将对象绑定到控件

负责与作为对象的数据进行交互的主要类是 System.Data.Entity.DbContext。DbContext API 不作为 .NET Framework 的一部分发布。为了更加灵活和频繁地向 Code First 和 DbContext API 发布新功能,实体框架团队通过 Microsoft 的 NuGet 分发功能分发 EntityFramework.dll。

  • NuGet 允许您通过将相关 DLL 从 Web 直接拉入您的项目来添加对 .NET 项目的引用。

  • 称为“库包管理器”的 Visual Studio 扩展提供了一种将适当的程序集从 Web 拉入项目的简单方法。

数据库上下文
  • DbContext API 主要旨在简化与实体框架的交互。

  • 它还减少了访问常用任务所需的方法和属性的数量。

  • 在实体框架的早期版本中,这些任务的发现和编码通常很复杂。

  • 上下文类在运行时管理实体对象,其中包括使用数据库中的数据填充对象、更改跟踪以及将数据保存到数据库。

定义 DbContext 派生类

使用上下文的推荐方法是定义一个派生自 DbContext 的类,并公开表示上下文中指定实体集合的 DbSet 属性。如果您正在使用 EF 设计器,将为您生成上下文。如果您使用 Code First,您通常会自己编写上下文。

以下代码是一个简单的示例,显示 UniContext 派生自 DbContext。

  • 您可以将自动属性与 DbSet 一起使用,例如 getter 和 setter。

  • 它还使代码更加简洁,但当您没有其他逻辑可应用时,您不需要使用它来创建 DbSet。

public class UniContext : DbContext {
   public UniContext() : base("UniContext") { }
   public DbSet<Student> Students { get; set; }
   public DbSet<Enrollment> Enrollments { get; set; }
   public DbSet<Course> Courses { get; set; }
}
  • 以前,EDM 用于生成从 ObjectContext 类派生的上下文类。

  • 使用 ObjectContext 有点复杂。

  • DbContext 是 ObjectContext 的包装器,实际上与 ObjectContext 类似,并且在所有开发模型(例如 Code First、Model First 和 Database First)中都很有用且简单。

查询

您可以使用三种类型的查询,例如 -

  • 添加新实体。
  • 更改或更新现有实体的属性值。
  • 删除现有实体。

添加新实体

使用实体框架添加新对象就像构造对象的新实例并使用 DbSet 上的 Add 方法注册它一样简单。以下代码适用于您想要将新学生添加到数据库时。

private static void AddStudent() {

   using (var context = new UniContext()) {

      var student = new Student {
         LastName = "Khan", 
         FirstMidName = "Ali", 
         EnrollmentDate = DateTime.Parse("2005-09-01") 
      };

      context.Students.Add(student); 
      context.SaveChanges();

   }
}

更改现有实体

更改现有对象就像更新分配给要更改的属性的值并调用 SaveChanges 一样简单。在以下代码中,Ali 的姓氏已从 Khan 更改为 Aslam。

private static void AddStudent() {

   private static void ChangeStudent() {

      using (var context = new UniContext()) {

         var student = (from d in context.Students
            where d.FirstMidName == "Ali" select d).Single();
         student.LastName = "Aslam";
         context.SaveChanges();

      }
   }
}

删除现有实体

要使用实体框架删除实体,请使用 DbSet 上的 Remove 方法。删除现有和新添加实体的工程。对已添加但尚未保存到数据库的实体调用Remove将取消该实体的添加。该实体将从更改跟踪器中删除,并且 DbContext 不再跟踪该实体。对正在更改跟踪的现有实体调用删除将在下次调用 SaveChanges 时注册该实体以进行删除。以下示例显示了从数据库中删除名字为 Ali 的学生的实例。

private static void DeleteStudent() {

   using (var context = new UniContext()) {
      var bay = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
      context.Students.Remove(bay);
      context.SaveChanges();
   }
}

实体框架 - 类型

在实体框架中,有两种类型的实体,允许开发人员将自己的自定义数据类与数据模型一起使用,而无需对数据类本身进行任何修改。

  • POCO实体
  • 动态代理

POCO实体

  • POCO 代表“普通”CLR 对象,可以用作数据模型的现有域对象。

  • 映射到实体的 POCO 数据类在数据模型中定义。

  • 它还支持与实体数据模型工具生成的实体类型相同的大多数查询、插入、更新和删除Behave。

  • 您可以使用 POCO 模板从概念模型生成与持久性无关的实体类型。

让我们看一下以下概念实体数据模型的示例。

概念实体模型

为上述实体模型生成 POCO 实体 -

步骤 1 - 右键单击​​设计器窗口。它将显示以下对话框。

设计师之窗

步骤 2 - 选择添加代码生成项...

代码生成

步骤 3 - 选择 EF 6.x DbContext Generator,输入名称,然后单击“添加”按钮。

您将在解决方案资源管理器中看到生成了 POCODemo.Context.tt 和 POCODemo.tt 模板。

解决方案浏览器

POCODemo.Context 生成 DbContext 和您可以返回并用于查询的对象集,例如上下文、学生和课程等。

产生

另一个模板处理所有类型的学生、课程等。以下是从实体模型自动生成的学生类的代码。

namespace ConsoleApplication1 {

   using System;
   using System.Collections.Generic;

   public partial class Student {

      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", 
         "CA2214:DoNotCallOverridableMethodsInConstructors")]

      public Student() {
         this.Enrollments = new HashSet<Enrollment>();
      }

      public int ID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
      public System.DateTime EnrollmentDate { get; set; }

      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", 
         CA2227:CollectionPropertiesShouldBeReadOnly")]

      public virtual ICollection<Enrollment> Enrollments { get; set; }

   }
}

为实体模型中的课程和注册表生成类似的类。

动态代理

创建 POCO 实体类型的实例时,实体框架通常会创建动态生成的派生类型的实例,该派生类型充当实体的代理。IT也可以说它是一个运行时代理类,就像POCO实体的包装类一样。

  • 您可以覆盖实体的某些属性,以便在访问该属性时自动执行操作。

  • 该机制用于支持关系的延迟加载和自动更改跟踪。

  • 此技术也适用于使用 Code First 和 EF Designer 创建的模型。

如果您希望实体框架支持相关对象的延迟加载并跟踪 POCO 类中的更改,那么 POCO 类必须满足以下要求 -

  • 自定义数据类必须声明为具有公共访问权限。

  • 自定义数据类不得被密封。

  • 自定义数据类不能是抽象的。

  • 自定义数据类必须具有不带参数的公共或受保护的构造函数。

  • 如果您希望使用 CreateObject 方法为 POCO 实体创建代理,请使用不带参数的受保护构造函数。

  • 调用 CreateObject 方法并不能保证代理的创建:POCO 类必须遵循本主题中描述的其他要求。

  • 该类无法实现 IEntityWithChangeTracker 或 IEntityWithRelationships 接口,因为代理类实现了这些接口。

  • ProxyCreationEnabled 选项必须设置为 true。

下面的例子是动态代理实体类。

public partial class Course {

   public Course() {
      this.Enrollments = new HashSet<Enrollment>();
   }

   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

要禁用创建代理对象,请将 ProxyCreationEnabled 属性的值设置为 false。

实体框架 - 关系

在关系数据库中,关系是关系数据库表之间通过外键存在的一种情况。外键 (FK) 是一个列或列的组合,用于建立和强制两个表中的数据之间的链接。下图包含三个表。

  • 学生
  • 课程
  • 注册
关系型数据库

在上图中,您可以看到表之间的某种关联/关系。表之间的关系分为三种类型,不同表之间的关系取决于相关列的定义方式。

  • 一对多关系
  • 多对多关系
  • 一对一的关系

一对多关系

  • 一对多关系是最常见的关系类型。

  • 在这种类型的关系中,表 A 中的一行可以在表 B 中具有多个匹配行,但表 B 中的一行在表 A 中只能有一个匹配行。

  • 外键在表示关系多端的表中定义。

  • 例如,上图中的Student和Enrollment表是一对多的关系,每个学生可能有多个学号,但每个学号只属于一个学生。

在实体框架中,这些关系也可以用代码创建。以下是与一对多关系关联的学生和注册课程的示例。

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class Enrollment {

   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
	
   public Grade? Grade { get; set; }
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

在上面的代码中,您可以看到Student类包含Enrollment的集合,但是Enrollment类只有一个Student对象。

多对多关系

在多对多关系中,表 A 中的一行可以在表 B 中拥有多个匹配行,反之亦然。

  • 您可以通过定义第三个表(称为联结表)来创建此类关系,该表的主键由表 A 和表 B 的外键组成。

  • 例如,“学生”表和“课程”表具有多对多关系,该关系是通过从这些表中的每一个表到“注册”表的一对多关系来定义的。

以下代码包含 Course 类和上面的两个类,即StudentEnrollment

public class Course {
   [DatabaseGenerated(DatabaseGeneratedOption.None)]
	
   public int CourseID { get; set; }
   public string Title { get; set; }
	
   public int Credits { get; set; } 
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

您可以看到 Course 类和 Student 类都有 Enrollment 对象的集合,这些对象通过连接类 Enrollment 建立多对多关系。

一对一的关系

  • 在一对一关系中,表 A 中的一行在表 B 中只能有一个匹配行,反之亦然。

  • 如果两个相关列都是主键或具有唯一约束,则会创建一对一关系。

  • 在一对一关系中,主键还充当外键,并且两个表都没有单独的外键列。

这种类型的关系并不常见,因为以这种方式相关的大多数信息都在一个表中。您可以使用一对一的关系来 -

  • 将一个表分成许多列。
  • 出于安全原因隔离表的一部分。
  • 存储短暂的数据,只需删除表即可轻松删除。
  • 存储仅适用于主表子集的信息。

以下代码是添加另一个类名 StudentProfile,其中包含学生电子邮件 ID 和密码。

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
   public virtual StudentProfile StudentProfile { get; set; }
}

public class StudentProfile {

   public StudentProfile() {}
   public int ID { get; set; }
   public string Email { get; set; }
   public string Password { get; set; }
	
   public virtual Student Student { get; set; }
}

您可以看到 Student 实体类包含 StudentProfile 导航属性,StudentProfile 包含 Student 导航属性。

每个学生只有一个电子邮件和密码来登录大学域。这些信息可以添加到学生表中,但出于安全原因,它被分隔到另一个表中。

实体框架 - 生命周期

寿命

上下文的生命周期从实例创建时开始,到实例被释放或垃圾回收时结束。

  • 当我们使用 ORM 时,上下文生命周期是一个非常关键的决定。

  • 上下文的执行方式类似于实体缓存,因此这意味着它保存对所有已加载实体的引用,这些实体的内存消耗可能会快速增长,并且还可能导致内存泄漏。

  • 在下图中,您可以看到通过 Context 从应用程序到数据库的上层数据工作流程,反之亦然。

数据工作流程

实体生命周期

实体生命周期描述了实体的创建、添加、修改、删除等过程。实体在其生命周期中有多种状态。在了解如何检索实体状态之前,我们先来看看什么是实体状态。状态是System.Data.EntityState类型的枚举,声明以下值 -

  • 已添加:实体被标记为已添加。

  • 已删除:实体被标记为已删除。

  • 已修改:实体已被修改。

  • 未更改:实体未修改。

  • 分离:不跟踪实体。

实体生命周期中的状态变化

有时实体的状态是由上下文自动设置的,但也可以由开发人员手动修改。尽管从一种状态切换到另一种状态的所有组合都是可能的,但其中一些组合是毫无意义的。例如,实体添加到已删除状态,反之亦然。

让我们讨论不同的状态。

不变状态

  • 当实体未更改时,它绑定到上下文,但尚未被修改。

  • 默认情况下,从数据库检索的实体处于此状态。

  • 当实体附加到上下文(使用 Attach 方法)时,它同样处于 Unchanged 状态。

  • 上下文无法跟踪它未引用的对象的更改,因此当附加它们时,它假定它们未更改。

分离状态

  • 分离是新创建的实体的默认状态,因为上下文无法跟踪代码中任何对象的创建。

  • 即使您在上下文的 using 块内实例化实体,也是如此。

  • 分离甚至是禁用跟踪时从数据库检索的实体的状态。

  • 当实体分离时,它不会绑定到上下文,因此不会跟踪其状态。

  • 它可以被处置、修改、与其他类结合使用,或者以您可能需要的任何其他方式使用。

  • 因为没有上下文跟踪它,所以它对实体框架没有任何意义。

添加状态

  • 当实体处于已添加状态时,您几乎没有选择。事实上,你只能将其与上下文分离。

  • 当然,即使您修改了某些属性,状态仍保持为“已添加”,因为将其移动到“已修改”、“未更改”或“已删除”是没有意义的。

  • 它是一个新实体,与数据库中的行没有对应关系。

  • 这是处于这些状态之一的基本先决条件(但该规则不是由上下文强制执行的)。

添加状态

修改状态

  • 当一个实体被修改时,这意味着它处于未更改状态,然后某些属性发生了更改。

  • 实体进入 Modified 状态后,可以移动到 Detached 或 Deleted 状态,但即使手动恢复原始值,也无法回滚到 Unchanged 状态。

  • 它甚至不能更改为“已添加”,除非您将实体分离并添加到上下文中,因为数据库中已存在具有此 ID 的行,并且在持久化它时会出现运行时异常。

已删除状态

  • 实体进入已删除状态,因为它是未更改或已修改,然后使用了 DeleteObject 方法。

  • 这是限制性最强的状态,因为从该状态更改为除分离之外的任何其他值都是毫无意义的。

如果您希望上下文控制的所有资源都在块末尾释放,则使用 using语句。当您使用using语句时,编译器会自动创建一个try/finally块并在finally块中调用dispose。

using (var context = new UniContext()) {

   var student = new Student {
      LastName = "Khan", 
      FirstMidName = "Ali", 
      EnrollmentDate = DateTime.Parse("2005-09-01")
   };

   context.Students.Add(student);
   context.SaveChanges();
}

在处理长时间运行的上下文时,请考虑以下事项 -

  • 当您将更多对象及其引用加载到内存中时,上下文的内存消耗可能会迅速增加。这可能会导致性能问题。

  • 请记住在不再需要上下文时将其丢弃。

  • 如果异常导致上下文处于不可恢复的状态,则整个应用程序可能会终止。

  • 随着查询和更新数据的时间间隔增大,遇到并发相关问题的可能性也会增加。

  • 使用 Web 应用程序时,每个请求使用一个上下文实例。

  • 使用 Windows Presentation Foundation (WPF) 或 Windows 窗体时,请为每个窗体使用一个上下文实例。这使您可以使用上下文提供的更改跟踪功能。

经验法则

网络应用程序

  • 现在,对于 Web 应用程序来说,每个请求都使用上下文是一种常见的最佳实践。

  • 在 Web 应用程序中,我们处理的请求非常短,但保留了所有服务器事务,因此它们是上下文生存的适当持续时间。

桌面应用程序

  • 对于桌面应用程序,如 Win Forms/WPF 等,上下文按表单/对话框/页面使用。

  • 由于我们不希望将上下文作为应用程序的单例,因此当我们从一种形式转移到另一种形式时,我们将处理它。

  • 通过这种方式,我们将获得很多上下文的能力,并且不会受到长期运行上下文的影响。

实体框架 - 代码优先方法

实体框架提供了三种创建实体模型的方法,每种方法都有自己的优缺点。

  • 代码优先
  • 数据库优先
  • 模型第一

在本章中,我们将简要描述代码优先方法。一些开发人员更喜欢在代码中使用设计器,而另一些开发人员则宁愿只使用他们的代码。对于这些开发人员来说,实体框架有一个称为“代码优先”的建模工作流程。

  • Code First 建模工作流针对不存在的数据库,Code First 将创建它。

  • 如果您有一个空数据库,那么也可以使用它,然后 Code First 也会添加新表。

  • Code First 允许您使用 C# 或 VB.Net 类定义模型。

  • 可以选择使用类和属性上的属性或使用 Fluent API 来执行其他配置。

代码优先方法

为什么先编码?

  • Code First 实际上是由一组拼图组成的。首先是您的域类。

  • 域类与实体框架无关。它们只是您业务领域的项目。

  • 那么,实体框架有一个上下文来管理这些类和数据库之间的交互。

  • 上下文并非特定于 Code First。这是一个实体框架功能。

  • Code First 添加了一个模型构建器,用于检查上下文正在管理的类,然后使用一组规则或约定来确定这些类和关系如何描述模型,以及该模型应如何映射到数据库。

  • 所有这些都发生在运行时。你永远不会看到这个模型,它只是在记忆中。

  • 如果需要,Code First 能够使用该模型创建数据库。

  • 如果模型发生变化,它还可以使用称为 Code First Migrations 的功能来更新数据库。

实体框架 - 模型优先方法

在本章中,让我们学习如何使用称为模型优先的工作流程在设计器中创建实体数据模型。

  • 当您开始一个数据库甚至还不存在的新项目时,模型优先非常有用。

  • 该模型存储在 EDMX 文件中,可以在实体框架设计器中查看和编辑。

  • 在模型优先中,您在实体框架设计器中定义模型,然后生成 SQL,这将创建与您的模型匹配的数据库架构,然后执行 SQL 以在数据库中创建架构。

  • 您在应用程序中与之交互的类是从 EDMX 文件自动生成的。

以下是使用模型优先方法创建新控制台项目的简单示例。

步骤 1 - 打开 Visual Studio 并选择文件 → 新建 → 项目

控制台项目

步骤 2 - 从左侧窗格中选择“已安装”→“模板”→“Visual C#”→“Windows”,然后在中间窗格中选择“控制台应用程序”。

步骤 3 - 在名称字段中输入 EFModelFirstDemo。

步骤 4 - 要创建模型,首先右键单击解决方案资源管理器中的控制台项目,然后选择添加→新项目...

模型

将打开以下对话框。

添新

步骤 5 - 从中​​间窗格中选择 ADO.NET 实体数据模型,然后在名称字段中输入名称 ModelFirstDemoDB。

步骤 6 - 单击“添加”按钮,这将启动“实体数据模型向导”对话框。

模型向导对话框

步骤 7 - 选择空 EF 设计器模型并单击下一步按钮。实体框架设计器将打开并显示一个空白模型。现在我们可以开始向模型添加实体、属性和关联。

步骤 8 - 右键单击​​设计图面并选择属性。在“属性”窗口中,将实体容器名称更改为 ModelFirstDemoDBContext。

设计表面

步骤 9 - 右键单击​​设计图面并选择添加新→实体...

添加新实体

添加实体对话框将打开,如下图所示。

实体对话框

步骤 10 - 输入 Student 作为实体名称,输入 Student Id 作为属性名称,然后单击“确定”。

学生

步骤 11 - 右键单击​​设计图面上的新实体,然后选择添加新→标量属性,输入名称作为属性的名称。

新实体

步骤 12 - 输入 FirstName,然后添加另外两个标量属性,例如 LastName 和 EnrollmentDate。

标量属性

步骤 13 - 按照上述所有步骤添加另外两个实体课程和注册,并添加一些标量属性,如以下步骤所示。

视觉设计师

步骤 14 - 我们在视觉设计器中有三个实体,让我们在它们之间添加一些关联或关系。

步骤 15 - 右键单击​​设计图面并选择添加新→关联...

添加协会

步骤 16 - 使关系的一端指向具有一个重数的“学生”,另一端指向具有多个重数的“注册”。

一的重数

步骤 17 - 这意味着一个学生有多个注册,并且注册属于一个学生。

步骤 18 - 确保选中“将外键属性添加到“发布”实体”框,然后单击“确定”。

步骤 19 - 同样,在课程和注册之间添加一个关联。

课程及报名

步骤 20 - 添加实体之间的关联后,您的数据模型将如下所示。

数据模型

现在我们有了一个简单的模型,可以从中生成数据库并用于读取和写入数据。让我们继续生成数据库。

步骤 1 - 右键单击​​设计图面并选择从模型生成数据库...

来自模型的数据库

步骤 2 - 您可以选择现有数据库或通过单击“新建连接”创建新连接...

生成数据库

步骤 3 - 要创建新数据库,请单击“新建连接...”

连接属性

步骤 4 - 输入服务器名称和数据库名称。

服务器和数据库

步骤 5 - 单击“下一步”。

服务器和数据库1

步骤 6 - 单击完成。这将在项目中添加 *.edmx.sql 文件。您可以通过打开 .sql 文件,然后右键单击并选择“执行”,在 Visual Studio 中执行 DDL 脚本。

DDL 脚本

步骤 7 - 将显示以下对话框以连接到数据库。

连接到数据库

步骤 8 - 成功执行后,您将看到以下消息。

成功执行

步骤 9 - 转到服务器资源管理器,您将看到数据库是使用指定的三个表创建的。

数据库已创建

接下来,我们需要交换模型以生成使用 DbContext API 的代码。

步骤 1 - 右键单击​​ EF 设计器中模型的空白位置,然后选择添加代码生成项...

EF 设计师

您将看到以下“添加新项目”对话框打开。

对话框打开

步骤 2 - 在中间窗格中选择 EF 6.x DbContext Generator,然后在“名称”字段中输入 ModelFirstDemoModel。

步骤 3 - 您将在解决方案资源管理器中看到已生成 ModelFirstDemoModel.Context.tt 和 ModelFirstDemoModel.tt 模板。

模板生成

ModelFirstDemoModel.Context 生成 DbCcontext 和对象集,您可以返回并使用它们进行查询,例如上下文、学生和课程等。

另一个模板处理所有类型的学生、课程等。以下是学生类,它是从实体模型自动生成的。

CSharp代码

以下是 C# 代码,其中输入一些数据并从数据库检索一些数据。

using System;
using System.Linq;

namespace EFModelFirstDemo {

   class Program {

      static void Main(string[] args) {

         using (var db = new ModelFirstDemoDBContext()) {

            // Create and save a new Student

            Console.Write("Enter a name for a new Student: ");
            var firstName = Console.ReadLine();

            var student = new Student {
               StudentID = 1,
               FirstName = firstName
            };
				
            db.Students.Add(student);
            db.SaveChanges();
				
            var query = from b in db.Students
               orderby b.FirstName select b;

            Console.WriteLine("All student in the database:");

            foreach (var item in query) {
               Console.WriteLine(item.FirstName);
            }

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
         }
      }
   }
}

执行上述代码时,您将收到以下输出 -

Enter a name for a new Student:
Ali Khan
All student in the database:
Ali Khan
Press any key to exit...

我们建议您逐步执行上述示例,以便更好地理解。

实体框架 - 数据库优先方法

在本章中,让我们学习如何使用数据库优先方法创建实体数据模型。

  • 数据库优先方法为实体数据模型提供了代码优先和模型优先方法的替代方法。它从项目中的数据库创建模型代码(类、属性、DbContext 等),这些类成为数据库和控制器之间的链接。

  • 数据库优先方法从现有数据库创建实体框架。我们使用所有其他功能,例如模型/数据库同步和代码生成,就像我们在模型优先方法中使用它们一样。

让我们举一个简单的例子。我们已经有一个包含 3 个表的数据库,如下图所示。

新控制台项目

步骤 1 - 让我们创建一个名为 DatabaseFirstDemo 的新控制台项目。

步骤 2 - 要创建模型,首先右键单击解决方案资源管理器中的控制台项目,然后选择添加 → 新项目...

创建模型

步骤 3 - 从中​​间窗格中选择 ADO.NET 实体数据模型,然后在名称字段中输入名称 DatabaseFirstModel。

步骤 4 - 单击“添加”按钮,这将启动“实体数据模型向导”对话框。

型号内容

步骤 5 - 从数据库中选择 EF Designer,然后单击“下一步”按钮。

实体模型向导

步骤 6 - 选择现有数据库并单击下一步。

现有数据库

步骤 7 - 选择 Entity Framework 6.x 并单击下一步。

实体框架

步骤 8 - 选择要包含的所有表视图和存储过程,然后单击完成。

您将看到实体模型和 POCO 类是从数据库生成的。

POCO 类

现在让我们通过在program.cs 文件中编写以下代码来从数据库中检索所有学生。

using System;
using System.Linq;

namespace DatabaseFirstDemo {

   class Program {

      static void Main(string[] args) {

         using (var db = new UniContextEntities()) {

            var query = from b in db.Students
               orderby b.FirstMidName select b;

            Console.WriteLine("All All student in the database:");

            foreach (var item in query) {
               Console.WriteLine(item.FirstMidName +" "+ item.LastName);
            }

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
         }
      }
   }
}

执行上述程序时,您将收到以下输出 -

All student in the database:
Ali Khan
Arturo   finand
Bill Gates
Carson Alexander
Gytis Barzdukas
Laura Norman
Meredith Alonso
Nino Olivetto
Peggy Justice
Yan Li
Press any key to exit...

当执行上述程序时,您将看到先前输入数据库中的所有学生的姓名。

我们建议您逐步执行上述示例,以便更好地理解。

实体框架 - DEV 方法

在本章中,让我们重点关注使用 Designer 或 Database First 或仅使用 Code First 构建模型。以下是一些指南,可帮助您决定选择哪种建模工作流程。

  • 我们已经看到了代码优先建模、数据库优先建模和模型优先建模工作流程的示例。

  • “数据库优先”和“模型优先”工作流使用设计器,但一个从数据库开始创建模型,另一个从模型开始创建数据库。

设计师模型
  • 对于那些不想使用可视化设计器和代码生成的开发人员来说,实体框架有一个完全不同的工作流程,称为“代码优先”。

  • Code First 的典型工作流程非常适合您甚至没有数据库的全新应用程序。您定义您的类和代码,然后让 Code First 确定您的数据库应该是什么样子。

  • 也可以从数据库开始 Code First,这使得 Code First 有点矛盾。但是有一个工具可以让您将数据库逆向工程为类,这是在编码方面取得领先的好方法。

考虑到这些选项,让我们看看决策树。

  • 如果您更喜欢在生成的代码中使用可视化设计器,那么您将需要选择涉及 EF 设计器的工作流之一。如果您的数据库已经存在,那么 Database First 就是您的路径。

  • 如果您想在没有数据库的全新项目上使用可视化设计器,那么您将需要使用模型优先。

  • 如果您只想使用代码而不是设计器,那么 Code First 可能适合您,并且可以选择使用将数据库逆向工程为类的工具。

  • 如果您有现有的类,那么最好的选择是将它们与 Code First 一起使用。

实体框架-数据库操作

在前面的章节中,您学习了定义实体数据模型的三种不同方法。

  • 其中两个,数据库优先和模型优先,依赖于实体框架设计器与代码生成的结合。

  • 第三个,Code First,让您跳过视觉设计器,只编写自己的代码。

  • 无论您选择哪条路径,您最终都会得到域类,并且一个或多个实体框架 DbContext 类允许您检索和保留与这些类相关的数据。

应用程序中的 DbContext API 用作类和数据库之间的桥梁。DbContext 是实体框架中最重要的类之一。

  • 它能够表达和执行查询。

  • 它从数据库中获取查询结果并将其转换为模型类的实例。

  • 它可以跟踪实体的更改,包括添加和删除,然后触发创建插入、更新和删除语句,并按需发送到数据库。

以下是本章中我们将对其执行不同操作的域广告上下文类。这与我们在数据库优先方法一章中创建的示例相同。

上下文类实现

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Linq;

namespace DatabaseFirstDemo {

   public partial class UniContextEntities : DbContext {

      public UniContextEntities(): base("name = UniContextEntities") {}

      protected override void OnModelCreating(DbModelBuilder modelBuilder) {
         throw new UnintentionalCodeFirstException();
      }

      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
   }
}

域类实现

课程班级

namespace DatabaseFirstDemo {

   using System;
   using System.Collections.Generic;
	
   public partial class Course {

      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", 
         "CA2214:DoNotCallOverridableMethodsInConstructors")]

      public Course() {
         this.Enrollments = new HashSet<Enrollment>();
      }
	
      public int CourseID { get; set; }
      public string Title { get; set; }
      public int Credits { get; set; }
	
      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", 
         "CA2227:CollectionPropertiesShouldBeReadOnly")]
			
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }
}

学生班

namespace DatabaseFirstDemo {

   using System;
   using System.Collections.Generic; 

   public partial class Student {

      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", 
         "CA2214:DoNotCallOverridableMethodsInConstructors")]

      public Student() {
         this.Enrollments = new HashSet<Enrollment>();
      }

      public int ID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
      public System.DateTime EnrollmentDate { get; set; }

      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", 
         "CA2227:CollectionPropertiesShouldBeReadOnly")]
			
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }
}

招生班级

namespace DatabaseFirstDemo {

   using System;
   using System.Collections.Generic; 

   public partial class Enrollment {

      public int EnrollmentID { get; set; }
      public int CourseID { get; set; }
      public int StudentID { get; set; }
      public Nullable<int> Grade { get; set; }
		
      public virtual Course Course { get; set; }
      public virtual Student Student { get; set; }
   }
}

创建操作

使用实体框架添加新对象就像构造对象的新实例并使用 DbSet 上的 Add 方法注册它一样简单。以下代码可让您将新学生添加到数据库中。

class Program {

   static void Main(string[] args) {

      var newStudent = new Student();

      //set student name

      newStudent.FirstMidName = "Bill";
      newStudent.LastName = "Gates";
      newStudent.EnrollmentDate = DateTime.Parse("2015-10-21");
      newStudent.ID = 100;

      //create DBContext object

      using (var dbCtx = new UniContextEntities()) {

         //Add Student object into Students DBset
         dbCtx.Students.Add(newStudent);

         // call SaveChanges method to save student into database
         dbCtx.SaveChanges();
      }
   }
}

更新操作

更改现有对象就像更新分配给要更改的属性的值并调用 SaveC 一样简单