六、数据访问——EF7 与存储库,SQL Server,和存储过程

在本章中,我们将涵盖以下内容:

  • 为存储库模式配置 IOC 生命周期
  • 在现有数据库中使用 EF
  • 使用 InMemory 提供程序
  • 管理长请求批处理
  • 使用带有 EF 的存储过程
  • 编写 EF 提供者

介绍

在本章中,我们将学习如何使用称为实体框架的对象关系映射器创建一个数据访问层。 我们将配置它的 IOC 生命周期,创建一个包含或不包含存储过程的 CRUD,并记录和管理异常。

我们将首先解释实体框架内部使用的一些概念,这些概念在 orm 中反复出现,然后我们将看到 EF 中哪些特性不再存在于 EF 中。

ORM 是一个将数据类映射到数据库中的数据表的库,例如Customer类映射到数据库中的 Customer 表,BankTransaction类映射到数据库中的BankTransaction表,等等。

ORM 库帮助开发人员构建数据访问层,而不需要考虑表查询的复杂性。 例如,它们可以帮助您将表中接下来 20 行中的IsDeleted字段设置为False。 用 ORM 库编写这类查询非常容易。

DbContext

ORM 使用 SQL 模式的代码表示来请求数据库。 最初,他们映射了一个关系数据库。 现在有很多包装器来映射 ORM,其中有很多不同的数据源,比如 noSQL 数据库、键值对或文档、redis、文档 DB、CSV 文件,等等。

在实体框架中,继承DbContext类的类映射 SQL 模式。

DbContext包含与 DB 表映射的 DbSet 对象。

实体框架(EF)方法

要使用 EF,我们需要一个继承自DbContext类和DbSet对象作为DbContext类属性的类。

在 EF 之前,我们有几种与 EF 合作的方式:

  • 空代码首先模型(来自用于生成新数据库的 c#类)
  • 首先从现有数据库中进行代码(生成 EF 实体的类和DbContext类,但目标是继续使用带有代码优先模型特性的 EF)
  • 空的 EF 设计器模型(通过图形界面在一个.edmx模型文件中),我们创建了面向对象的实体,生成一个新的数据库(模式、表、关系、约束)和相关的 c#代码。
  • 来自数据库的 EF 设计器(这从一个现有数据库生成一个.edmx文件模型)

我们可以以三种方式使用 EF 7:

  • 代码首先
  • 数据库的第一
  • 两者都可以(我们可以在现有数据库中开始使用 EF,然后用代码优先的方法扩展该数据库)
  • EF Designer 模型已被删除,因此不再有 EDMX 文件
  • ObjectContext API 已被删除
  • 实体 SQL 已被删除

The unique ability of an ORM is that it can store the object representation of the database in memory. The design pattern used internally in ORMs to do this is the repository pattern that we will talk about later in this chapter in another recipe.

为存储库模式配置 IOC 生命周期

在本教程中,我们将学习在 ASP 中注册、解析和赋予 EF 一个生命周期的简单方法.NET 的核心。

准备

我们将用Statup.csConfigureServices方法注射 EF。 ConfigureServicesIServiceCollection接口参数已经为 ASP 中最常用的库提供了一些助手.NET MVC。

默认情况下,在 Visual Studio 的 ASP 的空项目中不存在此代码.NET Core 模板,但如果我们使用 Web 应用 ASP,它已经存在.NET Core 模板。

之后,我们将向应用添加一个继承自DbContext的类,以执行抛出 ORM 的数据库操作。 最后,在配置应用的生命周期之前,我们将在应用和数据库持久性层之间创建一个存储库层(即使所有代码物理上都在同一个应用中)。

但是,首先让我们在 SQL Server 上创建一个名为CookBook的数据库(从 2008 年到 2014 年)。

然后,我们将创建两个表,BookRecipe:

CREATE TABLE [dbo].[Book](
 [Id] [int] IDENTITY(1,1) NOT NULL,
 [Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_Book] PRIMARY KEY CLUSTERED 
(
 [Id] 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
CREATE TABLE [dbo].[Recipe](
 [Id] [int] IDENTITY(1,1) NOT NULL,
 [Name] [varchar](50) NOT NULL,
 [IdBook] [int] NOT NULL,
 CONSTRAINT [PK_Recipes] PRIMARY KEY CLUSTERED 
(
 [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[Recipe] WITH CHECK ADD CONSTRAINT [FK_Recipes_Book] FOREIGN KEY([IdBook])
REFERENCES [dbo].[Book] ([Id])
GO
ALTER TABLE [dbo].[Recipe] CHECK CONSTRAINT [FK_Recipes_Book]
GO

最后,让我们在Book表和Recipe表中插入一些项:

USE [CookBook]

INSERT INTO [dbo].[Book]([Name]) VALUES ('ASP.NET MVC 6')
INSERT INTO [dbo].[Book]([Name]) VALUES ('EF 7')

INSERT INTO [dbo].[Recipe]([Name],[IdBook]) VALUES('Tasks runners',1)
INSERT INTO [dbo].[Recipe]([Name],[IdBook]) VALUES('IoC',1)
INSERT INTO [dbo].[Recipe]([Name],[IdBook]) VALUES('DbContext',2)
INSERT INTO [dbo].[Recipe]([Name],[IdBook]) VALUES('In-Memory Provider',2)
GO

怎么做……

  1. 首先,我们添加 EF 作为一个注入服务:
public void ConfigureServices(IServiceCollection services) 
{ 
services.AddEntityFramework()
}

智能感知可以帮助我们找到这个方法,它已经作为一个扩展方法助手存在。

  1. 接下来,我们将 EF 与数据库相关联。 EF 不知道我们将使用的数据存储(任何关系数据库,任何非关系数据库,等等):
public void ConfigureServices(IServiceCollection services) 
{ 
services.AddEntityFramework() 
.AddSqlServer() 
}
  1. 现在我们将命名为ApplicationDbContextDbContext与存储在appsettings.json中的参数中的连接相关联。

在此过程中,我们在为应用设置服务时配置了一个数据库提供者:

public void ConfigureServices(IServiceCollection services) 
{ 
services.AddEntityFramework() 
.AddSqlServer() 
.AddDbContext( 
options => options.UseSqlServer(
 Configuration["Data:DefaultConnection:ConnectionString"]
 )); 
}

我们使用以下语法访问appsettings.json文件 JSON 对象的层次结构:

"Data": { 
"DefaultConnection": { 
"ConnectionString": "Server=(localdb)...." 
} 
} 
  1. 接下来,我们创建自己的DbContext,通过覆盖CookBookContext类的OnConfiguring方法来与数据库通信:
public class CookBookContext : DbContext 
{ 
 public CookBookContext() 
 { 
 var config = new ConfigurationBuilder() 
 .AddJsonFile("appsettings.json") 
 .AddEnvironmentVariables(); 
 Configuration = config.Build(); 
 } 
 public IConfiguration Configuration { get; set; } 
 public DbSet Books { get; set; } 
 public DbSet Recipes { get; set; } 
 protected override void OnConfiguring 
 (DbContextOptionsBuilder options) 
 { 
 var connectionString = Configuration["Data:DefaultConnection:ConnectionString"]; 
 options.UseSqlServer(connectionString); 
 }
} 

现在我们将创建一个服务层来隔离控制器代码和数据库代码:

public class RecipeRepository : IRecipeRepository 
{ 
 private readonly CookBookContext _context; 

 public RecipeRepository(CookBookContext context) 
 { 
 _context = context; 
 } 

 public IEnumerable GetAllRecipes() 
 { 
 return _context.Recipes.AsEnumerable(); 
 } 
} 
  1. 最后,我们将配置存储库生命周期,同时配置所有依赖于它的对象图。 在本例中,CookBookContext将具有与RecipeRepository相同的生命周期。 我们将选择 ASP.NET Core 作用域生命周期,对应于每个请求的生命周期; 这意味着该类实例将在 HTTP 请求期间可用,而无需重新实例化它(作为每个请求的单个实例)。

让我们将以下代码行添加到Startup.cs类的ConfigureService方法中:

services.AddScoped<IRecipeRepository, RecipeRepository>(); 

在现有数据库中使用 EF

在本菜谱中,我们将学习如何从现有数据库创建一个DbContext文件。

在 EF 中有创建DbContextDbSet类的方法。

一种是数据库优先的方法。 使用这种方法,EF 工具支持覆盖数据库表及其中的字段,以创建DbContextDbSet类。

另一种是代码优先的方法。 使用这种方法,开发人员可以手工创建DbContextDbSet类,EF 库可以使用这些类来访问数据库表。

当他们发布 EF Core 1.0 时,微软宣布 EF 工具不再支持覆盖和创建现有数据库中的DbContextDbSet类。

准备

使用 Visual Studio 2015,我们将创建一个 ASP.NET MVC 6 项目。

怎么做……

我们将创建一个存储库:

  1. 首先,我们必须确保在project.json中导入了以下依赖项:

  1. 让我们打开 Visual Studio 2015 的开发人员命令提示符。 当它被放入项目文件夹后,我们将键入以下命令:
dnx ef 

命令回显信息如下:

  1. 之后,我们输入以下命令:
dnx ef scaffold "DatabaseConnectionString" EntityFramework.MicrosoftSqlServer --outputDir Models 

命令回显信息如下:

现在我们可以看到所有生成的文件:

  1. 让我们打开CookBookContext来看看生成了什么:

  1. 现在打开Book.csRecipe.cs:

  1. 让我们也打开Revipe.cs

使用 InMemory 提供程序

在本食谱中,我们将学习如何在 EFDbContext中使用InMemory提供程序选项。

准备

我们将创建一个 ASP。 Visual Studio 2015 中的 NET Core 项目。

怎么做……

  1. 首先,让我们看看在 SQL Server 上的Cookbook数据库中Book表所包含的行:

  1. 我们可以在CookbookContextOnConfiguring方法中看到,我们使用 SQL Server 作为提供者:

  1. 让我们创建一个 MVCCookbook控制器和一个Books视图来查看Book表的所有行:

Books视图应该如下所示:

  1. 现在我们将安装必要的包来使用 EF Core 的新的InMemory提供程序。 我们可以用三种方法之一:

Install-Package EntityFramework.InMemory -pre

命令回显信息如下:

我们可以看到安装了EntityFramework.InMemory:

智能感知会询问我们想为EntityFramework.Core使用哪个版本

  1. 现在,让我们测试一下InMemory提供者,更改OnConfiguring方法中的提供者选项:

  1. 让我们通过在图书集合中添加一个条目来改变 MVC 控制器中的代码:

  1. 最后,我们将只显示刚刚添加的项目,因为我们在内存中工作,而不是在数据库中:

  1. 因为InMemory提供者对于测试非常重要,所以我们还将使用xUnittest方法中测试它(我们将在下一个配方中讨论xUnit)。

要做到这一点,让我们安装我们需要的xUnit库:

我们还创建了测试命令,以便在需要时通过命令行运行单元测试。

  1. 接下来,让我们创建test方法,并指定DbContextOptionsBuilder我们正在使用InMemory提供程序:

  1. 我们可以通过右键单击test方法名并选择 Debug test 或者启动之前在project.json文件中创建的测试命令来看到以下结果:

它是如何工作的…

提供程序对于无需模拟数据库的单元测试非常有用。

管理长请求批处理

在这个食谱中,我们将学习新的 EF 7 长请求批处理特性。

准备

我们将使用 Visual Studio 2015 和 EF 7,使用 SQL Server 提供程序。 在前面的配方中,我们安装了xUnit来运行单元测试。 在这个配方中,我们将创建另一个单元测试来使用 EF 运行批次。

我们将在 SQL 分析器中看到生成的 SQL,这是一个用于捕获 SQL 服务器上的数据库流量的工具。 通过这种方式,我们将能够观察 EF 生成的 SQL 代码。

怎么做……

  1. 首先,我们将配置DbContext来指示DbContext允许的最大请求数,以及我们刚刚创建的新单元测试方法中的超时时间(可选):

  1. 接下来,让我们添加一些请求,以在Book表中插入一些项:

现在我们可以在 SQL Profiler 中看到生成的 SQL:

  1. 接下来,让我们添加一些请求来更新Book表中的一些项

我们可以在 SQL Profiler 中看到生成的 SQL:

  1. 让我们看看Book表中的项目:

  1. 接下来,让我们添加一些请求,从Book表中删除一些项目:

我们可以在 SQL Profiler 中看到生成的 SQL:

记住,我们将MaxBatchRequest的值配置为 10,因此 EF 将为超过 10 的请求生成第二个 SQL 批处理:

我们可以在Book表中看到被删除的项目:

有更多的…

有一个名为EntityFramework.Extended的库用于在 EF 的旧版本中创建批处理。 我们也可以在 ASP 中使用它.NET Core with Framework .NET 4.6。

语法如下:

//update all book with id of 1 to id of 2
context.Books.Where(b => b.Id == 1)
 .Update(t => new Book { Id = 2 });
//delete all books where Name matches
context.Books.Where(u => u.Name == "SQL").Delete();

使用带有 EF 的存储过程

在本食谱中,我们将学习如何在 EF 中使用存储过程。

准备

与前面的方法一样,我们将继续使用相同的CookBook数据库,并使用xUnit创建单元测试方法。

怎么做……

我们将创建一个存储库:

  1. 首先,我们在CookBook数据库中创建两个存储过程:
CREATE PROCEDURE [dbo].[GetBooks] 
AS
BEGIN
 SET NOCOUNT ON;
 SELECT [Id],[Name] FROM [dbo].[Book]
END
GO

CREATE PROCEDURE [dbo].[InsertBook]
 @name varchar(50)
AS
BEGIN
 SET NOCOUNT ON;
 INSERT INTO [dbo].[Book] ([Name]) VALUES (@name)
END
GO
  1. 然后,我们添加两个方法,对应于刚刚创建到DbContext的两个存储过程:

  1. 接下来,让我们使用这些方法:

编写 EF 提供者

在这个食谱中,我们将学习如何创建一个新的 EF 提供者。 它可用于创建关系或非关系提供程序。

准备

我们将在project.json或 NuGet 中导入Microsoft.EntityFrameworkCore.Relational库。

怎么做……

  1. 首先,让我们获得一些要重写的示例代码,它允许我们创建一个新的 EF 提供程序。 我们可以在https://github.com/aspnet/EntityFramework.Docs/tree/master/docs/internals/sample中找到模板。
  2. 接下来,我们可以通过在DbContextOptionBuilder上创建一个扩展方法来添加这个新的提供程序,如下所示:
public static class MyProviderDbContextOptionsExtensions
{
 public static DbContextOptionsBuilder UseMyProvider(
 this DbContextOptionsBuilder optionsBuilder, string connectionString)
 {
 ((IDbContextOptionsBuilderInfrastructure)optionsBuilder)
 .AddOrUpdateExtension(
 new MyProviderOptionsExtension
 {
 ConnectionString = connectionString
 });
 return optionsBuilder;
 }
}

UseMyProvider()方法还可以用于返回DbContextOptionsBuilder周围的一个特殊包装器,该包装器允许用户使用连锁调用配置多个选项。

  1. 让我们实现Add方法:
public static class EntityFrameworkServicesBuilderExtensions 
{ 
public static EntityFrameworkServicesBuilder 
 AddMyProvider(this EntityFrameworkServicesBuilder 
 builder) 
{ 
var serviceCollection = builder.GetInfrastructure();
 serviceCollection.TryAddEnumerable(ServiceDescriptor 
.Singleton<IDatabaseProvider, 
DatabaseProvider<MyDatabaseProviderServices, 
 MyProviderOptionsExtension>>()); 
serviceCollection.TryAdd(new ServiceCollection() 
// singleton services 
.AddSingleton() 
.AddSingleton() 
// scoped services 
.AddScoped() 
.AddScoped() 
.AddScoped() 
.AddScoped
 () 
.AddScoped() 
.AddScoped() 
.AddScoped()); 
return builder; 
} 
}

我们可以在我们的OnConfiguring``DbContext方法中使用这个新的提供者及其UseX方法。

另请参阅

https://github.com/aspnet/EntityFramework/wiki/Writing-an-EF-Provider