利用dotnet命令行,快速创建简单WEB三层架构

我们要保证dotnet的开发环境和运行已经安装在本地,我们可以利用“
dotnet –version”命令进行检查,如果发现提示命令行不存在,说明环境还没安装,需要先安装,安装过程本文不在详细描述,参考微软的官方说明即可,本文重点在于如何用dotnet命令行,快速构建简单三层架构

一:首先,我们创建好项目的文件夹,然后进入文件夹,创建一个web的应用程序,具体用到的命令是“dotnet new”,dotnet new – 根据指定的模板,创建新的项目、配置文件或解决方案。常用的有类库“classlib”、控制台应用程序“console”、WebAPI应用“webapi”、ASP.NET Core Web 应用程序“webapp”

好了,我们创建一个名称叫Server的WebAPI程序、业务逻辑层的类库BLL、数据交互层DAL和工具层Common

dotnet new webapi -n Server
dotnet new classlib -n BLL
dotnet new classlib -n DAL
dotnet new classlib -n Common

此时,我们可以用visual code看下目录结构

二:我们要让这些类库至今添加引用关系,让Server引用BLL层和Common;BLL层引用DAL层和Common;DAL层引用Common层;这里,我们用到的命令是“dotnet add/list/remove reference”

dotnet add Server/Server.csproj reference BLL/BLL.csproj
dotnet add Server/Server.csproj reference Common/Common.csproj
dotnet add BLL/BLL.csproj reference DAL/DAL.csproj
dotnet add BLL/BLL.csproj reference Common/Common.csproj
dotnet add DAL/DAL.csproj reference Common/Common.csproj

三:接下来我们要在DAL层引入微软推荐的EntityFrameWork框架来进行数据库数据的访问,此处例子,我已经在“.Net Core + EntityFramework连接数据库”一文中有描述,此处只做简单说明,我用MySQL为例,我们需要引入

Microsoft.EntityFrameworkCore

Microsoft.EntityFrameworkCore.Design

Microsoft.EntityFrameworkCore.Tools

Pomelo.EntityFrameworkCore.MySql

Microsoft.Extensions.Configuration.Json

我们用dotnet add package – 添加或更新项目文件中的包引用。在引用之前,我们可以用dotnet package search 命令搜索 NuGet 包。

dotnet package search Microsoft.EntityFrameworkCore

然后,我们可以选择对应的版本,我这里使用的是8.0.2,进行添加,当然,也可以忽略版本号,用最新版本,前提是你的.net的框架也是最新的,可以兼容此版本

dotnet add DAL/DAL.csproj package Microsoft.EntityFrameworkCore --version 8.0.2
dotnet add DAL/DAL.csproj package Microsoft.EntityFrameworkCore.Design --version 8.0.2
dotnet add DAL/DAL.csproj package Microsoft.EntityFrameworkCore.Tools --version 8.0.2
dotnet add DAL/DAL.csproj package Pomelo.EntityFrameworkCore.MySql --version 8.0.2
dotnet add DAL/DAL.csproj package Microsoft.Extensions.Configuration.Json

三:下面框架和引用已经搭建完成了,我们需要把项目和数据库连接起来,让我们在DAL项目里面新建一个appsettings.json配置文件,把mysql的数据库连接字符串写上,里面填上你的数据库ip,用户名,密码,以及你想存放的数据库名称

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Port=3306;Database=数据库名称;User=root;Password=你的mysql密码;Charset=utf8mb4;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

在DAL项目里创建Modles文件夹用来存放数据库对象,比如User类,对应数据库User表

namespace DAL.Models;
using System.ComponentModel.DataAnnotations.Schema;

public class User
{
  [Column("Id")]
  public int Id { get; set; }
  
  [Column("UserName")]
  public string? UserName { get; set; }
}

在DAL项目里创建Contexts文件用来存放上下文类,此类建议和数据库名称一一对应

namespace DAL.Contexts;
using Microsoft.EntityFrameworkCore;
using DAL.Models;
using Microsoft.Extensions.Configuration;

public class webapplicationContext : DbContext
{
  public webapplicationContext()
  {
    Configuration = new ConfigurationBuilder().SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build();
  }
  public IConfiguration Configuration;
  public required DbSet<User> Users { get; set; }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
  }
}

四:初始化数据库,当我们完成了项目基本的搭建,这时候,我们需要用Entity Framework Core 的命令行工具,创建数据库,首先我们打开终端,定位到项目所在位置,然后,就可以用ef命令来初始化数据库,如图所示

cd DAL
dotnet ef migrations add InitialCreate
dotnet ef database update

完成后,发现我们对应的数据库就会有对应的库和表了

关于EntityFramWork的操作,请参考.Net Core+Entity Framework读写数据

下面,让我们回到Server文件夹下,运行dotnet run命令,找到对应端口,访问网页http://localhost:端口/swagger/index.html,看看是否正常

cd ..
cd Server
dotnet run

最后,我们可以给项目文件根里添加.gitignore文件,忽略bin文件,方便以后版本控制

*.log
temp/
.vs/
BLL/bin
BLL/obj
DAL/bin
DAL/obj
Server/bin
Server/obj
Common/bin
Common/obj

我们也可以在Server项目里新建Controllers文件夹,用来存放控制类

using Microsoft.AspNetCore.Mvc;

namespace Server.Controllers;

[ApiController]
[Route("api/[controller]")]
public class WeatherForcecastController : ControllerBase
{
    private readonly ILogger<WeatherForcecastController> _logger;

    public WeatherForcecastController(ILogger<WeatherForcecastController> logger)
    {
        _logger = logger;
    }

    private static readonly string[] Summaries = new[]
    {
      "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    [HttpGet(Name = "GetWeatherForecast")]
    public IEnumerable<WeatherForecast> GetWeatherForecast()
    {
        var forecast =  Enumerable.Range(1, 5).Select(index =>  new WeatherForecast
        (
          DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
          Random.Shared.Next(-20, 55),
          Summaries[Random.Shared.Next(Summaries.Length)]
        )).ToArray();
        return forecast;
    }

    public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
    {
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

最后把Server/Program.cs文件调整一下,加载控制类

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.MapControllers();
app.Run();

MySQL8 新特性和常用查询优化

1:默认数据引擎改变为InnoDB

Mysql8之前的版本,默认引擎为MyISAM(主要的非事务处理存储引擎),从Mysql8开始,数据库默认引擎为InnoDB(支持事务、分布式、事务部分回滚、行级锁、外键)

2:默认字符集改变为utf8mb4

Mysql8之前的版本,默认字符集为lation1,从Mysql8开始,数据库默认编码改为utf8mb4

3:新增设置系统变量参数(Persist),全局变量的持久化,如下:

show [global|session] variables like '%time_zone%'; 
 set [global|session|persist] time_zone='+8:00';  
 flush privileges; # 立即生效

4:自增变量的持久化

 Create table test1(
 id int auto_increment primary key,
 Val int
 );
 Insert into test1(val) values (1);
 Delete from table where id=1;
 -- 重启数据库
 Insert into test1(val) values (1);
 Select * from test1
 -- 发现id变成了2,但如果是MySQL5的版本,则id会是1
 

5:新增窗口函数

窗口函数类似sum(),count()那样的聚合函数,但是它不会将多行合并,而是将结果多行返回,也就是说,窗口函数是不需要Group By的,以下是几个简单的例子

 select *,sum(count) over() as totalcount from Orders;
 
 select *,count/(sum(count) over()) as totalcount from Orders;

6:新增通用表达式

通用表达试简称CTE,CTE是命名的临时结果集,作用范围是当前语句,CTE可以理解成一个可以复用的子查询,CTE可以引用其他CTE,但是子查询不能引用其他子查询 例如我们在8.0之前的sql,只能使用子查询获取类别名称

 Select g.*,(select name from category where id=g.cat_id)  cat_name from goods g;

但是在8.0之后,我们可以用CTE的方式

 With cte as ( select * from category)    
 Select g.*,(select cte.name from cte where cte.id=g.cat_id)  cat_name from goods g;

相比子查询,cte的效率会更高,因为非递归的cte只会查询一次并可以重复使用。
我们也可以使用cte引用其它cte,达到查询目的,查询例子如下

 With cte1 as ( select * from category),
 cte2 as (select g.*,cte1. name  cat_name  from goods g left join cte1 on g.cat_id=cte1.id) 
 select * from cte2

7:通用表达式CET递归神器

递归cte是特殊的cte,必须以WITH RECURSIVE开头,递归子查询包括两部分,SEED查询和RECURSIVE查询,中间由union[all]分隔, SEED查询只会执行1次,以创建初始数据集, RECURSIVE查询会重复执行,直到无法满足语句条件为止。

With RECURSIVE cte as (
 select 1 n     -- SEED查询
 union all 
 Select n+1 n from cte where n<8)  -- RECURSIVE查询
 Select * from cte;

以下是2个常用场景

场景1:递归查询指定节点的所有后代

 set @customer_id=2; -- 当前节点
 WITH RECURSIVE cte AS
 (
 SELECT 
 a.customer_id, a.inviter_id,
 cast(a.inviter_id as char(255)) path
 FROM t_customer_relation a WHERE a.customer_id=@customer_id
 UNION ALL
 SELECT
 k.customer_id,k.inviter_id,
 concat(cte.path,',', k.inviter_id) path
 FROM t_customer_relation k 
 INNER JOIN cte ON cte.customer_id = k.inviter_id
 )
 SELECT cte.customer_id,cte.inviter_id,cte.path  FROM cte 
 -- left join t_customer d  on cte.customer_id=d.id
 

场景2:根据当前节点,查询自己的祖宗

set @customer_id =29 ; -- 当前节点
 WITH RECURSIVE cte AS(
     SELECT
     customer_id, 
     inviter_id, 
     convert(inviter_id , char(255)) path 
     FROM t_customer_relation WHERE customer_id = @customer_id 
     UNION ALL
     SELECT 
     c.customer_id, 
     c.inviter_id, 
     concat(cte.path,',', c.inviter_id) path 
     FROM t_customer_relation c, cte WHERE c.customer_id= cte.inviter_id
 )SELECT * FROM cte;

8:支持json类型

Mysql是关系数据库,我们如果再Mysql8前如果有此类需求,一般用Blob类型存取。但是存在以下 缺点:

1.无法保证json正确性

2.json的二操作加工需要代码处理

3.读取json的某个字段,必须从数据库读出字段所有内容

4.无法在json上的某个字段建索引

现在,可以试试在表里使用json类型字段。如下:

Create table tb(
 jsdata  json
 );
 Insert into table(’{”key”:”123”}’);

值得注意的是,key的长度不能超过2个字节(65535)。Mysql8.0提供了很多操作json的函数,包括条件查询,具体可以参考官方文档说明

9:其他新特性

a:加密函数(md5(str),sha (str),sha2 (str,hash_length))

b:GROUP BY 不再隐式排序

c:DLL的原子化(drop table table1,table2;如果table2不存在,那table1将不会删除)

d:支持降序索引

e:统计直方图

f:支持表空间加密

g:支持不可见索引(相对于enable,哪怕隐藏时依然和正常索引一样实时更新,查询不再走)

h:跳过锁等待,sql如下:

select * from table where id=1 for update nowait; --有锁就报错)
select * from table where id=1 for update skip locked; --有锁就不理)

MySql时间格式函数

以下命令为MySql的常用时间函数,可以在编写服务端时,为时间运算和分组提供便利,具体函数如下

select now() `当前时间`,
 unix_timestamp(now()) `时间戳(秒)`,
 from_unixtime(unix_timestamp(now())) `时间戳->时间`,
 concat(date_format(utc_date(),'%Y-%m-%d'),date_format(utc_time(),' %H:%i:%s')) `格林尼治时间`,
 month(now()) `月份`,
 monthname(now()) `月份`,
 dayname(now()) `星期`,
 dayofweek(now()) `对应一周中的索引(1周日 2周一 …… 7周六)`,
 weekday(now()) `日期对应的周索引(0周一 1周二 …… 6周日)`,
 week('2021-01-02',0) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(0~53)  本年度中有一个周日
 week('2021-01-02',2) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(1~53)  本年度中有一个周日
 week('2021-01-02',1) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(0~53)  本年度中有3天以上
 week('2021-01-02',3) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(1~53)  本年度中有3天以上
 week('2021-01-02',4) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(0~53)  本年度中有3天以上
 week('2021-01-02',6) `计算时间是一年中的第几周`,   --  一周的第一天(日) 范围(1~53)  本年度中有3天以上
 week('2021-01-02',5) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(0~53)  本年度中有一个周一
 week('2021-01-02',7) `计算时间是一年中的第几周`,   --  一周的第一天(一) 范围(1~53)  本年度中有一个周一
 dayofyear(now()) `一年当中的第几天`,
 date_Add(now(),interval 1 day) `新增一天`,
 date_format(now(),'%Y-%m-%d %H:%i:%s') `时间格式化`

创建MYSQL的定时任务

自MySQL5.1.6起,增加了一个非常有特色的功能-事件调度器(Event Scheduler),可以用做定时执行某些特定任务(例如:删除记录、对数据进行汇总、数据备份等等)。更值得一提的是MySQL的事件调度器可以精确到每秒钟执行一个任务,而操作系统的计划任务(如:Linux的cron或Windows下的任务计划)只能精确到每分钟执行一次。对于一些对数据实时性要求比较高的应用(例如:股票、赔率、比分等)就非常适合。

1、在使用这个功能之前必须确保event_scheduler已开启,可执行

 SET GLOBAL event_scheduler = 1;

2、要查看当前是否已开启事件调度器,可执行如下SQL:

 SHOW VARIABLES LIKE 'event_scheduler';

注:以下为其他常用命令

 ALTER EVENT eventName ON COMPLETION PRESERVE DISABLE;  --关闭事件任务
 ALTER EVENT eventName ON COMPLETION PRESERVE ENABLE;   --开启事件任务
 SHOW EVENTS;                                          --查看事件任务

具体创建语法如下

CREATE EVENT [IFNOT EXISTS] event_name
 ONSCHEDULE schedule
 [ONCOMPLETION [NOT] PRESERVE]
 [ENABLE | DISABLE]
 [COMMENT 'comment']
 DO sql_statement;

schedule:
AT TIMESTAMP [+ INTERVAL INTERVAL]
| EVERY INTERVAL [STARTS TIMESTAMP] [ENDS TIMESTAMP]
  
INTERVAL:
quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}

首先来看一个简单的例子来演示每秒插入一条记录到数据表 

CREATE TABLE aaa(timeline TIMESTAMP);
 CREATE EVENT e_test_insert
 ON SCHEDULE EVERY 1 SECOND
 DO INSERT aaa VALUE(CURRENT_TIMESTAMP);

等待3秒之后,再执行查询看看,可以看到aaa表会有3条数据
再来看看修改他的语法

ALTER EVENT event_name
 [ONSCHEDULE schedule]
 [RENAME TOnew_event_name]
 [ON COMPLETION [NOT] PRESERVE]
 [COMMENT 'comment']
 [ENABLE | DISABLE]
 [DO sql_statement]

以下是几个简单例子

ALTER EVENT e_test DISABLE;   -- 临时关闭事件
 ALTER EVENT e_test ENABLE;   -- 开启事件  
 ALTER EVENT e_test ON SCHEDULE EVERY 5 DAY;  -- 将任务改为5天执行一次:

最后是删除事件语法

DROP EVENT [IF EXISTS] event_name