2019 年 8 月

第 34 卷,第 8 期

[数据点]

使用 .NET Core 3.0 使用跨平台 EF6!

作者 Julie Lerman

Julie Lerman虽然 Entity Framework Core 已经推出了几年,但仍有大量生产应用还在使用 EF6。并且,即使 EF Core 中引入了所有创新功能,如果你不打算更改 EF6 逻辑,并且无需利用 EF Core 新功能或改进的性能,就没有理由将当前运行的稳定生产代码移植到 EF Core。鉴于其开源特性,EF6 仍受到维护,甚至由 Microsoft 和社区进行调整。然而,许多不想让自己的 EF6 功能受到干扰的开发人员和团队希望将他们的软件从 .NET 移植到 .NET Core,以便利用 .NET Core 的许多优势,包括它的跨平台功能和许多创新功能。

可以将 EF6 逻辑封装到 ASP.NET Web API 中,并从 .NET Core 应用程序访问它。但是,在 ASP.NET Core API 的功能或 Windows 桌面应用的创新功能迁移到 .NET Core 3.0 后,使用该解决方案将无法从中受益。幸运的是,随着 EF 6.3 和 .NET Core 3.0 即将发布,你将能够“拥有你的蛋糕并吃掉它。” EF6.3 不仅可以继续在 .NET Framework 上运行,它还可以使用 .NET Core 3.0。这是完全可行的,因为除了在 .NET 4.0 和 .NET 4.5 上运行之外,EF6.3 还会被交叉编译为面向 .NET Standard 2.1。

本文中使用 EF6.3 和 ASP.NET Core 3.0 的最新预览版本,我将全面介绍如何在跨平台场景中尝试使用 EF6.3,即,使用 EF6.3 创建新的 ASP.NET Core 3.0 API。在我的 MacBook 上!在 macOS 中!使用 Visual Studio Code!然后将其部署到基于 Linux 的 Docker 容器中!到目前为止,EF6 还无法完成其中任何一项任务。从版本 2017 v15.3 开始,仍然可以在 Visual Studio 中的 .NET Framework 应用中使用 EF6.3,但对于 .NET Core 3.0 应用,需要可支持 .NET Core 3.0 的 Visual Studio 2019 的预览版本。

EF6.3 预览版确实对 .NET Core 3.0 有一些限制,预计在发布时会将具体限制整理出来。目前的限制包括:

  • .NET Core 3.0 尚不可在 Visual Studio 中与 EF 设计器一起使用。
  • 迁移命令尚不适用于 .NET Core 3.0 项目。
  • 编写时唯一工作的提供程序是 SQL Server。

你可能会发现与公告博客文章相关的讨论很有用 (bit.ly/2F9xtDt),但请注意,随着版本发布时间越来越近,许多这些问题应该都已得到解决。

即使存在这些限制,我真正感兴趣的是概念证明:在非 Windows 计算机上见证 EF6.3。因此,我将使用最简单的模型构建一个非常简单的项目,这足以让它在我的 MacBook 上运行了。因为我正在 macOS 中工作,并且目前唯一可用的数据库提供程序是 SQL Server,所以我有一个很好的借口在 Docker 容器中使用 SQL Server for Linux 来保存数据。

准备基础 API 项目

安装 .NET Core 3.0 并创建 ASP.NET Core API 的过程与平常没有什么不同。我在这里简要介绍一下,主要针对那些尚未完成这些步骤的用户。

首先确保你的计算机上安装了 .NET Core 3.0。正如我在 2019 年 6 月初所写的那样,最新版本是 2019 年 6 月 12 日发布的预览版 6。.NET Core 3.0 的安装页面是 bit.ly/2KoSOxh。你希望安装 SDK 包,其中不仅包含 SDK,还包含 .NET Core 和 ASP.NET Core 运行时。安装完成后,运行命令 dotnet  --version 将列出最新版本,而运行 dotnet --list-sdks 将显示系统中的所有版本。

接下来,为你的项目创建一个新文件夹(我称之为“coreapi”),并确保在命令行中位于该文件夹内。然后键入 CLI 命令:dotnet new webapi。此命令将使用 C# 作为默认语言,在文件夹中创建一个新的 ASP.NET Core API 项目,默认情况下,将在计算机上安装最新版本的 .NET Core。

当我在 MacBook 上工作时,通过在我的开发环境中使用 Visual Studio Code,我可以立即体验跨平台支持。我可以通过一种快捷方式,在命令行的项目文件夹处键入代码,启动 VS Code 并打开该文件夹。也可以启动 VS Code 并打开项目文件夹。

项目的基本资产由模板创建,包括默认的 ValuesController。正如图 1 中所示,项目文件的目标是 .NET Core 3.0。不再需要使用此版本的 .NET Core 显式引用 csproj 中的任何 ASP.NET Core 包,这就是 ItemGroup 为空的原因。

coreapi 项目及其 csproj 文件的内容
图 1 coreapi 项目及其 csproj 文件的内容

将 EF6.3 添加到项目中

可以通过在 ItemGroup 部分中包含以下包引用,将 EF6.3 直接添加到 csproj 文件中:

<PackageReference Include="EntityFramework" Version="6.3.0-preview6-19304-03" />

请注意,这里引用的是我在演示本文内容时可用的最新预览版本。查看 NuGet 页 (bit.ly/2RgTo0l) 可以确定 Entity Framework 的当前版本。当你阅读本文时,EF6.3 可能已经完全发布!

我需要一些数据用于存储,所以我创建了一个名为“Human”的小类:

public class Human {
  public int HumanId { get; set; }
  public string Name { get; set; }
}

请注意,命名空间和 using 语句包含在下载示例中,但不包含在此处的代码清单中。

为了保存数据,我创建了一个名为“HumanContext”的 DbContext 类:

public class HumanContext : DbContext {
  public HumanContext (string connectionString) : base (connectionString)
  {
    Database.SetInitializer<HumanContext> (new HumanInitializer ());
  }
  public DbSet<Human> Humans { get; set; }
  protected override void OnModelCreating (DbModelBuilder modelBuilder
  {
    modelBuilder.Entity<Human> ().ToTable ("Humans");
  }
}

构造函数需要传入连接字符串。

为了防止在将单词 Human 变为复数形式 Humen (!) 时 EF 完全失败,我使用了一个映射,强制将表名改为 Humans。另外,在构造函数中,我将数据库初始化表达式设置为自定义初始化表达式 HumanInitializer,如果模型发生更改,它将使用一些浪漫的 human 来为测试数据库设定种子。如果你忘记了 EF6 的教训,我会提醒你,如果数据库尚不存在,此初始化表达式也会创建数据库。以下是 HumanInitializer 类:

public class HumanInitializer : DropCreateDatabaseIfModelChanges<HumanContext>
{
  protected override void Seed (HumanContext context)
  {
    context.Humans.AddRange (new Human[] {
      new Human { Name = "Juliette" },
      new Human { Name = "Romeo" }
    });
  }
}

将 ASP.NET Core 和 EF6 进行绑定

在 ASP.NET Core 中使用 EF6.3 时,会错过 EF Core 的一些优点。其中一个就是集成的依赖关系注入。EF Core 可以使用其 AddDbContext 扩展方法将自身绑定到 ASP.NET Core 服务中。这样,ASP.NET Core 就可以根据需要将 DbContext 的对象实例注入需要它们的类中,例如控制器类。但是,如果没有 EF Core,AddDbContext 方法就不可用。此外,EF Core 数据库提供程序(如 Microsoft.EntityFrameworkCore.SqlServer)具有扩展方法,可以使用它向 AddDbContext 方法添加有关提供程序的详细信息。例如,SqlServer 提供了一个 UseSqlServer 方法,可以向此方法提供连接字符串等选项。

虽然在使用 EF6.3 时这些方法不可用,但仍可以将 HumanContext 与 ASP.NET Core 的依赖关系注入服务绑定,让它了解提供程序和连接字符串。只需使用不同的代码,它与 EF Core 代码都位于 API 的启动类的 ConfigureServices 方法内部。

可以使用 IServiceCollection Add 方法(以及 AddScoped 等变体)向服务添加任何类。然后,可以在运行时传入操作指令:需要对象。

然后,此代码说明要注意需要 HumanContext 对象的位置,作为响应,实例化一个新的 HumanContext,传入 appsettings.json 中的连接字符串:

services.AddScoped<HumanContext>
                (serviceProvider => new HumanContext (Configuration["Connection"]));

此操作将处理 HumanContext 的依赖关系注入。

如何弥补丢失的 UseSqlServer 方法以指定数据库提供程序和连接字符串?从 EF6.3 的预览版 6 开始,如果未指定任何提供程序,EF 将使用 System.Data.SqlClient。由于我将使用 SQL Server,因此不需要再添加任何代码。在这种情况下,SQL Server 将成为默认提供程序。

绑定操作的最后一步是将连接字符串放到 appsettings.json 中,以便 Configuration[“Connection”] 方法可以读取它:

"Connection":"Server=localhost,1601;Database=Humans;User Id=sa;Password=P@ssword1"

回想一下,之前我说过,我是在 MacBook 上操作,因此,我将在 Docker 容器中使用 SQL Server for Linux 作为数据库。当我运行容器时,我指定 1601 作为电脑上的端口,从该端口公开数据库服务器。下面是我用来运行容器的 docker 命令:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=P@ssword1'
  -p 1601:1433 -d --name SQLforEF6  mcr.microsoft.com/mssql/server:2017-latest

请注意,此命令需要位于一行中。如果要详细了解如何在容器中使用 SQL Server,请参阅我在 2017 年 7 月发表的文章“On-the-Fly SQL Servers with Docker”(使用 Docker 的即时 SQL 服务器),网址为:msdn.com/magazine/mt784660。与这篇文章的一个不同之处在于,我现在指向 Microsoft 容器注册表中 Microsoft Docker 容器的新主页,即 docker run 命令所指向的映像的 URL 中的“mcr”。

添加使用 EF6 的控制器

由模板创建的控制器是测试 API 是否正常工作的好方法,但它只执行构建并返回字符串。要测试数据库交互,请添加一个新的 HumanController 类,如图 2 所示。对于这个概念证明,你真正需要的只是一个用于获取一些数据的 HttpGet 方法。HumanInitializer 中的 Seed 方法将负责插入数据。

图 2 HumanController 类

[Route ("api/[controller]")]
[ApiController]
public class HumanController : ControllerBase
{
  HumanContext _context;
  public HumanController (HumanContext context)   {
    _context = context;
  }
  [HttpGet]
  public ActionResult<IEnumerable<Human>> Get ()
  {
   return _context.Humans.ToList ();
  }
}

请注意,我在 HumanContext 构造函数中定义的初始化将被触发时,EF 才会首次在应用程序实例中尝试与数据库进行某些交互。因此,在我尝试运行此方法之前,不会创建数据库和设定数据库种子。这是一个简单的演示,因此,我不担心可能会在多个服务器上安装此应用程序,以及如果它们试图同时运行初始化和代码种子设定,可能会导致的冲突。但是,在允许应用程序负责数据库创建和种子设定时,要始终注意这一副作用。

运行 API

最后,在一切就绪后,可以运行或调试 API。我通常首先在命令行中使用 dotnet run 来运行它。运行 API 后,我想首先浏览 localhost:5000/api/values 中的值控制器,以验证 API 本身是否正常工作。浏览器中应输出“value1”和“value2”。然后我在 localhost:5000/api/human 上点击依赖于数据库的 API。如果一切顺利,API 将在第一次运行时花一点时间来创建 Humans 数据库并创建 Humans 表以及设定种子。完成后,它应该输出两个 Humans 的 Ids 和名称,如图 3 所示。

coreapi 的 Get 方法的结果
图 3 coreapi 的 Get 方法的结果

签出容器化的数据库

这足以证明数据库已创建并已设定种子。但是,如果我仔细检查数据库,特别是当它位于容器化的服务器中时,我总是感觉更好。

调用 docker ps 命令以列出正在运行的容器可以证明容器确实正在运行:

➜  ~ docker ps

CONTAINER ID  IMAGE                                        COMMAND                 
4bfc01930095  mcr.microsoft.com/mssql/server:2017-latest   "/opt/mssql/bin/sqls…"
CREATED             STATUS              PORTS                    NAMES
23 hours ago        Up 13 hours         0.0.0.0:1601->1433/tcp   SQLforEF6

另外,也可以使用 VS Code Docker 扩展的 Docker Explorer 查看我的 SQLforEF6 容器是否正在运行。

通过使用 Azure Data Studio,我可以连接到该容器的 SQL Server 实例来浏览数据库及其数据(请参阅图 4)。有关此工具的详细信息,请参阅我之前关于 Azure Data Studio 的文章,网址为 msdn.com/magazine/mt832858

在 Azure Data Studio 中探索新数据库
图 4 在 Azure Data Studio 中探索新数据库

将依赖 EF6.3 的 API 部署到 Docker for Linux

虽然可以将依赖于 Windows 的应用放入 Windows 容器中,但 Linux 容器更易于使用。由于 EF6.3 现在可以跨平台运行,我将创建一个基于 Linux 的 API 映像。

除了创建映像之外,我还会创建一个 docker-compose 文件来运行基于此映像的容器,并使其能够与容器中的 SQL Server 通信。我在本杂志 2019 年 4 月刊、5 月刊和 6 月刊的三部分系列文章中探讨了详细工作原理,因此在这里我只传达一些关键点。你也可以在示例下载中查看所有代码。

同样,VS Code Docker 扩展可以帮助完成大部分工作。按 F1,然后键入 Docker,将找到扩展程序提供的许多命令。选择“Docker:将 Docker 文件添加到工作区。” 然后在出现提示时,选择 ASP.NET Core 作为应用程序平台。选择 Linux 作为操作系统,保留默认端口 80,按照其余提示进行操作。

不过,模板生成的 Dockerfile 需要更新,因为我的目标是 .NET Core 3.0 的预览版 6。我必须更改 FROM 映像目标,直接从 Microsoft 容器注册表 (MCR) 中提取预览版本 6。更改 Dockerfile 的前四行,如下所示:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.0.0-preview6 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.0.100-preview6 AS build

接下来,我添加了一个 docker-compose 文件来安排构建这个新映像,在容器中运行它,并运行 API 可以与之交互的另一个 SQL Server 容器。如果你还没有阅读我之前的文章,请务必了解一下,使 API 容器知道如何查找数据库容器。

图 5 显示了我添加到项目中的 docker-compose.yml 文件。

图 5 要运行 API 和数据库容器的 docker-compose.yml 文件

version: '3.4'
services:
  coreapi:
    image: ${DOCKER_REGISTRY-}api
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 80:80
  db:
    image: mcr.microsoft.com/mssql/server
    environment:
      SA_PASSWORD: "P@ssword1"
      ACCEPT_EULA: "Y"
    ports:
      - "1601:1433"

最后一个更改是 API 的 appsettings.json 文件中的连接字符串。目前它指向 localhost,1601。但是新容器中的 ASP.NET API 在自己的 localhost 上没有服务器。不过,如果更改服务器名称以匹配 docker-compose 文件中的服务名称 (db),Docker 将确保 API 容器可以找到数据库容器。

以下是 appsettings.json 中的新连接列表:

"Connection":"Server=db;Database=Humans;User Id=sa;Password=P@ssword1"

就是这样。现在,可以使用 docker-compose 运行容器了。只需右键单击文档资源管理器中的 docker-compose.yml 文件,得益于 Docker 扩展,其中一个选项是“Compose Up”。选择此选项并等待它完成。可能还需要等待 30 秒左右的时间,SQL Server 才能完成系统数据库设置。然后,可以在 localhost:80/api/human 中浏览到 API。再等待一些时间,让它创建 Humans 数据库并设定数据库种子。SQL Server 在执行此操作时有点慢,但完成后,可以浏览到 API 并查看与图 4**** 中相同的输出!然后,可以再次右键单击该 yml 文件,然后选择“Compose Down”以删除新容器。

无需 Windows 即可使用 EF6.3!

虽然我没有实际采用 EF6.3 执行操作,但对于能够在非 Windows 环境中完成整个概念验证,我感到非常惊讶,并且印象深刻。我不会使用 EF6.3 启动一个新项目。所有新工作都应该使用 EF Core 来完成,它具有很多优势,尤其是它的跨平台覆盖范围方面的优势。但是,如果你有使用 EF6 完成的生产代码,并希望相关应用从 .NET Core 功能(无论是跨平台功能还是其他强大功能)中受益,现在都可以从 EF6 这块大蛋糕中分一杯羹。


Julie Lerman 住在佛蒙特州的丘陵地区,担任 Microsoft 区域主管、Microsoft MVP、Docker Captain 和软件团队导师。可以在全球的用户组和会议中看到她对数据访问和其他主题的介绍。她的博客地址是 thedatafarm.com/blog。她是“Entity Framework 编程”及其 Code First 和 DbContext 版本(全都出版自 O’Reilly Media)的作者。请通过 Twitter 关注她 (@julielerman),并观看她在 bit.ly/PS-Julie 上的 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Diego Vega (Diego.Vega@microsoft.com)、Brice Lambson <(bricelam@microsoft.com)>


在 MSDN 杂志论坛讨论这篇文章