为ASP.NET Core RazorPage 使用EF Core 连接MySQL数据库

Author Image
admin Monday, June 8, 2020 阅读数: 32

[YoYoMooc]为ASP.NET Core RazorPage 使用EF Core 连接MySQL数据库

对于大多数ASP.NET Core 正式项目来说都会依赖一个数据库,无论是SqlServer、MySQL或sqllite等。而使用数据库则意味着会产生一个数据库文件,而这个文件应该保存在Docker的数据卷中。这样Docker容器被销毁的时候,它们不会被删除。

在接下来的内容中,我们为当前的RazorPage示例程序,添加种子数据连接到数据库,同时也将数据库中的数据保存到Docker中的Volume(卷)中。

我们有很多的数据库可以选择,之前的基础课程中,我们选择的是SqlServer,这次我们采用MySQL作为案例。无论是MySQL和SqlServer都可以良好的在Linux容器中运行。 我们同样采用ORM框架Entity Framework Core (EF Core)来帮助我们连接数据库。

为了确保后面的课程顺利进行,我们先删除之前的所有容器,输入以下命令:

docker rm -f $(docker ps -aq)

关于SqlServer 我们在课程的后面阶段可以考虑实现。

拉取并检查数据库镜像

在往容器化的应用程序添加新组件的时候,我们需要检查镜像文件,看看它是否会使用卷,如果使用卷的话,我们可以得知它是如何配置,以及如何创建容器。如果跳过这个步骤,可能看起来会正常,但是可能会造成你删除容器的时候,你的数据文件也同时被删了。这个会引发一场灾难。

现在我们从Dockerhub中获取镜像,当然你网络速度较慢,可以配置镜像加速。输入以下命令:


docker pull mysql:8.0

docker inspect mysql:8.0

通过检查返回的信息,我们可以找到类似如下信息:

 "Volumes": {
                "/var/lib/mysql": {}
            },

这是告知我们,mysql:8.0的镜像可以将目录/var/lib/mysql作为数据卷,这个卷就是MySQL存储数据文件的地方。

提示:不要试图创建一个包含ASP.NET Core MVC应用程序和数据库的Docker镜像,让它们可以在单个容器中运行。 按照Docker的约定是为应用程序中的每个组件使用一个单独的容器,这使升级或替换应用程序的各个组件变得更加容易。同时在部署应用程序的时候可以采用更灵活的方法来扩展应用程序。 如果我们创建一个包含了所有应用程序组件的完整容器,您将无法从Docker的这种模块化设计中受益。

创建一个数据库容器及Docker卷

现在我们准备创建一个数据库容器,开始之前我们先创建一个Docker卷,输入以下命令:


docker volume create --name productdata

这会在我们的本地创建一个名为productdata的docker卷,待会用于存放数据库的数据文件。

现在我们需要创建一个MySQL容器,将容器中的指定的目录/var/lib/mysql关联到我们创建的卷上。输入以下命令:

docker run -d --name mysql -v productdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=bb123456 -e bind-address=0.0.0.0 mysql:latest


完整的内容解释,查看以下表格:

名称 描述
-d 这个参数会将运行中的docker容器在后台运行。
--name 此参数是将当前的mysql镜像,创建的容器指定一个名称。
-e MYSQL_ROOT_PASSWORD=bb123456 -e 表示的是环境变量。在这里,MySQL容器使用 MYSQL_ROOT_PASSWORD环境变量来设置连接数据库所需的密码,在这里我设置的密码为bb123456,你可以自行更改。
-e bind-address 这个环境变量是保障MySQL能够连接到当前的网络接口上
-v productdata:/var/lib/mysql 该参数告诉Docker 有一个卷名称为productdata提供给容器中的/var/lib/mysql 文件夹

运行以上命令完成后,我们可以使用以下命令进行跟踪容器的状态:

docker logs -f mysql

MySql需要一段时间才能初始化,在此期间它会输出各种日志消息。 其中许多 消息将是可以忽略的警告。当数据库准备就绪后,会停止显示消息,当其中一条消息也是最后一条消息显示为如下类似的内容时,这表明数据库已经准备好了。

2020-05-21T06:15:09.786687Z 0 [Note] mysqld: ready for connections.
Version: '8.0.0-dmr'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)

第一次会比较慢,后续停止容器后再开始就会变得更快,因为它会使用productdata卷中创建的文件。现在我们可以按键键入Control + C停止监视内容的输出,使数据库在后台容器中运行。

为RazorPage项目添加MySQL支持

回到我们的项目中打开项目文件YoYoMooc.ExampleApp.csproj,添加以下代码:

  <ItemGroup>   
   
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
     <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
  </ItemGroup>

添加好我们注入的nuget包配置信息,这里我们使用的是ef core中MySQL的程序提供包。保存YoYoMooc.ExampleApp.csproj文件后,来到项目的根目录中。

输入以下命令:

dotnet restore

会进行包的还原,还原成功,表示nuget包安装成功。

创建一个连接数据库的仓储类

目前我们项目中的数据都是硬编码完成的,目的只是为了启动应用程序。现在我们需要通过EF Core帮助应用程序关联数据库上下文。 所以添加一个名为ProductDbContext.cs的文件到YoYoMooc.ExampleApp目录下的Models文件夹中。

补充代码如下:

namespace YoYoMooc.ExampleApp.Models
{
    public class ProductDbContext : DbContext
    {
        public ProductDbContext(DbContextOptions<ProductDbContext> options)
        : base(options)
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

ProductDbContext类将提供对数据库中的Product表和及属性的访问。

要在应用程序的其他地方提供对数据的访问,请添加一个名为的文件将DataProductRepository.cs添加到YoYoMooc.ExampleApp/Models文件夹,添加如下代码:


  public class DataProductRepository : IProductRepository
    {
        private ProductDbContext context;

        public DataProductRepository(ProductDbContext ctx)
        {
            context = ctx;
        }

        public IQueryable<Product> Products => context.Products;
    }

这是一个简单的仓库库,通过Product访问在数据库中的产品列表信息, 在实际的项目中,我们会创建通过仓储去实现添加和修改等方法,但是这里我们主要是学习Docker,所以忽略它。

现在我们需要添加一些种子数据到数据库中,需要在YoYoMooc.ExampleApp/Models文件夹,添加一个种子数据文件SeedData.cs文件,填入以下代码:


/// <summary>
    /// 种子数据
    /// </summary>
    public static class SeedData
    {
        /// <summary>
        /// 初始化数据库和种子数据
        /// </summary>
        /// <param name="dbcontext"></param>

        public static IApplicationBuilder UseDataInitializer(
             this IApplicationBuilder builder)
        {
            using (var scope = builder.ApplicationServices.CreateScope())
            {

                var dbcontext = scope.ServiceProvider.GetService<ProductDbContext>();
                System.Console.WriteLine("开始执行迁移数据库...");
                dbcontext.Database.Migrate();
                System.Console.WriteLine("数据库迁移完成...");
                if (!dbcontext.Products.Any())
                {
                    System.Console.WriteLine("开始创建种子数据中...");
                    dbcontext.Products.AddRange(
                    new Product("空调", "家用电器", 2750),
                    new Product("电视机", "家用电器", 2448.95m),
                    new Product("洗衣机 ", "家用电器", 1449.50m),
                    new Product("油烟机 ", "家用电器", 3454.95m),
                    new Product("冰箱", "家用电器", 9500),
                    new Product("猪肉 ", "食品", 36),
                    new Product("牛肉 ", "食品", 49.95m),
                    new Product("鸡肉 ", "食品", 22),
                    new Product("鸭肉", "食品", 18)
                    );
                    dbcontext.SaveChanges();
                }
                else
                {
                    System.Console.WriteLine("无需创建种子数据...");
                }


            }
            return builder;

        }

    }

配置服务

现在我们需要将EF Core以及自定义的种子数据,配置到应用程序的服务中。打开startup.cs文件,添加以下代码:


        public void ConfigureServices(IServiceCollection services)
        {
            // services.AddTransient<IProductRepository, MockProductRepository>();

            services.AddTransient<IProductRepository, DataProductRepository>();

            services.AddRazorPages();


            var host = Configuration["DBHOST"] ?? "localhost";
            var port = Configuration["DBPORT"] ?? "3306";
            var password = Configuration["DBPASSWORD"] ?? "bb123456";


            var connectionStr = $"server={host};userid=root;pwd={password};"
            + $"port={port};database=products";


            services.AddDbContextPool<ProductDbContext>(options =>
            options.UseMySql(connectionStr));

        }


然后在startup.cs文件中Configure方法中,配置下种子数据。添加以下代码;


            app.UseDataInitializer();


在传统的开发方式下,MVC项目,数据库的连接字符串是配置appsettings.json中,在里面定义好具体的信息,但是在容器化开发中,我们大多数是通过配置环境变量的形式,因为它可以更加灵活的启动一个指定的容器,这样你就无须去每次去重建镜像或者重新创建容器了。

在本例中,我们配置了DBHOST、DBPORT和DBPASSWORD的环境变量,会通过它们来设置主机名称、TCP端口和数据库密码。如果没有定义这些环境变量的值,会使用默认值。

创建数据迁移

接下来,我们需要创建Entity Framework Core的迁移记录,我们使用的是EF Core中的代码优先的特性,它会自动将我们的领域模型即实体,在数据库中创建表和结构。

输入以下命令:

dotnet ef migrations add Initial

如果你正在使用Visual Studio,你可以打开包管理器控制台,使用Visual Studio包管理器控制台创建数据库迁移,输入以下命令:

Add-migration Initial

无论您使用哪个命令,Entity Framework Core都将创建一个迁移记录,里面包含将用于创建数据库模式的c#类的文件夹。

执行完成后,没有任何报错,如果你想手动生成数据库,输入以下命令 dotnet ef database update如果是vs的话则是update-database

注意:目前我们没有映射端口到的宿主主机,所以无法访问。如果要本地访问请执行以下命令

 

docker run -d  -p 3253:3306  --name mysql -v productdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=bb123456  -e bind-address=0.0.0.0 mysql:8.0


修改视图内容

打开项目YoYoMooc.ExampleApp中的视图Index.cshtml.cs文件,添加几个属性值


        /// <summary>
        /// 服务器的名称
        /// </summary>
        public string Hostname { get; set; }
        public string DBHOST { get; set; }
        public string DBPORT { get; set; }
        public string DBPASSWORD { get; set; }

然后修改 OnGet方法代码如下:


   public void OnGet()
        {
            Message = _config["MESSAGE"] ?? "深入浅出 ASP.NET Core 与Docker";

            Products = _repository.Products.ToList();


            Hostname = _config["HOSTNAME"];
            DBHOST = _config["DBHOST"] ?? "localhost";
            DBPORT = _config["DBPORT"] ?? "3306";
            DBPASSWORD = _config["DBPASSWORD"] ?? "bb123456";

        }

然后打开视图文件Index.cshtml,修改视图内容如下:

	<h3 class="display-4">欢迎</h3>
		<p>创建年轻人的 <a href="https://www.yoyomooc.com">第一个 ASP.NET Core项目</a>.</p>

			<h4 class="bg-success text-xs-center p-1 text-white"> @Model.Message</h4>
 
		<h4 class="bg-success text-xs-center p-1 text-white">服务器Id: @Model.Hostname</h4>
		<h1 class="bg-primary text-xs-center p-1 text-white">@Model.DBHOST</h1>
		
        <table class="table table-sm table-striped">
			<thead>
				<tr><th>ID</th><th>名称</th><th>分类</th><th>价格</th></tr>
			</thead>
			<tbody>
				@foreach (var p in Model.Products)
				{
					<tr>
						<td>@p.ProductID</td>
						<td>@p.Name</td>
						<td>@p.Category</td>
						<td>¥@p.Price.ToString("F2")</td>
					</tr>
				}
			</tbody>
		</table>
        </div>


创建一个MVC的应用镜像


 dotnet publish --framework netcoreapp3.1 --configuration Release --output dist

docker build . -t yoyomooc/exampleapp -f Dockerfile

测试应用程序

剩下的就是通过为ASP.NET Core MVC应用程序创建一个容器来测试新镜像,并且确保它可以与已经在自己的容器中运行的MySQL数据库通信, 首先我们要保证我们的容器是能够正常工作的,同时在我们之前提到过启动容器时,Docker将其连接到内部虚拟网络并为其分配一个Internet协议(IP)地址,以便它可以与主机服务器以及同一容器上的其他容器进行通信网络。

关于这个功能的详情我们会在后面的章节中细说。

要使MVC容器与数据库对话,我需要知道Docker分配给的IP地址MySQL容器。输入以下命令,检查Docker的配置 虚拟网络。

docker network inspect bridge

此命令的响应将显示Docker如何配置虚拟网络和将包括一个容器部分,其中显示连接到网络的容器和分配给它们的IP地址。

应该只有一个容器,其名称字段为mysql。 请注意IPv4Address字段,如下所示:

"Containers": {
            "5aea9ac5557d172117ed56baba66fd070acc9421198344dfda0becae00f5ebb0": {
                "Name": "mysql",
                "EndpointID": "a01a73de5b19359ed7a0043667305fbfe3277678bef588807ed0b5fb51608503",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
}

这是Docker分配给容器的IP地址。对我来说,地址是172.17.0.2,但是您可能会看到其他地址。这是MVC应用程序必须在其数据库中使用的IP地址与MySQL通信的连接。

这是Docker分配给容器的IP地址。对我来说,地址是172.17.0.2,但您可能会看到其他地址。这是MVC应用程序在其数据库连接中与MySQL通信所必须使用的IP地址。可以通过DBHOST环境变量将此地址提供给应用程序,这在我们之前的MVC应用程序的Startup类的添加读取。

现在执行以下命令,在后台创建和启动MVC容器,然后再监视它的输出内容:

docker run -d --name productapp -p 3000:80 -e DBHOST=172.17.0.2 -e DBPASSWORD=bb123456 yoyomooc/exampleapp
docker logs -f productapp

当MVC应用程序启动时,您将看到显示Entity Framework Core 迁移应用到数据库,这确保创建了数据库架构,并且确保了添加种子数据,然后会输出以下内容:


warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {4d902682-d44c-47d0-aad9-18f1547803d0} may be persisted to storage in unencrypted form.
开始执行迁移数据库...
数据库迁移完成...
开始创建种子数据中...
启动 ASP.NET Core 深入浅出Docker...
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
      Failed to determine the https port for redirect.

docker run命令将主机操作系统上的端口3000映射到主机操作系统中的端口80容器,这是Kestrel用于接收ASP.NET Core运行时的HTTP请求的端口。 要测试该应用程序,请打开一个新的浏览器选项卡,并请求URL http:// localhost:3000。浏览器将发送Docker将接收的HTTP请求并将其定向到MVC容器中的端口80,产生响应

d

MVC应用程序响应中显示的数据是从MySQL服务器获取的使用Entity Framework Core。 Docker网络允许EF Core打开与MySQL容器并使用它来发送SQL查询。 这两个组件都存在于各自的容器中,并且网络请求会像往常一样,不知道另一个容器在同一服务器上并且连接它们的网络是虚拟的,并已由Docker创建和管理。

测试完MVC应用程序后,请键入Control + C以停止监视输出

并使容器在后台运行。 您可以检查容器是否仍在运行

docker ps

下节课 我们就来解释 网络

练习用的命令


 docker run -d --name productapp -p 3000:80 -e DBHOST=172.17.0.2 -e DBPASSWORD=bb123456 yoyomooc/exampleapp

docker run -d --name mysql -v productdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=bb123456 -e bind-address=0.0.0.0 mysql:latest


docker run -d -p 3253:3306  --name mysql -v productdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=bb123456  mysql:8.0.0 



dotnet publish --framework netcoreapp3.1 --configuration Release --output dist

docker build . -t yoyomooc/exampleapp -f Dockerfile

docker run -d --name productapp -p 3000:80 -e DBHOST=172.17.0.2 -e DBPASSWORD=bb123456 yoyomooc/exampleapp