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

使用 C# 进行 Entity Framework 入门 - 第一部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2016 年 6 月 10 日

CPOL

16分钟阅读

viewsIcon

23800

在本文中,我们将了解如何在通过 Visual Studio 允许我们使用的各种语言开发的应用程序中使用 Entity Framework。本文以及下一篇文章中的示例将是 C# 在 WinForms 下的使用,但正如前面提到的,这个选择不会影响开发者可能合理要求的不同预期用途。

引言


在本文中,我们将了解如何在通过 Visual Studio 提供的各种语言开发的应用程序中使用 Entity Framework。本文和下一篇文章中的示例将是 C# 在 WinForms 下的使用,但正如前面提到的,这个选择不会影响开发者可能合理要求的不同预期用途。

什么是 Entity Framework?

Entity Framework (以下简称 EF) 是 Microsoft 在 .NET 开发(3.5 SP1 及更高版本)中提供的 ORM(对象关系映射)框架。其目的是抽象出与关系数据库的绑定,以便开发者可以将数据库实体视为一组对象,然后进而将其视为类及其属性。本质上,我们谈论的是我们的应用程序与数据访问逻辑之间的解耦,这被证明是一个重要的优势。例如:如果我们需要在(单个程序上下文中)迁移到不同的数据库制造商,则需要审查我们与当前数据管理器交互的方式和指令。

Entity Framework 的方法

目前,EF 主要支持两种与此用法相关的方法。它们是 Database-First 和 Code-First(EF7 中不再提供第一种,但截至 6.1.3 版本仍然有效)。从名称上看,这两种方法之间的区别很明显:使用 Database-First 时,我们必须建模一个预先存在的数据库(因此,从中导出我们的对象);而在 Code-First 模式下,我们将需要通过提供代表表字段的属性来确定数据库结构。Code-First 并不一定意味着必须在没有数据库的情况下开始工作,因为我们可以对现有数据库中的类进行建模,并连接到它以执行常规的 I/O 操作。我们可以说,这两种方法除了某些工具性的特性外,代表了一种优先级排序,用于确定应用程序将要处理的数据结构,是“在数据库之前”(从中导出类)还是“在代码之前”(从中可以构建数据库模型)。

在项目中引用 Entity Framework

在查看 EF 的使用示例之前,您必须在项目中引用它,即使库可供解决方案访问。然后,我们将创建一个新的 Visual Studio 项目,选择所需的模板(在示例中,如预期的那样,将是 C# WinForms)。之后,保存解决方案(这很重要,以避免 EF 添加警报),然后打开 NuGet 包管理器。



在其中,我们将安装 Entity Framework 包,然后只需单击“安装”按钮即可将其添加到我们的解决方案中。完成后,我们将看到 EF 相关的引用已包含在项目的引用中。

现在,我们已准备好在开发方面使用我们 ORM 的潜力。我们还将了解如何根据上述两种范例执行初始数据库连接和模型类。

Database-First

让我们首先考虑 Database-First 模式,它——正如我们所说——主要针对现有数据库,即从中导出模型的数据库。再次提醒,至少在撰写本文时,EF7 及更高版本已弃用此模式,转而支持 Code-First,这一点在评估解决方案分析时非常重要。

准备数据库

出于说明目的,我们创建了一个名为 TECHNET 的数据库,其中包含两个表:“Articles”和“Families”。创建实体以及后续内容的 T-SQL 脚本如下。

USE [TECHNET]
GO
 
SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER ON
GO
 
CREATE TABLE [dbo].[Articoli](
    [IdArticolo] [int] IDENTITY(1,1) NOT NULL,
    [CodArt] [nvarchar](25) NOT NULL,
    [DesArt] [nvarchar](50) NOT NULL,
    [CodFamiglia] [nvarchar](6) NOT NULL,
 CONSTRAINT [PK_Articoli] PRIMARY KEY CLUSTERED
(
    [IdArticolo] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
 
GO
 
ALTER TABLE [dbo].[Articoli] ADD  CONSTRAINT [DF_Articoli_CodArt]  DEFAULT ('') FOR [CodArt]
GO
 
ALTER TABLE [dbo].[Articoli] ADD  CONSTRAINT [DF_Articoli_DesArt]  DEFAULT ('') FOR [DesArt]
GO
 
ALTER TABLE [dbo].[Articoli] ADD  CONSTRAINT [DF_Articoli_CodFamiglia]  DEFAULT ('') FOR [CodFamiglia]
GO
 
CREATE TABLE [dbo].[Famiglie](
    [CodFamiglia] [nvarchar](6) NOT NULL,
    [DesFamiglia] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Famiglie] PRIMARY KEY CLUSTERED
(
    [CodFamiglia] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
 
GO

创建表后,用一些数据填充它们。

USE [TECHNET]
 
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART001', 'ARTICOLO TEST' , 'FAM01')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART002', 'PRODOTTO PROVA', 'FAM01')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART003', 'ART. 003', 'FAM02')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART004', 'ART. 004', 'FAM02')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART005', 'ART. 005', 'FAM02')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART006', 'ART. 006', 'FAM02')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART007', 'ART. 007', 'FAM03')
INSERT INTO Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART008', 'ART. 008', 'FAM04')
 
INSERT INTO Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM01', 'PROD. MECCANICI')
INSERT INTO Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM02', 'PROD. ELETTRONICI')
INSERT INTO Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM03', 'RICAMBI')
INSERT INTO Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM04', 'IMPORT')

您会注意到两个表之间存在一个简单的、未明确声明的链接,其中 CodFamiglia 是连接 Articles 表和 Families 表以访问后者的描述的字段。运行脚本,我们将获得一些记录。

使用 EF 对模型进行类建模

让我们看看如何在 Visual Studio 端对我们的程序中用于使用这些数据实体的类进行建模。

我们向解决方案添加一个新元素,并在 Visual C# 项目->数据菜单中,选择一个 ADO.NET Entity Data Model 对象,我们将其命名为“TechnetModello”。



确认选择后,Visual Studio 将启动 Entity Data Model 的向导,以便我们可以指定要使用的模型类型。在这种情况下,我们的选择将是“EF Designer from database”,它实现了 Database-First 范例。



系统将要求您连接到我们的数据库,单击“New Connection”并随后设置连接参数。下图显示了一个示例,您可以根据实际开发环境进行调整。



我们将看到生成的连接字符串,并可以将其保存在 App.config 中,并为其命名以供将来使用。



最后,系统将询问我们将要在模型中创建哪些实体。在我们的例子中,我们选择创建的两个表,其余设置保留为默认值。



向导将完成事件操作。它将生成一个数据模型,该模型由数据库实体的类表示,并显示一个图形表示,我们可以在其上创建显式关联或更改某些属性。

重要

遵循 Database-First 范例,我们已经提到数据库将提供框架建模类,而不是反之。因此,如果我们在此模式下修改了某些属性,然后需要重新生成模型,这些更改——仅存在于代码的一侧——将丢失,被数据库中的声明所覆盖。



通过浏览向导为我们创建的类,我们注意到两个特别重要的类,它们在层次结构上——它们是 TechnetModello.tt 文件的子代——Articoli.cs 和 Famiglie.cs。

打开它们,我们会注意到它们遵循它们所引用的表的结构,将类名公开为与表名相同,字段作为其对应项,并适当转换为代码侧使用的类型。



一个简单的查询

假设您想从 items 表中提取 Codart = 'ART001' 的记录,并想显示视频描述(DESART 字段)。由于我们的数据上下文已转换为类,我们可以使用方便的 LINQ 语法,应用于 TECHNETEntities 数据上下文的子数据实体。

查询如下所示:

using (TECHNETEntities db = new TECHNETEntities())
{
    Articoli art = db.Articoli.Where((x) => x.CodArt == "ART001").FirstOrDefault();
 
    MessageBox.Show(art.DesArt);
}

除了实体本身,我们现在还链接到了表类型:art 变量已被声明为 item 类型,拥有其类的所有属性。

我们在 items 类上执行 Where 方法,使用 lambda 函数选择所需的项编号,并使用 FirstOrDefault 方法返回找到的第一个对象(此处未进行数据有效性检查,在实际环境中当然必须提供)。

此时,使用 art 变量,我们可以引用它的属性,然后请求显示一个 MessageBox,其文本等于 art 的 DESART 属性,我们得到以下输出:



显而易见,Entity Framework 具有巨大的潜力,这对于开发人员来说意味着非常重要的因素,例如大大节省时间,并能将他们的精力集中在解决方案本身,而不是其与数据库之间的中间层,后者现在已完全管理。

表之间的关系和模型后果

在模型中,我们确定 Articles 和 Families 两个实体是相互关联的。实际上,我们没有定义可以指示 EF 如何连接这两个表的 Foreign Keys,它们是独立的。在这种情况下,我们将不得不求助于更复杂的 LINQ 指令,这些指令实现了在模型级别上缺失的 join。

假设我们想选择 ART005 项,并想在屏幕上显示其产品描述,而不是其自身类的描述。在当前情况下,我们可以这样请求:

using (TECHNETEntities db = new TECHNETEntities())
{
    var art = db.Articoli
                .Join(db.Famiglie,
                      articolo => articolo.CodFamiglia,
                      famiglia => famiglia.CodFamiglia,
                      (articolo, famiglia) => new { Articoli = articolo, Famiglie = famiglia})
                .Where((x) => x.Articoli.CodArt == "ART005")
                .FirstOrDefault();
 
    MessageBox.Show(art.Articoli.DesArt + " - " + art.Famiglie.DesFamiglia);
}

像之前一样实例化 TECHNETEntities 对象,这次我们声明一个隐式类型。它将包含 LINQ 查询结果,该结果将根据以下条件展开:选择 db.Articoli,然后根据它们各自的 CodFamiglia 字段与 db.Famiglie 进行 Join,并从中提取;Join 一个包含两者属性的新对象。Where 方法访问 Articles 类并在该对象上进行 Codart 字段的选择,然后返回查询的第一个元素。

我们的 art 变量现在是一个复合类型,我们可以分别访问项部分和 Families 部分的属性:请注意 MessageBox 中如何显示要发出的文章描述以及系列的描述。通过搜索产品 ART005 进行测试,显示的结果如下:



如果我们已经在设计阶段考虑到了这个约束,在适当的地方插入了 Foreign Keys,EF 就会注意到它,并会自动创建这种报告。然后,我们对数据库结构进行更改,以查看这如何影响模型类。

使用 SQL Management Studio 方便地在表中插入项,一个 Foreign Key 来建立 Articles 和 Families 之间的关系。



保存数据库更改后,Visual Studio 端需要重新生成数据模型。只需打开 TechnetModello.edmx 文件,它会图形化地显示我们的数据模型,然后右键单击选择“Update Model from Database”,就会出现下图:



接着是一个简短的向导,我们只需确认刷新。

因此,我们将看到我们的模型将被升级,从而在两个表之间建立关系。



这一步将重要地修改我们的类。Families 类将获得一个名为 Articles、类型为 ICollection <Articles> 的新属性,而 Articles 类现在有一个名为 Families、类型为 Families 的属性,声明为 virtual。



这意味着在我们的数据模型中,这两个实体将自然地相互关联。在这种情况下,如果我们想再次运行第一个查询,即文章描述和系列的描述,我们将不会直接指示编译器表是如何关联的,而是可以引用 EF 提供的属性。
用语言来说,最后一个例子变成了:

using (TECHNETEntities db = new TECHNETEntities())
{
      Articoli art = db.Articoli.Where((x) => x.CodArt == "ART005").FirstOrDefault();
 
      MessageBox.Show(art.DesArt + " - " + art.Famiglie.DesFamiglia);
}

换句话说,items 类已经包含了一个从数据库派生的 Families 属性。因此,我们可以直接查询 Articles,然后从中选择想要的属性,知道它会找到 Families 属性,这将解决独立表之间存在的 Join,并且可以直接从 items 类型变量访问。

Code First

在本节中,我们将介绍 Code-First 模式,该模式可用于此数据库。而关于从零(或几乎从零)开始定义数据库的方法,将留待下一篇文章。下载部分的项目就属于第二种模式。考虑到 Code-First(无论它来自预定义模板还是完全自定义)比物理数据基础和读取器具有更高的优先级,因此读者可以自己创建 SQL Server 实例。为了方便起见,我们创建了一个名为 Model 的文件夹,其中将包含 EF 最初创建的类,但我们可以对模型进行修改。

再次添加一个 ADO.NET Entity Data Model 对象,我们将其命名为“TechnetModelloCF”。要遵循的步骤与之前看到的类似,不同之处在于要选择的模式,在这种情况下将是“Code-First from Database”。我们将继续完成剩余的步骤,就像在 Database-First 的情况下一样。

类定义

正如我们将看到的,与 Database-First 中生成的类相比,这些类在图形表示和实体关系方面有显著差异。让我们从一个类开始详细查看:在 Database-First 的情况下,这并不明显。

namespace ArticoloEF.Model
{
    using System;
    using System.Data.Entity;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
 
    public partial class TechnetModelloCF : DbContext
    {
        public TechnetModelloCF()
            : base("name=TechnetModelloCF")
        {
        }
 
        public virtual DbSet<Articoli> Articoli { get; set; }
        public virtual DbSet<Famiglie> Famiglie { get; set; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Famiglie>()
                .HasMany(e => e.Articoli)
                .WithRequired(e => e.Famiglie)
                .WillCascadeOnDelete(false);
        }
    }
}

这是 TechnetModelloCF 类,我们分配给模型的类名,它将继承 EF 的 DbContext 类的类型和功能。这个类的主要任务是提供数据库实体,也建立特定数据库参数的生成,以及根据需要解释对象之间的关系。


在我们的例子中,我们首先注意到有一个构造函数,其中传递了“name = TechnetModelloCF”参数。制造商将在需要时尝试建立数据库连接,这将通过传递的参数完成,该参数可以直接是 ConnectionString,或者像示例中那样,是一个指向 App.config 文件中定义的 ConnectionString 的名称。

接下来,定义了访问范围(我们的表),它们将是 DbSet 类型对象,基于各自的类定义。它们也被声明为 virtual,以便允许物理访问存储数据。最后,在重写 OnModelCreating 方法(其中定义了模型创建规则)中,明确了两个实体之间的关系。更具体地说,它在 ModelBuilder 中指示了这个 Family 实体,它以多对一关系与 items 绑定,并且在主表删除引用到该次级表时,不会执行级联删除操作。

让我们看看另外两个类。

让我们从 Articoli.cs 开始。

namespace ArticoloEF.Model
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;
 
    [Table("Articoli")]
    public partial class Articoli
    {
        [Key]
        public int IdArticolo { get; set; }
 
        [Required]
        [StringLength(25)]
        public string CodArt { get; set; }
 
        [Required]
        [StringLength(50)]
        public string DesArt { get; set; }
 
        [Required]
        [StringLength(6)]
        public string CodFamiglia { get; set; }
 
        public virtual Famiglie Famiglie { get; set; }
    }
}

实际上,与 Database-First 中生成的类相比,差别不大,只是增加了一些括号内的指示,位于各个字段前面。这些被称为 Data Annotations,它们非常有用且简洁,可以在声明数据特定方面时提供极大的灵活性。这不是强制性指令,但同时也可以更精确地建模我们的实体。


在示例中,已知的类是在类之前声明的 Table 记录。它指定了数据库;也就是说,要引用的表名。如果未指定,表将假定类名(这是最正常的情况,但可能需要选择一个与类名不同的表名)。在字段上,我们看到三个特殊条件:Key,前缀是 articleID 字段,它指示模型该表的主键。事实上,articleID 是一个必填字段(对应 T-SQL NOT NULL 约束),而 StringLength 指定了最大长度。

在必要的区分下,Famiglie.cs 类也使用了相同类型的表示法。

namespace ArticoloEF.Model
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;
 
    [Table("Famiglie")]
    public partial class Famiglie
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Famiglie()
        {
            Articoli = new HashSet<Articoli>();
        }
 
        [Key]
        [StringLength(6)]
        public string CodFamiglia { get; set; }
 
        [Required]
        [StringLength(50)]
        public string DesFamiglia { get; set; }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Articoli> Articoli { get; set; }
    }
}

在这种情况下,您会注意到一个额外的类构造函数,它基于 HashSet 类初始化定义为 ICollection <Articles> 的属性,该类执行列表操作。

类修改和迁移
假设我们想在 Items 表中添加一个字段。我们插入一个名为 CostoStandard 的字段,类型为 Decimal。




如果我们现在尝试运行应用程序会怎样?等待几秒钟后,将会引发一个错误,警告我们数据模型与我们尝试访问的内容之间的差异。在这种情况下,CostoStandard 列不是一个有效字段,事实上,它不在数据库中。



在这里,我们引入了迁移的概念:EF 可以根据我们的类和数据库进行比较,来确定需要对其进行哪些更改,以使其与程序中设置的模型兼容。我们首先准备我们的项目迁移。这种操作是通过程序包管理器控制台(工具 » Nuget 包管理器 » 程序包管理器控制台)进行的,该控制台提供了多个 PowerShell cmdlet,包括我们将要使用的。

要使项目能够进行迁移,您必须输入:

Enable-Migrations  



在此注意到,EF 将检查是否存在一个数据库用于进行第一次比较。该命令将在项目中创建一个名为 Migrations 的目录,该目录最初将只包含 Configuration.cs 文件。这个文件很有用,例如,如果我们想部署初始数据,假设从头开始构建数据库或引入包含预加载记录的表;我们可以通过 Seed 方法创建它们——这是下一篇文章的主题。

可以使用以下命令创建迁移:

Add-Migration <NOME_MIGRAZIONE>  

其中 <NAME MIGRATION> 是开发者输入的文本,用于标识该特定迁移版本。输出如图所示:



在这里,我请求创建了一个名为 CostoStandardSuArticoli 的迁移。您会注意到在 Migration 文件夹中创建了一个类文件(其类扩展了 DbMigration 类型),其名称由当前时间戳和我们输入的名称组成。在下面的屏幕截图中,您可以看到创建文件的内容,我们看到一系列用于根据我们修改后的类定义数据库的指令。因此,我们看到在 Articles 表的创建指令中,有新的 CostoStandard 字段。

在创建自定义迁移并进行初始启动迁移之前,最好通过以下指令:

Add-Migration InitialCreate –IgnoreChanges  

这使得在当前环境中,我们仍然从现有数据库派生数据的 Code-First 数据建模能够完全投入实践。



此时,您只需启动迁移,然后创建并观察结果。

这通过以下指令完成:

Update-Database  

操作完成,通过 Management Studio 显示 items 表的架构,可以看到新列。

下载

通过以下链接,您可以下载示例中使用的 Code-First 项目:

https://code.msdn.microsoft.com/Introduction-to-Entity-09676091

要正确使用源代码,必须更改 App.config 文件中指定的 SQL Server 实例的访问权限,并且在该实例中必须创建一个名为 TECHNET 的数据库。

结论

在第一部分中,我们仅触及了 Entity Framework 的表面,尝试了解它是什么,它如何帮助我们,以及它使用的方法。我们看到了由数据库创建的对象的使用示例,并开始初步了解迁移机制。我们建议读者熟悉这些基本知识,并期待在接下来的系列文章中继续进行此概述。

其他语言

本文也有以下本地化版本:

© . All rights reserved.