C#速成指南:从入门到进阶,实战WPF与Unity3D开发

694 阅读9分钟

Download: C#速成指南:从入门到进阶,实战WPF与Unity3D开发

GAO清下ZAI: https://www.ukoou.com/resource/1161

在前面的文章中,书大师网站展示的书籍信息是硬编码的。虽然满足了基本需求,但不便于后面的信息更新。更新书籍信息还得修改代码,而且以往发布的书籍信息没有记录。所以我们需要用数据库来做数据的持久化,把数据和代码分离。

为使操作数据库的编程更加高效,我们的项目将使用 EF Core 作为 ORM 框架。

升级为 .NET 6 正式版 上周 .NET 6 已经正式发布了,VS 2022 也同步发布了,相信很多人已经更新了。只要你升级 VS 2022 为最新版本,就会自动安装或升级 .NET 为 6.0。如果你用的是 MacOS 或 Linux 系统,则可能需要去官网手动下载 SDK。

同样,我们的生产环境也做相应的升级,由原来的 RC2 版本升级到正式版。升级很简单,直接按照官网文档操作就行。我的服务器是 CentOS 8,对应的 .NET 6 安装文档:

docs.microsoft.com/en-us/dotne… 注意,如果你生产环境和我一样是 CentOS 8,请按 CentOS 7 的方式安装 ASP.NET Core 运行时,因为:

.NET大牛之路 035 | 实战:使用MySQL+EF Core实现数据持久化 官网表示 .NET 6 不支持 CentOS 8 了,但 CentOS 8 包的安装是兼容 CentOS 7 的,所以按 CentOS 7 的方式安装即可。执行文档中的两个命令即可:

sudorpmUvhhttps://packages.microsoft.com/config/centos/7/packagesmicrosoftprod.rpmsudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm sudo yum install aspnetcore-runtime-6.0 安装完成后,需要修改一下我们的 bookist.service 服务文件:

.NET大牛之路 035 | 实战:使用MySQL+EF Core实现数据持久化 然后使用 systemctl daemon-reload 重新加载服务。

安装 MySQL 我们这个项目使用的是 MySQL 数据库,版本是 8.x。由于我们使用的是 EF Core ORM 框架,对于同一套代码,不同数据库,API 都是一样的,所以你也可以使用任何其它你熟悉的数据库。

为了简单起见,开发和生产环境我直接使用同一数据库,加上大多数个人没有条件为一个小网站设一个专门的数据库服务器,所以我把 MySQL 数据库直接安装在应用生产环境。

CentOS 8 安装 MySQL 8 很简单,直接运行下面命令:

$ dnf -y install @mysql 稍等片刻即可完成安装,使用下面命令验证一下版本:

$ mysql -V mysql Ver 8.0.26 for Linux on x86_64 (Source distribution) 安装完成后,我们还需要把 MySQL 服务开启,并设为开机启动,运行如下两行命令:

开启 MySQL 服务

$ systemctl start mysqld

使 MySQL 开机启动

$ systemctl enable mysqld Created symlink /etc/systemd/system/multi-user.target.wants/mysqld.service → /usr/lib/systemd/system/mysqld.service. 我们还需要设置一下数据库用户 root 的访问密码,以便我们程序能够连接。使用如下命令进入 MySQL 的安全向导:

$ mysql_secure_installation 运行该脚本后,会让你选择配置安全选项,比如密码强度设置,这个根据自己需要选择。其中,有两个选项需要注意,一是允许 root 远程访问,二是配置 root 用户密码:

.NET大牛之路 035 | 实战:使用MySQL+EF Core实现数据持久化 为了更方便远程管理数据库,我们需要开户 MySQL 的远程登录,所以建议设置高强度的密码。如果你使用的是云服务器,需要先到安全组配置中添加 3306 端口的入站规则。以腾讯云为例:

.NET大牛之路 035 | 实战:使用MySQL+EF Core实现数据持久化 注意,如果是重要项目,请关闭 root 远程登录。如果需要远程登录,则创建权限受限的数据库帐号。

最后,需要配置一下允许远程访问数据库的 IP 地址,这里配置为所有 IP(因为家庭宽带 IP 一般是动态的):

$ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 14 Server version: 8.0.26 Source distribution

mysql> CREATE USER 'root'@'%' IDENTIFIED BY '这里是你的密码'; Query OK, 0 rows affected (0.01 sec)

mysql> GRANT ALL ON . TO 'root'@'%'; Query OK, 0 rows affected (0.01 sec)

mysql> FLUSH PRIVILEGES; Query OK, 0 rows affected (0.00 sec)

mysql> exit 使用任一 MySQL 客户端(我用的是 Navicat),测试一下在本地是否可以正常连接数据库。

添加 EF Core 包 Entity Framework Core 是微软提供的一个非常强大的 ORM(Object Relational Mapper )框架。下面在我们的项目中使用该框架。

切换到 Bookist.Web 目录,使用如下命令安装两个 Nuget 包:

dotnetaddpackageMicrosoft.EntityFrameworkCore.Designdotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Pomelo.EntityFrameworkCore.MySql 第一个是 EF Core 的设计库,包含 EF Core 基本库和工具库;第二个是一个第三方的 MySql 支持库,不同的数据库需要使用对应的支持库,它们实现了使用 EF Core 用统一的接口访问数据库。安装完后会在 Bookist.Web.csproj 文件中生成如下代码:

... runtime; build; native; contentfiles; analyzers; buildtransitive all ... 如果你用的是 VS,可以用 Nuget 包管理器搜索 EntityFrameworkCore 安装这两个包。

设计实体类 要使 ORM 框架能把数据表映射为 C# 对象,则需要把 C# 类设计成和数据库实体一致,EF Core 会按照约定俗成的规则(比如默认类的属性应对数据表的字段)自动处理。

我们把之前的 BookVM 类重命名为 Book,属性基本保持不变,只是加上了主键和一些用于描述字段定义的特性:

using System.ComponentModel.DataAnnotations;

namespace Bookist.Web.Models;

public class Book { public int Id { get; set; }

[Required, MaxLength(100)]
public string Title { get; set; }

[Required, MaxLength(255)]
public string Cover { get; set; }

[MaxLength(100)]
public string Author { get; set; }

public DateOnly PubDate { get; set; }

[MaxLength(2000)]
public string Description { get; set; }

[Required, MaxLength(50)]
public string Format { get; set; }

[Required, MaxLength(100)]
public string FetchUrl { get; set; }

[MaxLength(10)]
public string FetchCode { get; set; }

} 这里的 MaxLength 将告诉 EF Core 为字符串类型的字段指定最大长度,比如 Title 是 varchar(100)。

我们不需要编写任何 SQL 脚本,通过使用 EF 工具可以自动为我们生成脚本,并可自动执行到数据库,创建对应的 Book 数据表,下文将介绍如何操作。

编写 DbContext 对于 EF Core 来说,一般一个数据库对应一个 DbContext 类,它对应数据库操作的上下文,所有的数据库操作都是在这个上下文中进行。

在 Bookist.Web/Models 目录中添加一个类,名为 BookistDbContext,代码如下:

using Microsoft.EntityFrameworkCore;

namespace Bookist.Web.Models;

public class BookistDbContext : DbContext { public BookistDbContext(DbContextOptions options) : base(options) { }

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Book>();
}

} 注意,自定义的 DbContext 类一定要和上面这样写一个带有 DbContextOptions 参数的构造函数。

在 BookistDbContext 类中覆盖 OnModelCreating() 方法,在该方法中通过 builder.Entity() 方法指定要映射的实体类。

DbContext 很强大,基本上所有的数据库定义和操作都可以通过它来完成,比如定义数据修改的勾子,后面我们还会经常接触这个类。

配置 EF Core 要使我们的应用程序能连上数据库,自然少不了数据库连接。在 appsettings.json 中添加数据库连接字符串,如下:

{ "ConnectionStrings": { "BookistConnection": "server=你的服务器IP;port=3306;database=bookist;uid=root;pwd=你的数据库密码" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } 然后我们还需要注册 EF Core 服务,编辑 Program.cs 文件如下:

using Bookist.Web.Models; using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args); var config = builder.Configuration;

// 注册服务 builder.Services.AddControllersWithViews(); builder.Services.AddDbContext(opt => { var conStr = config.GetConnectionString("BookistConnection"); opt.UseMySql(conStr, ServerVersion.Parse("8.0")); });

var app = builder.Build();

// 注册中间件 app.UseStaticFiles(); app.MapDefaultControllerRoute();

app.Run(); 这里通过 AddDbContext 方法注册了相关服务并指定了 BookistDbContext 的数据库连接字符串。

使用 EF 工具构建数据库 .NET CLI 提供了 EF 相关的工具,代号为 dotnet-ef。它可以帮助我们自动生成脚本、更新数据库结构等。第一次使用需要使用如下命令安装:

$ dotnet tool install --global dotnet-ef 可使用以下命令调用工具: dotnet-ef 已成功安装工具“dotnet-ef”(版本“6.0.0”)。 如果你已经安装过,也可以执行该命令将 dotnet-ef 升级到最新版本。

在 Bookist.Web 目录下,使用如下命令给当前实体类创建一个数据库变更版本:

$ dotnet ef migrations add 001 为了简单起见,我这里直接使用数字序号代表每次数据库的变更,你也可以使用更友好的描述性的词。执行该命令后,会在目录下生成一个 Migrations 文件夹和对应的数据库修改描述文件,*.cs 和 *.Designer.cs。

第一次创建数据库,我们可以直接使用如下命令(不推荐这种方式):

$ dotnet ef database update 这个命令会自动生成数据库升级脚本,并自动执行。此时,我们的数据库已经创建好了,包括一个 Books 表。

但是直接自动执行升级脚本是非常不安全的。稍大点的开发团队是有专门的 DBA 来修改数据库结构的,一般我们需要把数据库升级脚本给到 DBA 去执行。而且,有时候数据库升级不仅仅是结构发生变化,也有可能数据也需要调整,比如删除一个字段的同时把这个字段的值移到另一个表,这时候我们需要增加手写脚本了。

所以推荐的做法是,使用如下命令先生成数据库升级脚本:

$ dotnet ef migrations script -o ./Scripts/001.sql 检查生成的脚本文件 001.sql,确认无误后再把脚本复制到服务器上执行。

由于我们还没有做后台书籍管理的功能,所以暂时需要手动添加数据,执行如下脚本创建初始数据:

INSERT INTO bookist.Book (Title, Cover, Author, PubDate, Description, Format, FetchUrl, FetchCode) VALUES ('CLR via C# (4th Edition)', 'img.geekgist.com/Foc1d-NbacA…', 'Jeffrey Richter', '2012-10-01', 'Dig deep and master the intricacies of the CLR, C#, and .NET development. You’ll gain pragmatic insights for building robust, reliable, and responsive apps and components.', 'PDF', 'url19.ctfile.com/f/15677019-…', 'bookist'); 从数据库读取数据 数据和 EF Core 配置都准备好了,现在只需要使用 EF Core 把数据从数据库读取出来展示到页面上就行。

原来我们是把数据硬编码到 Controller 中返回给 View 的,这次改为从数据库中读取,视图代码不动。这里就体现了 MVC 分层的优点,业务代码修改了不会影响到视图。

修改 HomeController 代码如下:

using Bookist.Web.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore;

namespace Bookist.Web.Controllers;

public class HomeController : Controller { private readonly BookistDbContext _context;

public HomeController(BookistDbContext context)
{
    _context = context;
}

public async Task<ViewResult> Index()
{
    var book = await _context.Set<Book>()
        .OrderBy(x => x.Id).LastOrDefaultAsync();
    return View(book);
}

} 运行起来,浏览器查看一下确保程序没有问题。到这,我们就成功使用 EF Core + MySQL 实现了数据持久化。

完整代码已同步更新到 GitHub:

github.com/liamwang/bo… 保护敏感信息 前文我们在 appsettings.json 中配置了数据库连接字符串,请千万不要把这种敏感信息提交到公开的 Git 仓库。

在 .NET 中,我们可以使用 user-secrets 工具来管理敏感信息,这个工具可以将敏感信息保存在一个 secrets.json 文件中,它不在项目文件夹下,而是存放在另外的地方。对于三种操作系统,它的位置是:

Windows: %APPDATA%/Microsoft/UserSecrets//secrets.json Linux : ~/.microsoft/usersecrets//secrets.json Mac : ~/.microsoft/usersecrets//secrets.json 一个 .NET 应用对应一个唯一的 UserSecretsId,一般是一个 GUID。

现在我们要把数据库链接字符串存在项目之外的 secrets.json 文件中,避免它随项目代码一起提交到 Git 仓库。

首先使用如下命令初给我们的项目始化一个 UserSecretsId:

$ dotnet user-secrets init Set UserSecretsId to 'adfa2b88-1fe4-438e-b567-03296ac46b2d' for MSBuild project 'D:\bookist\Bookist.Web\Bookist.Web.csproj'. 打开 Bookist.Web.csproj 文件,可以看到生成的 UserSecretsId:

net6.0 disable enable adfa2b88-1fe4-438e-b567-03296ac46b2d ... 然后使用如下命令在 secrets.json 中设置我们想要保护的敏感信息,比如数据库连接字符串:

$ dotnet user-secrets set "ConnectionStrings:BookistConnection" "server=;port=3306;database=bookist;uid=root;pwd=" Successfully saved ConnectionStrings:BookistConnection = server=;port=3306;database=bookist;uid=root;pwd= to the secret store. 你可以使用 dotnet user-secrets list 命令查看已经存储的敏感信息列表。

如果你使用的是 VS,则操作 user-secrets 更简单。直接右击项目,在菜单中选择 Manage User Secrets 选项:

.NET大牛之路 035 | 实战:使用MySQL+EF Core实现数据持久化 它会直接帮你创建好 secrets.json 文件,并自动打开,你直接编辑该文件就行,比使用命令行方便很多。

如果本机 secrets.json 和 appsettings.json 有相同的配置项,程序会优先读取前者的值。

对于 secrets.json 的所有配置项,最好也在 appsettings.json 保留相应的占位,比如数据库连接字符串:

{ "ConnectionStrings": { "BookistConnection": "" } } 在 appsettings.json 中的使用占位可以告诉其它开发者有这样一个配置,这对于开源项目十分有用。

发布 和前两篇文章一样,执行发布命令生成发布文件并复制到服务器上,然后重启服务即可,这里不再赘述。

不一样的是,这次我们生成发布文件后,配置文件中并没有包含真实的连接字符串。为了使生产环境拥有独立的配置项(比如生产环境的连接字符串和本机不一样),避免每次替换把生产环境的配置文件覆盖掉,我们在生产环境中创建一个名为 appsettings.Production.json 的文件,其内容和 appsettings.josn 文件一致,只修改其中数据库连接字符串:

{ "ConnectionStrings": { "BookistConnection": "server=localhost;database=bookist;uid=root;pwd=***" } } 生产环境运行的是 Release 模式,程序会优先读取 *.Production.json 配置文件。这个 appsettings.Production.json 文件只在生产服务器上有,所以即使我们复制全部文件替换到服务器也没有关系。