开始使用 Entity Framework Core: 数据库优先开发






4.86/5 (30投票s)
本文是“Entity Framework Core 入门”系列文章的第一部分。在本篇中,我们将构建一个 ASP.NET Core MVC 应用程序,该程序使用 Entity Framework Core 执行基本的数据访问。
什么是对象关系映射器?
ORM(对象关系映射器)使开发人员能够通过编程方式操作概念性的应用程序模型,而不是直接编程操作关系型存储模式,从而构建数据访问应用程序。目标是减少数据相关应用程序所需的代码量和维护工作。像 Entity Framework Core 这样的 ORM 提供了以下好处:
- 应用程序可以采用更面向应用程序的概念模型进行工作,包括具有继承、复杂成员和关系的类型。
- 应用程序无需硬编码依赖于特定的数据引擎或存储模式。
- 概念模型与特定于存储的模式之间的映射可以更改,而无需更改应用程序代码。
- 开发人员可以处理一致的应用程序对象模型,该模型可以映射到各种存储模式,这些模式可能在不同的数据库管理系统中实现。
- 多个概念模型可以映射到单个存储模式。
- 语言集成查询 (LINQ) 支持为对概念模型进行查询提供编译时语法验证。
什么是 Entity Framework Core?
根据官方文档:Entity Framework (EF) Core 是流行的 Entity Framework 数据访问技术的一个轻量级、可扩展且跨平台的版本。EF Core 是一个对象关系映射器 (O/RM),它使 .NET 开发人员能够使用 .NET 对象与数据库进行交互。它消除了开发人员通常需要编写的大部分数据访问代码。
要获取有关 EF Core 的更多信息,我建议您访问官方文档:https://docs.microsoft.com/en-us/ef/core/
设计工作流
与任何其他 ORM 一样,Entity Framework Core 支持两种主要的设计工作流:代码优先方法,即您创建类(POCO 实体)并从中生成新数据库。数据库优先方法允许您使用现有数据库并根据您的数据库模式生成类。在本篇中,我们先探讨数据库优先方法。
如果您仍然对两者之间的区别和优势感到困惑,请放心,我们将在本系列的下一部分中详细介绍。
开始吧!
如果您已准备好探索 EF Core 数据库优先开发并亲手实践,那么让我们开始吧!
数据库创建
在此演练中,为了简单起见,我们将只创建一个数据库表,其中包含一些简单的表属性。请继续打开 Microsoft SQL Server Management Studio 并运行下面的 SQL 脚本来创建数据库和表。
CREATE DATABASE EFCoreDBFirstDemo
GO
USE [EFCoreDBFirstDemo]
GO
CREATE TABLE [dbo].[Student](
[StudentId] [bigint] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](30) NULL,
[LastName] [varchar](30) NULL,
[Gender] [varchar](10) NULL,
[DateOfBirth] [datetime] NULL,
[DateOfRegistration] [datetime] NULL,
[PhoneNumber] [varchar](20) NULL,
[Email] [varchar](50) NULL,
[Address1] [varchar](50) NULL,
[Address2] [varchar](50) NULL,
[City] [varchar](30) NULL,
[State] [varchar](30) NULL,
[Zip] [nchar](10) NULL,
CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED
(
[StudentId] 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
上面的 SQL 脚本应该会创建“EFCoreDBFirstDemo”数据库,其中包含以下表:
图 1:dbo.Student SQL 表
没什么花哨的。dbo.Student
表只包含一些基本属性,供我们稍后在 Web 应用程序中使用。
创建 ASP.NET Core 项目
我们的下一步是创建一个网页,我们可以在其中发送和检索数据库中的数据。
启动 Visual Studio 2017,让我们创建一个新的 ASP.NET Core Web 应用程序项目。为此,请选择“文件”>“新建”>“项目”。在“新建项目”对话框中,选择“Visual C#”>“Web”>“ASP.NET Core Web 应用程序 (.NET Core)”,如下图所示:
图 2:ASP.NET Core Web 应用程序项目模板
将您的项目命名为您喜欢的任何名称,但在本次练习中,我们将将其命名为“DBFirstDevelopment”,以符合主题。单击“确定”,然后选择“ASP.NET Core 模板”中的“Web 应用程序”,如下图所示:
图 3:ASP.NET Core Web 应用程序模板
现在单击“确定”,让 Visual Studio 生成运行应用程序所需的必要文件。下图显示了默认生成的项目文件:
图 4:默认 ASP.NET Core Web 应用程序项目文件
如果您不熟悉 ASP.NET Core 中生成的新文件结构,请不用担心,我们将在本系列的下一部分中了解它们。现在,我们继续前进。
首次运行
按 CTRL + F5 或单击工具栏上的 IIS Express 运行按钮,生成并运行应用程序。如果您看到下面的输出,那么恭喜您,您现在拥有一个正在运行的 ASP.NET Core Web 应用程序。
图 5:首次运行
模型
我们在应用程序的根目录下创建一个名为“Models”的文件夹,在该文件夹下再创建一个名为“DB”的文件夹。现在我们的项目结构应该看起来像这样:
图 6:Models 文件夹
“DB”文件夹将包含我们的 DBContext 和实体模型。我们将使用 Entity Framework Core 作为数据访问机制来处理数据库。我们不会使用老式的 Entity Framework 设计器来为我们生成模型,因为 EF 设计器 (EDMX) 不支持 ASP.NET Core 1.1。
集成 Entity Framework Core
ASP.NET Core 被设计成轻量级、模块化和可插拔的。这使我们可以插入仅对我们的项目必需的组件。话虽如此,我们需要将 Entity Framework Core 包添加到我们的 ASP.NET Core 应用程序中,因为我们将需要它。有关 Entity Framework Core 的更多详细信息,请查看本文末尾的参考文献部分。
有两种方法可以在 ASP.NET Core 中添加包:您可以使用程序包管理器控制台,或通过 NuGet 包管理器 (NPM)。在本次练习中,我们将使用 NPM,以便您有一个直观的参考。
现在,右键单击应用程序的根目录,然后选择“管理 NuGet 程序包”。选择“浏览”选项卡,然后在搜索栏中键入“Microsoft.EntityFrameworkCore.SqlServer”。结果应该如下所示:
图 7:管理 NuGet 包
选择“Microsoft.EntityFrameworkCore.SqlServer”并单击“安装”。在撰写本文时,最新的稳定版本是 v1.1.2。您可能会看到“更改审查”和“许可接受”对话框。只需单击“我接受”,然后按照向导说明操作,直到完成安装。
由于我们将使用数据库优先开发方法来处理现有数据库,因此我们还需要安装下面的其他包:
- Microsoft.EntityFrameworkCore.Tools (v1.1.1)
- Microsoft.EntityFrameworkCore.SqlServer.Design (v1.1.2)
现在,请继续通过程序包管理器控制台或 NuGet 包管理器 (NPM) GUI 安装它们。
使用程序包管理器控制台
转到“工具”>“NuGet 包管理器”>“程序包管理器控制台”并单独运行以下命令:
Install-Package Microsoft.EntityFrameworkCore.Tools Install-Package Microsoft.EntityFrameworkCore.SqlServer.Design
使用 NuGet 包管理器 GUI
转到“工具”>“NuGet 包管理器”>“管理解决方案的 NuGet 包”,然后键入“Microsoft.EntityFrameworkCore.Tools”,如下图所示:
图 8:添加 Microsoft.EntityFrameworkCore.Tools 包
单击“安装”,然后对“Microsoft.EntityFrameworkCore.SqlServer.Design”执行相同的操作。
图 9:添加 Microsoft.EntityFrameworkCore.SqlServer.Design 包
单击“安装”以将包还原到您的项目。
引用注释:
1. 始终建议安装每个包的最新稳定版本,以避免开发过程中出现意外错误。
2. 如果您遇到“无法解析 'Microsoft.EntityFrameworkCore.Tools (>= 1.1.2)' for '.NETCoreApp,Version=v1.1'”此错误,在生成项目时,只需重启 Visual Studio 2017。
还原所有必需的包后,您应该会在项目依赖项中看到它们,如下图所示:
图 10:Entity Framework Core 包已还原
有关 Entity Framework Core 中新功能的详细信息,请参阅本文末尾的参考文献部分。
从现有数据库创建实体模型
现在,是时候根据我们之前创建的现有数据库创建 Entity Framework 模型了。
在撰写本文时,有两种方法可以从现有数据库生成模型(也称为反向工程)。
选项 1:使用程序包管理器控制台
- 转到“工具”–>“NuGet 包管理器”–>“程序包管理器控制台”。
- 然后运行下面的命令,从现有数据库创建模型:
Scaffold-DbContext "Server=SSAI-L0028-HP\SQLEXPRESS;Database=EFCoreDBFirstDemo;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models/DB
上面的 Server 属性是 SQL Server 实例名称。您可以在 SQL Management Studio 中通过右键单击数据库来找到它。在此示例中,服务器名称为“SSAI-L0028-HP\SQLEXPRESS”。
Database 属性是您的数据库名称。在这种情况下,数据库名称为“EFCoreDBFirstDemo”。
–OutputDir 属性允许您指定生成文件的位置。在这种情况下,我们将其设置为 Models/DB。
选项 2:使用命令行窗口
如果由于某种原因选项 1 对您不起作用,请尝试以下方法:
- 转到应用程序的根文件夹。在这种情况下是“DBFirstDevelopment”。
- 按住 Shift 键,右键单击并选择“在此处打开命令窗口”。
- 然后运行以下脚本:
dotnet ef dbcontext scaffold "Server=SSAI-L0028-HP\SQLEXPRESS;Database=EFCoreDBFirstDemo;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer --output-dir Models/DB
上面的脚本与我们在选项 1 中使用的脚本非常相似,除了我们使用命令 dotnet ef dbcontext scaffold
和 --output-dir
来设置脚手架文件的目标位置。
引用请注意,您需要根据您的数据库服务器配置更改 Server 值。如果您使用的是不同的数据库名称,则也需要更改 Database 值。
上面的命令将在 Models/DB 文件夹中从现有数据库生成模型。这是下面的屏幕截图:
图 11:Entity Framework 生成的模型
引用技巧
如果您仍然遇到错误,则可能需要将 PowerShell 升级到 5 版本。
- 您可以在此处下载:https://www.microsoft.com/en-us/download/details.aspx?id=50395
- 您需要在连接字符串中根据您的服务器配置更改 Server 和 Database 属性的值。
反向工程过程根据现有数据库的模式创建了 Student.cs
实体类和派生上下文 (EFCoreDBFirstDemoContext.cs)。
以下是生成的代码。
Student 类
using System;
using System.Collections.Generic;
namespace DBFirstDevelopment.Models.DB
{
public partial class Student
{
public long StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public DateTime? DateOfBirth { get; set; }
public DateTime? DateOfRegistration { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
}
上面的实体类不过是一个简单的 C# 对象,代表您将查询和保存的数据。
EFCoreDBFirstDemoContext 类
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
namespace DBFirstDevelopment.Models.DB
{
public partial class EFCoreDBFirstDemoContext : DbContext
{
public virtual DbSet<Student> Student { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer(@"Server=SSAI-L0028-HP\SQLEXPRESS;Database=EFCoreDBFirstDemo;Trusted_Connection=True;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>(entity =>
{
entity.Property(e => e.Address1).HasColumnType("varchar(50)");
entity.Property(e => e.Address2).HasColumnType("varchar(50)");
entity.Property(e => e.City).HasColumnType("varchar(30)");
entity.Property(e => e.DateOfBirth).HasColumnType("datetime");
entity.Property(e => e.DateOfRegistration).HasColumnType("datetime");
entity.Property(e => e.Email).HasColumnType("varchar(50)");
entity.Property(e => e.FirstName).HasColumnType("varchar(30)");
entity.Property(e => e.Gender).HasColumnType("varchar(10)");
entity.Property(e => e.LastName).HasColumnType("varchar(30)");
entity.Property(e => e.PhoneNumber).HasColumnType("varchar(20)");
entity.Property(e => e.State).HasColumnType("varchar(30)");
entity.Property(e => e.Zip).HasColumnType("nchar(10)");
});
}
}
}
EFCoreDBFirstDemoContext
类代表与数据库的会话,并允许您查询和保存实体类的实例。
如果您注意到,生成的模型创建为部分类。这意味着您可以通过为每个实体/模型类创建另一个部分类来按需扩展它们。
使用依赖注入注册 DBContext
下一步是使用依赖注入 (DI) 注册我们的 EFCoreDBFirstDemoContext
类。为了遵循 ASP.NET Core 的配置模式,我们将数据库提供商配置移至 Startup.cs
。为此,请按照以下步骤操作:
- 打开 Models\DB\EFCoreDBFirstDemoContext.cs 文件
- 删除
OnConfiguring()
方法并添加以下代码:
public EFCoreDBFirstDemoContext(DbContextOptions<EFCoreDBFirstDemoContext> options)
: base(options)
{ }
上面的构造函数将允许通过依赖注入将配置传递到上下文。
3. 打开 appsettings.json
文件并在下方添加以下脚本作为我们的数据库连接字符串:
, "ConnectionStrings": { i. "EFCoreDBFirstDemoDatabase": "Server=SSAI-L0028-HP\\SQLEXPRESS;Database=EFCoreDBFirstDemo;Trusted_Connection=True;" }
4. 打开 Startup.cs
5. 在文件开头添加以下 using 语句:
using DBFirstDevelopment.Models; using DBFirstDevelopment.Models.DB; using Microsoft.EntityFrameworkCore;
6. 在 ConfigureServices()
方法中添加以下代码行:
// Add ASPNETCoreDemoDBContext services. services.AddDbContext<EFCoreDBFirstDemoContext>(options => options.UseSqlServer(Configuration.GetConnectionString("EFCoreDBFirstDemoDatabase")));
就是这样。现在我们准备处理数据了。
脚手架控制器及其视图
现在我们的数据访问已经就绪,我们可以通过创建一个 ASP.NET Core MVC 控制器来处理数据并执行基本的创建、读取、更新和删除 (CRUD) 操作。
启用项目脚手架
- 在“解决方案资源管理器”中右键单击
Controllers
文件夹,然后选择“添加”>“控制器”。 - 选择“完整依赖项”并单击“添加”。
- 您可以忽略或删除 ScaffoldingReadMe.txt 文件。
现在脚手架已启用,我们可以为 Student
实体脚手架一个控制器,或者只生成一个空控制器。在此示例中,我们将使用 Entity Framework 生成带有视图的控制器。
- 在“解决方案资源管理器”中右键单击 Controllers 文件夹,然后选择“添加”>“控制器”。
- 选择“使用 Entity Framework 的 MVC 控制器(附带视图)”。
- 单击“添加”。
在“添加控制器”对话框中,执行以下操作:
- 将 Student 选为模型类。
- 将 EFCoreDBFirstDemoContext 选为数据上下文类。
- 勾选“生成视图”选项。
- 在“使用布局页”选项中,浏览至 Views > Shared > _Layout.cshtml。
这是示例的屏幕截图:
图 12:添加控制器对话框
只需单击“添加”。脚手架将生成控制器和运行应用程序所需的许多视图文件。这是脚手架文件的屏幕截图:
图 13:生成的视图文件
正如您所看到的,脚手架为您节省了大量时间和精力,因为您不必担心生成视图。执行 CRUD 操作的所有方法也为您生成,无需您编写代码,从而提高了您的工作效率。如果您需要更改“视图”或“控制器”方法中的任何内容,则可能会很少。
生成的代码
这是 StudentsController
类的生成代码。
using DBFirstDevelopment.Models.DB;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace DBFirstDevelopment.Controllers
{
public class StudentsController : Controller
{
private readonly EFCoreDBFirstDemoContext _context;
public StudentsController(EFCoreDBFirstDemoContext context)
{
_context = context;
}
// GET: Students
public async Task<IActionResult> Index()
{
return View(await _context.Student.ToListAsync());
}
// GET: Students/Details/5
public async Task<IActionResult> Details(long? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Student
.SingleOrDefaultAsync(m => m.StudentId == id);
if (student == null)
{
return NotFound();
}
return View(student);
}
// GET: Students/Create
public IActionResult Create()
{
return View();
}
// POST: Students/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("StudentId,FirstName,LastName,Gender,DateOfBirth,DateOfRegistration,PhoneNumber,Email,Address1,Address2,City,State,Zip")] Student student)
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(student);
}
// GET: Students/Edit/5
public async Task<IActionResult> Edit(long? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Student.SingleOrDefaultAsync(m => m.StudentId == id);
if (student == null)
{
return NotFound();
}
return View(student);
}
// POST: Students/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, [Bind("StudentId,FirstName,LastName,Gender,DateOfBirth,DateOfRegistration,PhoneNumber,Email,Address1,Address2,City,State,Zip")] Student student)
{
if (id != student.StudentId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!StudentExists(student.StudentId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(student);
}
// GET: Students/Delete/5
public async Task<IActionResult> Delete(long? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Student
.SingleOrDefaultAsync(m => m.StudentId == id);
if (student == null)
{
return NotFound();
}
return View(student);
}
// POST: Students/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(long id)
{
var student = await _context.Student.SingleOrDefaultAsync(m => m.StudentId == id);
_context.Student.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
private bool StudentExists(long id)
{
return _context.Student.Any(e => e.StudentId == id);
}
}
}
让我们快速回顾一下上面所做的。
StudentsController
类使用构造函数注入来访问 EFCoreDBFirstDemoContext
中定义的 DBContext
和 DBSet
。DBContext
包含用于执行某些操作的虚拟方法,例如 Add()
、Update()
、Find()
、SaveChanges()
等。它还包含相应的异步方法,例如 AddAsync()
、FindAsync()
、SaveChangesAsyn()
等。EFCoreDBFirstDemoContext
类仅包含一个 DBSet
,即 Student
实体,它被定义为 DBSet<Student>
。
Index()
操作方法从数据库获取所有 Student 记录。此方法定义为异步,它将返回相应的 Index View
以及 Students
实体列表。
Details()
操作方法根据参数 id 获取相应的数据库记录。当参数 id 的值为 null 时,它返回 NotFound()
,否则它使用 LINQ
语法根据相应 id 从数据库中获取记录。如果 LINQ SingleOrDefaultAsync()
方法返回一行,则 Details()
方法会将 Student
模型返回到 View,否则返回 NotFound()
。
Create()
操作方法简单地返回相应的视图。重载的 Create()
操作方法被 [HttpPost]
属性装饰,这表示该方法只能响应 POST
请求。此方法是我们实际处理将新数据添加到数据库的地方,前提是模型状态在发布时有效。
Edit()
操作方法将 id 作为参数。如果 id 为 null,则返回 NotFound()
,否则将相应数据返回到 View
。重载的 Edit()
操作方法根据 id 更新数据库中的相应记录。
与其他操作一样,Delete()
操作方法将 id 作为参数。如果 id 为 null,则返回 NotFound()
,否则将相应数据返回到 View
。重载的 Delete()
操作方法根据 id 删除数据库中的相应记录。
测试应用程序
现在使用 CTRL + 5 生成并运行应用程序。在浏览器中,导航到 /students/Create。它应该会显示以下页面。
图 14:创建新学生页面
在页面上填写字段,然后单击“创建”按钮。它应该会带您到显示已插入数据的索引页,如下图所示:
图 15:索引页
当然,编辑、查看详细信息和删除功能也无需您进行任何编码即可正常工作。:)
摘要
在本系列文章中,我们学习了使用 Entity Framework Core 和现有数据库构建 ASP.NET Core MVC 应用程序。我们还学习了脚手架的强大功能,只需单击几下即可根据 Entity Framework 模型快速生成带有视图的控制器。
下载源代码
参考文献
- https://blogs.msdn.microsoft.com/webdev/2017/03/07/announcing-visual-studio-2017/
- https://docs.microsoft.com/en-us/ef/core/get-started/aspnetcore/existing-db
- https://docs.microsoft.com/en-us/ef/core/providers/
- https://blogs.msdn.microsoft.com/webdev/2016/11/16/announcing-asp-net-core-1-1/
- https://docs.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext