Dot Net Core 进阶教程
文章目录
前言
.NET Core 是一个开源、跨平台的框架,用于构建现代 Web 应用、微服务、物联网 (IoT) 应用和云服务。它由 Microsoft 开发,并于 2016 年正式发布。.NET Core 是 .NET Framework 的一个轻量级、模块化版本,旨在提供更高的性能和更好的跨平台支持。
NutGet
NuGet 是 .NET 生态系统中的一个包管理器,用于管理和分发 .NET 库和工具。类似于java的Maven,python的pip,Node.js中的npm
使用
- 首先我们选择要下载的依赖,这里随便举个Pomelo.EntityFrameworkCore.MySql 3.2.6版本例子
VSCode傻瓜下载
- 第一步找到界面
- 搜索,选择项目版本下载
- 可以看到我们下载好的包了,如果要卸载点击卸载即可,或者删掉ItemGroup中的代码
程序包管理器
-
打开管理器
-
复制下载/卸载命令执行
异步
异步和多线程的区别
这里举一个例子-服务员点餐,当有多个服务员,这多个服务员给用户菜单,当用户点完菜服务员才可以走,多个服务员就是多线程。可以看到单个用户的点餐效率并没有改变。 而异步就是,这个服务员给完用户菜单就可以给别的用户菜单,而不必等待。
async,await介绍
“异步方法”:用async关键字修饰的方法
l)异步方法的返回值一般是Task<T>,T是真正的返回值类型,Task<int>。惯例:异步方法名字以Async:结尾。
2)即使方法没有返回值,也最好把返回值声明为非泛型的Task。
3)调用泛型方法时,一般在方法前加上await关,这样拿到的返回值就是泛型指定的T类型
4)异步方法的“传染性”:一个方法中如果有await调用,则这个方法也必须修饰为async
-
eg
static async Task downUrlContent(string url,string filename) { //由于HttpClient实现了IDisposable接口,需要using关闭资源 using (HttpClient client = new HttpClient()) { //获取请求的内容 string content = await client.GetStringAsync(url); //保存本地 await File.WriteAllTextAsync(filename, content); } }
原理
- 总结:async的方法会被C#编译器编译成一个类,会主要根据awaiti调用
进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调
用,用await看似是“等待”,经过编译后,其实没有“wait”。 - 注意:
异步不等于多线程
使用async不会new 一个新的线程来执行,如需要就要使用多线程了,例如Task.run();等等
甩手
当然我们可能会看到没有使用async和await搭配使用的api,其实这种是直接‘甩手‘的,有利于提高性能
-
正常eg
static async Task<string> doSelectNum(int i) { switch (i) { case 1: string s = await File.ReadAllTextAsync(@"E:\1.txt"); return s; case 2: string y= await File.ReadAllTextAsync(@"E:\20241030.log"); return y; } return ""; }
-
'甩手’eg
由于返回的都是Task,我们直接返回不做拆分
之前我们说的原理,由于async反编译是封装了一个类,然后分了几个代码端执行。只甩手Task,不“拆完了再装反编译上面的代码:只是普通的方法调用这样运行效率更高,不会造成线程浪费。static Task<string> doSelectNum(int i) { switch (i) { case 1: return File.ReadAllTextAsync(@"E:\1.txt"); case 2: return File.ReadAllTextAsync(@"E:\20241030.log"); } throw new Exception(); }
CancellationToken
有时需要提前终止任务,比如:请求超时、用户取消请
求,很多异步方法都有CancellationToken参数,用于
获得提前终止执行的信号。
-
示例
public static async Task cancelToken1Async2(string url, int n,CancellationToken token) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { string readUrlContent = await client.GetStringAsync(url); Console.WriteLine(readUrlContent); //如果达到token设定的值 if (token.IsCancellationRequested) { Console.WriteLine("凌冬将至"); break; } } } }
这里我们可以构建一个CancellationToken,CancellationToken中我们可以设置执行终止的时间,例如:
CancellationTokenSource cs = new CancellationTokenSource();cs.CancelAfter(1000);//1s 后终止;
await cancelToken1Async2(url, 100, cs.Token);//传入token
举一反三
当再web中,用户在点击完请求之后,关闭浏览器,而服务器还在运行,这就浪费不必要的资源,而CancellationToken可以帮我们识别这一点,自动关闭服务器资源,所以只需要传递CancellationToken来保证这点。
public async Task<IActionResult> Index(CancellationToken token)
{
await cancelToken1Async2(@"https://www.baidu.com", 1000, token);//传递
return View();
}
public static async Task cancelToken1Async2(string url, int n, CancellationToken token)
{
using (HttpClient client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
var readUrlContent = await client.GetAsync(url, token);//另一种形式
string html = await readUrlContent.Content.ReadAsStringAsync();
Debug.WriteLine(html);
如果达到token设定的值
//if (token.IsCancellationRequested)
//{
// Console.WriteLine("凌冬将至");
// break;
//}
}
}
}
- 这里用了另一种形式来获取html,之前的demo也行
异步其他问题
异步不用sleep
- 因为sleep会阻塞线程,而异步本质是在运行时可以运行其他内容
使用 await Task.Delay();
WhenAll
Task类的重要方法:
1.Task<Task>WhenAny(IEnumerable<Task> tasks)等,任何一个Task完成,Task就完成
2.Task<TResult[]>WhenAll<TResult>(paramTask<TResult>:[]tasks)等,所有Task完成,Task才完成。用于等待多个任务执行结束但是不在乎它们的执行顺序。
3.FromResult)创建普通数值的Task对象
- 当有多个异步任务时,await 是等待异步执行完再执行下面的,但是我们需要多个异步任务不需要等待,可以同时执行,可以用when all来获取几个异步的返回
计算路径文件字符
public async Task<IActionResult> WenAllIndex()
{
string url = @"C:\Users\xiaohh\Desktop\上传文件\testread";
//获取路径下的所有文件
string[] allFilePaths = Directory.GetFiles(url, "*.*", SearchOption.AllDirectories);
Task<int>[] task = new Task<int>[allFilePaths.Length];
for(int i = 0; i < task.Length; i++)
{
task[i] = readSingleLength(allFilePaths[i]);
}
int[] countChar = await Task.WhenAll(task);//获取各个任务的结果
int len = countChar.Sum();
ViewBag.Len = len;
return View();
}
/// <summary>
/// 计算文件的字符数
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<int> readSingleLength(string url)
{
var result = await System.IO.File.ReadAllTextAsync(url);
return result.Length;
}
- 每个文件用异步读取字符数
LINQ
委托
1.委托是可以指向方法的类型,调用委托变量时执行的就是变量指向的方法
2.NET中定义了泛型委托Action(无返回值)和Func(有返回值),所以一般不用自定义委托类型。
-
什么意思呢?
通常我们定义一个变量 int x =1//那么这个x就是指向int类型的变量 委托同理,委托就是指向方法的变量,FunM:为你定义的方法名,deM为你定义的委托 deM m = FunM;此时你就可以使用变量调用方法了,m()
-
例子
-
我们可以不用定义委托,使用Func或者Action
Func<string> f1 = Func1M; //public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2); Func<int,int,int> f2 = Func2M;//前两个参数,后一个返回值 ViewBag.F1M = f1();//调用方法 ViewBag.F2M = f2(1, 4); return View();
Lambda表达式
知道了委托,那么Lambda表达式就不难理解了,Lambda表达式就是将方法匿名,没有方法名,例如上面的表达式我们可以用Lambda
Func<int,int,int> f2 = Func2M;//前两个参数,后一个返回值
Func<int, int, int> f22 = delegate (int x, int y)
{
return x + y;
};
Func<int, int, int> f222 = (int x, int y) => { return x + y; };
上面三种形式都是一样的
linq的用法和sql语句很类似,和java stream流的过滤也类似,所以自己搜教程去学习
-
eg
List<Person> list =PersonUtil.getPersons(); //按年龄分组,获取最大值,然后将年龄和组别的最大值映射成一个对象 var gList=list.GroupBy(e => e.Age).Select(e => new { MyKey=e.Key, Max = e.Sum(e=>e.Age) }) ; foreach(var item in gList) { Console.WriteLine($"{item.MyKey}-{item.Max}"); }
-
eg2
//统计频次大于2的字符,并按频次顺序排列 string str = "aflajdflk2jiedsljflkjfalsjfeoifjdsalkfa"; //根据字符分组,然后构建字符,个数对象 var dic = str.GroupBy(c => c).Select(c => new { letter = c.Key, cont = c.Count() }).OrderByDescending(x=>x.cont).ToDictionary(g => g.letter, g => g.cont); foreach(var pair in dic) { Console.WriteLine(pair.Key+"======"+pair.Value); }
依赖注入(DI)
依赖注入(Dependency Injection,简称 DI)是一种设计模式,用于减少代码之间的耦合度,提高代码的可测试性和可维护性。通过依赖注入,对象的依赖关系不再由对象自身创建或管理,而是由外部提供给对象。例如:本来你要自己做饭,现在雇佣了一个名厨师,想要什么菜告诉他就行了,你等着吃。
DI几个概念
服务(service):对象;
注册服务;
服务容器:负责管理注册的服务;(管理对象)
查询服务:创建对象及关联对象;(从容器中获取对象)
对象生命周期:Transient(瞬态:获取的对象都是new);Scoped
(范围:在范围之内都是同一个对象);Singleton(单例:都是同一个对象)
NET中使用DI
NET中使用DI
1、测试代码见备注
2、根据类型来获取和注册服务
可以分别指定服务类型(service type)和实现类
型(implementation type)。这两者可能相同,
也可能不同。服务类型可以是类,也可以是接口,
建议面向接口编程,更灵活。
3、.NET控制反转组件取名为Dependencylnjection,
但它包含ServiceLocator的功能。
生命周期
1、给类构造函数中打印,看看不同生命周期的对象创建
使用serviceProvider…CreateScope()创建Scope。
2、如果一个类实现了IDisposable:接口,则离开作用域之后
容器会自动调用对象的Dispose方法
3、不要在长生命周期的对象中引用比它短的生命周期的对
象。在ASP.NET Core中,这样做默认会抛异常
4、生命周期的选择:如果类无状态,建议为Singleton;如
果类有状态,且有Scope控制,建议为Scoped,因为通常这
种Scope控制下的代码都是运行在同一个线程中的,没有并
发修改的问题;在使用Transient的时候要谨慎。
5、.NET注册服务的重载方法很多,看着文档琢磨吧
步骤(服务定位器)
-
Install-Package Microsoft.Extensions.DependencyInjection
-
using Microsoft.Extensions.DependencyInjection;
-
ServiceCollection用来构造容器对象IServiceProvider。调用ServiceCollection的BuildServiceProvider来获取对象
-
简单来说就是,创建容器,放入容器,获取对象
using ConsoleApp1; using Microsoft.Extensions.DependencyInjection; ServiceCollection services = new ServiceCollection(); //services.AddTransient<Person>(); //瞬态,每次生成不同对象 //services.AddSingleton<Person>();//单例,每次都是一个对象 services.AddScoped<Person>();//在scope范围内生成的是一个对象 using(ServiceProvider provider = services.BuildServiceProvider()) { var person = provider.GetService<Person>(); person.Name = "zhangsan"; person.say(); var person2 = provider.GetService<Person>(); person2.Name = "lisi"; person2.say(); var isTure = Object.ReferenceEquals(person, person2); Console.WriteLine(isTure); //true Person person3; using (IServiceScope scope = provider.CreateScope()) { person3 = scope.ServiceProvider.GetService<Person>(); person.Name = "zhangsan"; person.say(); var person4 = scope.ServiceProvider.GetService<Person>(); person2.Name = "lisi"; person2.say(); var isTure2 = Object.ReferenceEquals(person3, person4); Console.WriteLine(isTure2);//true 同一范围 } var isNotArea = Object.ReferenceEquals(person, person3); Console.WriteLine(isNotArea);//false 不同范围 }
-
实现接口重写方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { internal class Person:IPerson,IDisposable { public string Name { get; set; } public void Dispose() { Console.WriteLine("过了using域就会执行"); } public void say() { Console.WriteLine("say Hello Person===>"+Name); } } }
-
输出
步骤(DI)
-
C#依赖注入是根据对象的构造函数来创建赋值的
using Microsoft.Extensions.DependencyInjection; using System; ServiceCollection services = new ServiceCollection(); //加入容器 services.AddTransient<ILog, Log>();//指明服务是ILog,实现类是Log services.AddTransient<Controller>(); using (ServiceProvider provider = services.BuildServiceProvider()) { //根据构造函数自动注入ILog var c = provider.GetService<Controller>(); c.toJinx(); } class Controller { readonly ILog log; public Controller(ILog log) { this.log = log; } public void toJinx() { log.Log("下载文件"); } } interface ILog { void Log(string msg); } class Log : ILog { void ILog.Log(string msg) { Console.WriteLine("记录日志===>"+msg); } }
配置系统
Json配置
1、创建一个json文件,文件名随意,比如config.json,
设置“如果较新则复制”。参考图片
2、NuGet:安装Microsoft.Extensions.Configuration和
Microsoft.Extensions.Configuration.Json
3、编写代码,先用简单的方式读取配置。
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json",optional:true,reloadOnChange:true);
IConfigurationRoot configurationRoot = configurationBuilder.Build();
string wangzhe = configurationRoot["GAME"];
string douyi = configurationRoot.GetSection("APP:douyi").Value;
Console.WriteLine(wangzhe+douyi);
EFCORE
ORM
- 什么是ORM
以对象的形式进行数据库的读取,ORM是负责程序和数据库之间转换的中间层
1、EF Core是对于底层DO.NET Core的封装,因此
DO.NET Core支持的数据库不一定被EF Core支持。
2、EF Core支持所有主流的数据库,包括MIS SQL ServerOracle、MySQL、PostgreSQL、SQLite等。可以自己实现Provider支持其他数据库。
3、对于SQLServer支持最完美,MIySQL、PostgreSQL也不错(有能解决的小坑)。这三者是NET圈中用的最多的个。本课程主要用SQLServer讲,如果用其他数据库只要改一行代码+绕一些小坑即可,大部分代码用法不变。EFCore能尽量屏蔽底层数据库差异。
框架搭建
-
安装包:Install-Package Microsoft.EntityFrameworkCore.SqlServer
-
新建实体类Book
-
新建配置类BookConfig
class BookConfig : IEntityTypeConfiguration<Book> { public void Configure(EntityTypeBuilder<Book> builder) { builder.ToTable("T_Books");//映射的表 } }
-
新建MyDbContext
class MyDBContext:DbContext { public DbSet<Book> books; protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer("你的数据库连接字符串"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
-
使用(数据插入为例)
static void Main(string[] args) { Console.WriteLine("Hello World!"); using(var mycont = new MyDBContext()) { Book book = new Book(); book.Name = "钢铁是怎样练成的"; mycont.books.Add(book); mycont.SaveChanges();//保存更改 } }
-
其他操作可自行搜索