Asp.Net Core支持跨平台开发服务端软件,可以采用docker部署到Linux。以前开发基于Windows Server的服务端软件,配套的数据库通常采用SQL Server Express。虽然微软也发布了SQL Server的Linux版本,但是考虑到成熟度,还是优先采用在Linux平台上已经广泛使用了很多年的MySQL数据库,并且全部采用docker部署。
把这个大目标拆分为以下几个小目标:
- Asp.Net Core采用MySQL数据库;
- 在docker中运行MySQL数据库;
- Asp.Net Core网站访问MySQL docker数据库;
1.Asp.Net Core采用MySQL数据库
首先在Windows系统安装MySQL,百度上就可以找到安装包。MySQL可以全部采用默认安装,数据库密码设置为xxx。Asp.Net Core在执行添加控制器、Update Database等操作的时候,都要连接到MySQL,如果连不上会报错。
Asp.Net Core已经支持MySQL数据库,并且数据库驱动不止一种,先采用官方的驱动。NuGet安装MySql.Data.EntityFrameworkCore 6.10.5。
然后修改ODataWeb网站代码。
注意,初始化数据库的时候,一定要调用EnsureCreated(),可以在首次运行的时候自动创建数据库,否则要采用非常麻烦的方法去导入数据库。
注意:数据表全部采用小写名字,避免后续因为大小写问题造成BUG。
public class BookDbContext : DbContext
{
public BookDbContext(DbContextOptions<BookDbContext> options)
: base(options)
{
}
//MySql默认定义的数据表全部小写!并且大小写敏感!
public DbSet<Book> books { get; set; }
}
public static class SeedData
{
public static void SeedDB(BookDbContext context)
{
//重要:如果数据库完全为空,可以自动创建数据库!
context.Database.EnsureCreated();
// DB has been seeded
if (context.books.Any())
return;
context.books.AddRange(
new Book() { Name = "射雕英雄传", Publish = new DateTime(1964, 1, 1), Author = "金庸", Price = 9.5f, },
new Book() { Name = "神雕侠侣", Publish = new DateTime(1965, 3, 1), Author = "金庸", Price = 10.5f, },
new Book() { Name = "倚天屠龙记", Publish = new DateTime(1968, 12, 1), Author = "金庸", Price = 12, }
);
context.SaveChanges();
}
}
在Startup.cs中注册MySQL数据库服务。
public void ConfigureServices(IServiceCollection services)
{
//开发阶段使用内存数据库调试代码
//services.AddDbContext<BookDbContext>(opt => opt.UseInMemoryDatabase("bookdb"));
//注册MySQL数据库服务
services.AddDbContext<BookDbContext>(options =>
options.UseMySQL(Configuration.GetConnectionString("MySQLConnection")));
services.AddMvc();
//支持OData
services.AddOData();
}
然后在appsettings.json定义数据库连接字符串。注意SslMode=None。
"ConnectionStrings": {
"MySQLConnection": "Data Source=localhost;port=3306;Initial Catalog=bookdb;user id=root;password=xxx;SslMode=None"
},
然后调试运行网站,浏览http://localhost:5000/odata/Books,检查结果。
2. 在docker中运行MySQL数据库
如果打算自己做一个MySQL镜像,那是相当之麻烦的。不过docker官网上已经有MySQL镜像,直接拉下来用就可以了。这就是docker的好处,如果我们想用什么软件,就去拉什么镜像,根本不用关心这个软件怎么安装的,大大节省了我们宝贵的时间。
编写docker-compose.yml,把ODataWeb、MySQL、nginx联合起来。
注意:
1,设置MySQL数据库root的密码是xxx。
2,挂载宿主机当前目录下面的data目录到MySQL容器的/var/lib/mysql目录。这样MySQL启动后创建的全部数据库文件都可以在宿主机dbdata目录保留下来。否则,当容器被删除的时候,数据库也全没了,这是绝对不允许的。
3,对外暴露端口3306。当容器运行后,我们可以在外网直接通过3306访问到容器内部的MySQL,这样做便于管理,但是也存在安全风险。
4,设置ODataWeb容器depends_on依赖于MySQL容器。实际测试发现有点问题,后面再说。
version: '3'
services:
mysql-service:
container_name: mysqlservice
image: mysql
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=xxx
volumes:
- ./data:/var/lib/mysql
restart: always
odataweb-service:
container_name: odatawebservice
image: odatawebimage
build:
context: ./odataweb
dockerfile: Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
restart: always
depends_on:
- mysql-service
links:
- mysql-service
nginx-proxy:
container_name: nginxproxy
image: nginx
ports:
- "9000:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
restart: always
links:
- odataweb-service
3. Asp.Net Core网站访问MySQL docker数据库
ODataWeb网站的数据库连接字符串也要改一下,因为ODataWeb在容器里运行的时候,MySQL运行在另外一个容器中,localhost是无法访问MySQL的。如果指定MySQL容器IP,则以上的配置文件需要按照静态IP的方式去配置,比较麻烦。经过试验,发现可以直接写容器的名称mysqlservice,就跟nginx配置文件类似。
"MySQLConnection": "Data Source=mysqlservice;port=3306;Initial Catalog=bookdb;user id=root;password=xxx;SslMode=None"
删除宿主机之前创建的odataweb镜像,然后编译,发布网站,更新宿主机的docker-compose.yml、odataweb目录,执行docker-compose up重新创建镜像,运行容器。
然后可以看到提示了一大堆错误信息,但是最后网站运行起来了。
……
odatawebservice | Unhandled Exception: System.AggregateException: One or more errors occurred. (Unable to connect to any of the specified MySQL hosts.) —> MySql.Data.MySqlClient.MySqlException: Unable to connect to any of the specified MySQL hosts. —> System.AggregateException: One or more errors occurred. (Connection refused 172.18.0.2:3306) —> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException: Connection refused 172.18.0.2:3306
……
mysqlservice | 2018-01-07T08:44:26.502907Z 0 [Note] mysqld: Shutdown complete
mysqlservice |
mysqlservice |
mysqlservice | MySQL init process done. Ready for start up.
mysqlservice |
……
mysqlservice | 2018-01-07T08:44:27.011975Z 0 [Note] End of list of non-natively partitioned tables
odatawebservice | Hosting environment: Development
odatawebservice | Content root path: /app
odatawebservice | Now listening on: http://[::]:5000
odatawebservice | Application started. Press Ctrl+C to shut down.
问题的原因在于MySQL首次启动比较慢,需要初始化一大堆东西,虽然设置了ODataWeb容器依赖于MSQL容器,但是MySQL启动之后,ODataWeb就可以顺理成章地启动了,其实这时候MySQL还在执行初始化工作。因为只有首次运行会报错,所以无所谓,不再深究。
访问http://192.168.80.134:9000/odata/Books,检查结果。
可以看一下宿主机挂载目录,数据库文件都在这里了。
4. 其他初始化MySQL数据库的方法
以上方法是经过了多次试验最终确定的最简便的方法,单纯通过网站的代码去创建数据库,完全没有手动操作。
如果不通过网站的代码去初始化数据库,可以采用MySQL导入数据库的方法。
方法一:远程导入
首先,在本地的MySQL数据库中导出数据库结构和数据。前提条件是,在本地Windows中运行过ODataWeb网站,已经创建了bookdb数据库,这个只要在VS2017中按一下F5就有了。
在控制台进入MySQL的bin目录:在Windows文件资源管理器打开C:\Program Files\MySQL\MySQL Server 5.7\bin,在地址栏输入cmd,进入控制台。输入以下命令登录到MySQL系统。
mysql -h127.0.0.1 -uroot -pxxx -t -P3306
可以确认一下boodb是否已经存在。
show databases;
use bookdb;
show tables;
select * from books;
最后一定要exit退出MySQL系统,才能执行下面的命令导出数据库。
mysqldump -h localhost -u root -p bookdb > d:\temp\bookdb.sql
然后输入密码xxx,就可以导出数据库文件bookdb.sql。
然后改一下ODataWeb代码,注解EnsureCreated(),取消自动初始化数据库功能,重新发布网站。修改docker-compose.yml,让MySQL初始化的时候自动创建一个bookdb数据库,如果没有的话,后面无法远程导入数据库。
environment:
- MYSQL_DATABASE=bookdb
- MYSQL_ROOT_PASSWORD=xxx
用rm-rf data删除Linux宿主机的数据库文件挂载目录。重新创建镜像、运行容器,会发现反复提示大量错误,因为ODataWeb网站无法连接到数据库。
在控制台进入MySQL的bin目录,执行以下命令,把bookdb.sql远程导入到Linux宿主机的MySQL系统。这里的192.168.80.134是VMWare虚拟机的IP。
mysql -h 192.168. 80.134 -P 3306 -u root -p bookdb < d:\temp\bookdb.sql
然后输入密码xxx,然后ODataWeb网站终于成功运行起来了。
访问http://192.168.80.134:9000/odata/Books,检查结果。
方法二:容器内部导入
但是远程操作容器内部的MySQL,不是很安全。所以,一般运行容器的时候,是不对外网开放3306端口的,我们把docker-compose.yml中mysql容器的配置再改一下:
mysql-service:
container_name: mysqlservice
image: mysql
environment:
- MYSQL_DATABASE=bookdb
- MYSQL_ROOT_PASSWORD=xxx
volumes:
- ./dbdata:/var/lib/mysql
restart: always
用rm-rf data删除Linux宿主机的数据库文件挂载目录。重新创建镜像、运行容器,再次执行远程导入,报错:
ERROR 2003 (HY000): Can’t connect to MySQL server on ‘192.168.80.134’ (10060)
我们必须换一种方法,把bookdb.sql拖到Linux宿主机的数据库文件挂载目录data下面,然后进入MySQL容器内部,导入数据库。
在MySQL容器运行起来以后,新开一个SSH命令行窗口,输入以下命令进入容器:
docker exec -it mysqlservice /bin/bash
然后登录到容器内部的MySQL系统:
mysql -h127.0.0.1 -uroot -pxxx -t -P3306
然后导入数据库。注意bookdb.sql在容器内部的路径,跟docker-compose.yml中定义的挂载目录对应:
use bookdb;
source /var/lib/mysql/bookdb.sql;
看一下控制台的信息:
[root@localhost ~]# docker exec -it mysqlservice /bin/bash
root@c1f6b2849657:/# mysql -h127.0.0.1 -uroot -pxxx -t -P3306
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 5.7.20 MySQL Community Server (GPL)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.
mysql> use bookdb;
Database changed
mysql> source /var/lib/mysql/bookdb.sql;
Query OK, 0 rows affected (0.00 sec)
……
访问http://192.168.80.134:9000/odata/Books,检查结果。
从以上操作可以看出,手工导入数据库的方法是很麻烦的,所以最好的办法就是使用代码EnsureCreated()自动创建数据库。