认识.Net
.net只是一个开发平台
Net Core:是用于一个跨平台的框架
Net Framework:是一个用于win系统上所开发的框架
Xamarin/Mono:是进行移动开发的框架
.Net Framework介绍
缺点:
1、系统级别的安装,互相影响
2、无法独立部署
3、Asp.net 和 IIS深度耦合
4、Asp.net资源消耗大
5、非云原生
#####
.Net Core介绍
从net 5开始 微软后续默认.net就是Net Core
优点:
1、支持独立部署,不相互影响r
2、彻底模块化
3、没有历史包袱,运行效率高
4、不依赖IIS
5、跨平台,符合现代开发理念:依赖注入,单元测试等
.Framework与 Core的不同之处
1、不支持:
Aps.net WebForms,Wcf服务器端、WF、net Remoting、App Remoting, Appdomain
2、部分Windows-Only的特性 net core,但是无法跨平台:WinForm WPF、注册表、Event Log、AD等
Net Standard
Net Standard只有定义没有实现
Net Standard只是一个规范,它的类库可以被其他版本的Framework、Core、Xamrin进行引用如果是编写一个公用的类型尽可能的选择使用Standard
异步编程
C#的异步编程 关键字跟前端一样,async、await
但是使用了async
和await
就可以开始去傻瓜化的去进行多线程的开始。 但是异步编程不等于多线程
异步方法
1、就是通过关键字async
修饰的方法,它的返回值一般是Task<T> T是返回值的类型
2、即使方法没有返回值,但是最好也把返回值声明成非泛型的Task
3、然后去对这个异步方法进行调用,一般是在调用的方法前面加上await
4、异步方法的传染性,一个方法中如果有到await调用,那么这个方法必须修饰为async
using System; using System.IO; namespace te { class Programe { static async Task Main(string[] args) //static async void Main(string[] args) //如果只是将async 写在前面,不去对void进行修改, //程序就会出现 不包含入口点的静态Main方法,所以我们需要去把void 改成Task { /* String worlname = @"F:\Edg\1.txt"; File.WriteAllText(worlname, "hello"); String s = File.ReadAllText(worlname); Console.WriteLine(s);*/ //异步的写法 String worlname = @"F:\Edg\1.txt"; await File.WriteAllTextAsync(worlname, "Hellow"); String s = await File.ReadAllTextAsync(worlname); Console.WriteLine(s); } } } //反正就是如果你通过await去调用了异步方法,那么你就必须要去栈在调用这个方法前面加上async
编写异步方法
HttpClient是.Net框架中的一个类,用于发送HTTP请求并接收响应
using System; using System.IO; namespace te { class Programe { static async Task Main(string[] args) { await Download("http://www.youzack.com", @"F:\\Edg\1.txt"); Console.WriteLine("ok"); } //编写一个异步方法 static async Task Download(string url, string filname) { using (HttpClient httpclient = new HttpClient()) { String html = await httpclient.GetStringAsync(url) ; //通过上方的代码就可以直接拿到网页所获取到的信息 File.WriteAllText(filname, html); } } } } Download方法是一个异步方法,参数为要下载的网址url和保存的文件名filename 首先创建了一个HttpClient对象httpclient 使用httpclient发送GET请求并通过GetStringAsync方法异步获取网页的内容,将结果保存在html变量中,最后,使用File.WriteAllText方法将html的内容写入指定的文件中。
Linq语句
在讲Linq之前复习一下委托、Lambda
委托是可以指向方法的类型,调用委托变量执行就是变量指向的方法
示例: 其实一般来说我们只需要使用Action,Fun 委托就可以了,前者是无返回值,后者是有返回值
class Program { static void Main(string[] args) { Action a = F1; a(); Func<int, int, int> func1 = add; Console.WriteLine(add(3, 4)); } static void F1() { Console.Write("f1"); } static int add(int x, int y) { return x + y; } }
Lambda
委托变量是可以指向匿名方法的
class Program { static void Main(string[] args) { Action f1 = () => { Console.WriteLine("我是一个匿名方法"); }; f1(); Action<string, int> f2 = (string n, int i) => { Console.WriteLine($"n= {n},i={i}"); }; f2("LL", 18); Func<int, int, int> f3 = (int a, int b) => { return a + b; }; Console.WriteLine(f3(2, 4)); } } //大概就这样把,就是前端如果要编写一个函数不就是需要function 函数名(){} //但是通过简写就可以使用 函数名()=>{} , 函数名(){} //=>就是一个指向
正式开始学习Linq
引入linq的示例
通过Linq和Lambda去获取数组中大于10的数
static void Main(string[] args) { int[] nums = new int[] { 2,11,8,15,7 }; IEnumerable<int> res = nums.Where(a =>a>10); foreach (var item in res) { Console.Write(item); } } //其中nums.Where就是Linq语句 //Linq相当于就是对数据库进行操作的语句 差不多把
Linq常用拓展方法(这些扩展方法都是在IEnumerable< T >这个接口为基础)
IEnumberable<T>扩展方法
下方所有例子所使用的基本数据:
class Program { static void Main(string[] args) { List<Employee> list = new List<Employee>(); list.Add(new Employee { id = 1, Name = "LL", Age = 18, Gender = true, Salary = 5000 }); } } class Employee { public long id { get; set; } public string Name { get; set; } public int Age { get; set; } public bool Gender { get; set; } public int Salary { get; set; } public override string ToString() { return $"ID = {id},Name ={Name},Age = {Age},Gender = {Gender},Salary={Salary}"; } }
Where方法 查询方法
IEnumerable<Employee> item1 = list.Where(a => a.Age > 17); //Where(a => a.筛选的参数以及筛选条件) foreach(Employee item in item1) { Console.WriteLine(item); }
Count() 获取符合条件的数据条的个数
Console.WriteLine(list.Count(a => a.Age > 15));//5 Console.WriteLine(list.Count(a => a.Age > 15 && a.Salary>8000 ));
Any() 是否至少有一条数据
跟Count差不多可能比Count实现效率高
Console.WriteLine(list.Any(a => a.Age > 15));
获取一条数据
Single:有且只有一条满足要求的数据
Firsh:至少有一条,返回第一条
SingleOrDefault:最多只有一条满足要求的数据
FirshOrDefault:返回第一条或默认值
总结后面要要用到其他的直接看看数据库语句就完事了
依赖注入中的服务(DI)
依赖注入中控制反转的概念(IOC)
1、服务:相当于一个对象,比如说我想要一个操作文件的对象那就是个服务、想要一个连接数据库的对象就是一个服务
2、注册服务:你想要一个对象,那你不去注册一个对象那怎么能用到它呢
3、服务的容器:用来管理所注册的服务
4、查询服务:创建对象以及关联对象
5、对象声明周期
首先在使用IOC之前,需要去对对象的类型去注册服务,这种服务分成2种,一种是服务类型,一种是实现类型。服务类型可以是类,也可以是接口。但是尽量服务类型用于接口
在.net控制反转组件取名为Dependencylnjection(依赖注入),它包含ServiceLocator的功能
(简称DI)
在使用Dependencylnjection之前需要去安装它的包
(Install-Package Microsoft.Extensions.DependencyInjection)
然后通过using Microsoft.Extensions.Dependencyinjectior去使用
using Microsoft.Extensions.DependencyInjection; namespace djiso class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); services.AddTransient<CC>(); //AddScoped 添加范围服务 //AddSingleton 添加单列服务 //AddTransient 添加瞬态服务 using (ServiceProvider sp = services.BuildServiceProvider()) //ServiceProvider===>服务定位器 { CC t = sp.GetService<CC>(); t.Name = "SDY"; t.SayHi(); } /*TestSericeImple sdy = new TestSericeImple(); sdy.Name = "飞猪"; sdy.SayHi();*/ Console.Read(); } } public interface LL { public string Name { get; set; } public void SayHi(); } public class CC : LL { public string Name { get; set; } public void SayHi() { Console.WriteLine($"Hi,I.m{Name}"); } } }
总结:首先通过上方的代码,这是通过控制反转来去进行一个服务的调用,首先会创造一个ServiceCollection的容器,用来去存放后面需要注册的服务。然后注册服务(Add),然后再创建一个ServiceProvider(这个相当于就是一个服务定位器,记住一定要使用using去进行创建,不然会出现内存泄漏),后面就向这个服务定位器要一个服务,就可以拿到对象然后就可以调用对象的方法
服务的生命周期(知道是什么就行了)
在依赖注入中,服务的生命周期是指该服务的实例在容器中的生存周期。常见的生命周期有三种:单例(Singleton)、瞬时(Transient)和作用域(Scoped)
-
单例生命周期
:当一个对象被注册为单例时,容器只会创建一个该类型的实例,并返回该实例给所有使用该服务的请求者。单例生命周期的对象会一直存在于容器中,除非容器被销毁或者该服务被显式地注销。单例生命周期适用于那些需要保持状态的对象,比如共享同一份数据的配置文件等。 -
瞬时生命周期
:每次请求该服务时,容器都会创建一个新的实例,并返回该实例给请求者。当请求结束时,实例也会随之被销毁。瞬时生命周期适用于那些无状态的服务,比如一个简单的计算器。 -
作用域生命周期
:当一个对象被注册为作用域生命周期时,容器会在特定作用域内(例如HTTP请求、用户会话等)创建一个该类型的实例,并返回该实例给所有使用该服务的请求者。当该作用域结束时,实例也会随之被销毁。作用域生命周期适用于那些需要在某个特定范围内共享状态的对象,比如一个数据库连接池。
服务注册的其他方法
其他的Add方法
using Microsoft.Extensions.DependencyInjection; namespace dsadsa { class Program { static void Main(string[] args) { ServiceCollection services = new ServiceCollection(); services.AddScoped<Ifo, dpinne>(); services.AddSingleton<Ifo, dpinne>(); //跟上面那个一样的效果 //它有两个类型 一个是服务的类型,一个是实现的类型 using(ServiceProvider a = services.BuildServiceProvider()) //也可以写成 var xx == services.BuildServiceProvider() { Ifo tsl = a.GetService<Ifo>(); tsl.name = "GAME"; tsl.running(); //目前来说我不用去管实现类是谁,我只需要拿到这个服务去使用这个服务就可以了 } Console.Read(); } } interface Ifo { public string name { set; get; } public void running(); } public class dpinne:Ifo { public string name { get; set; } public void running() { Console.WriteLine("跑起来"+name); } } }
ServiceProvider的服务定位器方法
1、T GetService<T>() 如果获取不到对象,则返回null
2、object GetService(Type serviceType)
3、GetRequiredService<T>()如果获取不到对象,则抛异常
4、IEnumerable<T> GetServices<T>()适用于可能有很多满足条件的服务
总结上面的方法了解一下就可以了 一般来说 GetService就以及足够我们进行日常的开发了
真正的依赖注入
总结 :首先我们是创造出了多个服务(接口)和实现类,如果我们想要让一个实现类在实现自己本身服务的基础上,可以实现其他服务,那么我们就可以通过依赖注入的方式去实现
依赖注入:在使用依赖注入之前,需要将想要注入的对象赋值给类的私有成员,然后再在构造函数或者其他方法进行依赖注入。 详细请看下方代码
using Microsoft.Extensions.DependencyInjection; using System.Net; namespace yilai { class Program { static void Main(string[] args) { ServiceCollection service = new ServiceCollection(); service.AddScoped<Controller>(); service.AddScoped<lLog, LogImpl>(); service.AddScoped<IConfig, ConfigImple>(); service.AddScoped<IStorage, StorageImp>(); using (var sp = service.BuildServiceProvider()) { var c = sp.GetService<Controller>(); c.Test(); } } } class Controller { //在使用依赖注入之前,需要将注入的对象赋值给类的私有成员 //然后再在构造函数或者其他方法进行依赖注入 //然后就可以使用注入依赖的服务 private lLog log; private IStorage storage; public Controller(lLog log, IStorage storage) { this.log = log; this.storage = storage; } public void Test() { log.Log("开始上传"); this.storage.Save("sdada", "1.txt"); log.Log("上传结束"); } } //依赖注入的开始 interface lLog { public void Log(string msg); } class LogImpl : lLog { public void Log(string msg) { Console.WriteLine("日志" + msg); } } interface IConfig { public string GetValue(string name); } class ConfigImple : IConfig { public string GetValue(string name) { return "hello"; } } interface IStorage { public void Save(string content,string name); } class StorageImp : IStorage { //在使用依赖注入之前,需要将注入的对象赋值给类的私有成员 //然后再在构造函数或者其他方法进行依赖注入 private IConfig config; //这里是在StorageImp的构造函数中通过依赖注入的方式把IConfig接口注入到里面, //这样就可以使用StorageImp的同时使用IConfig public StorageImp(IConfig config) { this.config = config; } public void Save(string content, string name) { string server = config.GetValue(name); Console.WriteLine($"服务器{server}的文件名为{name}上传{content}"); } } }
DI综合案例:
需要说明:
1、演示DI的能力
2、有配置服务、日志服务,然后再开发一个邮件发送器服务。可以通过配置服务来从文件、环境变量、数据库等地方读取配置,可以通过日志服务来将程序运行过程中的日志信息写入文件、控制台、数据库等。
3、说明: 案例中开发了自己的日志、配置等接口,这只是在揭示原理,.NET有现成的,后面讲。
开始之前首先对项目进行模块划分:ConfigServices是配置服务的项目 、 LgoServices是日志服务项目、MailServices是邮件发送项目、MailServicesConsole为输入
配置系统(就是用来去读取文件信息的一种方法)
配置系统用于存储和访问应用程序的配置数据。配置数据可以包括应用程序的设置、连接字符串、日志级别等信息。C#提供了多种方式来实现配置系统
1、首先是JSON文件的配置建立一个JSON后缀名的文件
,然后讲将JSON文件的设置更改成为如果较新规则复制
2、安装Instal1-Package Microsoft.Extensions.Configuration.Json
(读取JSON文件包)
和Insta11-Package Microsoft.Extensions.Configuration
(配置框架的包)
一个最基本的读取JSON文件的配置系统方法(很古老的方法) static void Main(string[] args) { ConfigurationBuilder confige = new ConfigurationBuilder(); confige.AddJsonFile("json1.json"); IConfigurationRoot configurationRoot = confige.Build(); string name = configurationRoot["name"]; Console.WriteLine($"name={name}"); string add = configurationRoot.GetSection("Proxy:address").Value; Console.WriteLine($"addr={add}"); Console.ReadKey(); }
EF Core(ORM使用框架EF Core/其实还有另外一款框架Dapper)
1、什么是ORM(开发者通过对象来去操作关系数据库)
有哪些ORM(EF Core,Dapper,Sql 等) 其实相当于就是把Sql使用一个实例化对象来去操作
对象数据库:就是我把数据使用一个对象保存到数据库里面去,然后如果我想要的话就调用那个对象就行了
2、ORM的使用
就好比如:我想要往数据表中存储一个数据 使用sql语句: insert into user(name,password)values('admin','123456'); 但是如果我使用ORM的话: User user = new User(){Name="admin",PassWord="123456"}; orm.save(user); //new一个对象,然后通过linq语句实现存储数据的功能,然后通过orm把这个对象存储 然后到时候调用直接调用对象就完事了(把new出来的对象转换成一个insert语句然后扔给数据库来执行,orm可以说就是一个转换器)
3、EF Core的开发环境安装,以及数据库链接
//使用SqlServer数据库 通过资源管理控制台:Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 5.0.14 (因为现在我使用的是net6 所以必须要安装指定版本的安装包不然会安装失败) optionsBuilder.UseSqlServer("Server=DESKTOP-DGSNR9I\\SQLEXPRESS;Database=Demo2;Trusted_Connection=True;MultipleActiveResultSets=true"); //使用Mysql数据库 Install-Package Pomelo. EntityFrameworkCore.MySql-Version 5.0.14 optionsBuilder.UseMysql("server=localhost; user=root; password=root; databaes=ef", new MysqlServerVersion(new Version(5,6,0)));
创建实现了IEntityTypeConfiguration接口实体配置类,配置实体类和数据库表的对应关系
BookConfig.cs class BookConfig: IEntityTypeConfiguration<Book> { public void Configure(EntityTypeBuilder<Book> builder) { builder.ToTable("T_Book) //这里是创建一个T_Book的表 这个表对应Book这个实例 } } Book.cs [Table("Books")] //直接加上这一句话上面的BookConfig.cs文件就不需要创建了 public class Book { public long Id { get; set; } public string Title { get; set; } public DateTime PubTime { get; set; } public double Price { get; set; } } MyDbContext.cs(可以把它当成是一个链接数据库的一个配置文件) public class MyDbContext:DbContext { public DbSet<Book> Books { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlServer ("Server=DESKTOP-DGSNR9I\\SQLEXPRESS;Database=Demo2;Trusted_Connection=True;MultipleActiveResultSets=true"); 其中Server=这个地方是写你Server数据库的名称,如果你Server不是直接win登录就要后面写上password和user //这是一个链接数据库的字符串语句 } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //从当前程序集加载所有的IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); //获得当前这个项目的程序集 } } //安装包直接双击项目 然后把 <ItemGroup> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.14" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> 放在</PropertyGroup>的下面就行咯 不用再去控制条命令安装那么麻烦
4、Migration的安装
这个时候有一个比较重要的概念出现了,Migration数据库迁移
(在ORM中数据库并不是我们自己手动创建的,而是由Migration工具生成的,通过Migration可以进行对数据库的自动更新)
首先要安装Microsoft.EntityFrameworkCore.Tools,不然直接执行Add-Migration会报错 (其他是有图形化界面让你直接找的别为难自己好吧) 但是安装这个东西 如果创建的项目是net6为基础(不过现在基本都是高版本为主)就用下面这一句 Install-Package Microsoft.EntityFrameworkCore.Tools -Version 5.0.14 安装好上面这个包之后 再执行(生成一次迁移) Add-Migration (InitialCreate/ 这里写的是创建迁移的名字) 创建好之后会出现Migrations文件夹,里面的子文件里的代码是创建数据库表用C#代码去编写的 ,仔细看一下就知道了 然后通过Update-database 来去跟新/创建数据表 然后后续比如说你想要添加多一张表,修改什么东西的数据搞好之后再去add -> update就行了
5、Ef Core有关于CURD的实现
总结:在EF Core 中CURD的实现都是看你的Linq语句熟不熟悉,其余的都基本差不多
Program.cs //插入数据 static async Task Main(string[] args) { //ctx => 可以说是逻辑上的数据库 using(MyDbContext ctx = new MyDbContext()) { Dog d = new Dog(); d.Name = "云曦"; ctx.Add(d); //这个行为就是把d这个对象添加进逻辑上的表里面 //ctx.SaveChanges();//这个就是相当于update-database 把上面这个添加的行为同步到数据库里面 await ctx.SaveChangesAsync(); //习惯去使用异步方法的形式对于大体程序上有好处 } } //查询数据 using(MyDbContext ctx = new MyDbContext()) { Dog d = new Dog(); var Dogs = ctx.Dogs.Where(e=> e.Id<4); //直接在EF Core中使用linq语句 e=>e.id<4 foreach(var dd in Dogs) { Console.WriteLine(dd.Name); } //更新和删除也差不多的 using(MyDbContext ctx = new MyDbContext()) { Dog d = new Dog(); // 添加必需的属性值 var a = ctx.Dogs.Single(e=>e.Id>3 &&e.Id<1); a.Name = "帝后云曦!!"; var b = ctx.Dogs.Single(e => e.Name == "荒天帝"); ctx.Dogs.Remove(b); await ctx.SaveChangesAsync();
6 EF Core中的数据配置方式 /主键方法
第一种:Fluebt API
builder.ToTable("T_Book"); //这里是创建一个T_Book的表 这个表对应Book这个实例 builder.Property(b=>b.Title).HasMaxLength(50).IsRequired(); builder.Property(b=>b.BookFrom).HasMaxLength(20).IsRequired(); 相对来说就是一个比较简介简短一些
第二种:Data Annotation
Person.cs namespace EfMigration { [Table("T_Person")] public class Person { public long Id { get; set; } [Required] [MaxLength(29)] public string Name { get; set; } } } //这种写法更加的清晰,看个人的习惯把
主键方法
这个东西就是一个比较平常的东西,就是在我们定义主键的时候使用一下Guid算法上去就行了,
Guid算法作为主键ID不会重复挺好的 到时候真干起项目来的时候就指定一下Guid算法作为主键就完事了
Dog d = new Dog(); // 添加必需的属性值 d.Id = Guid.NewGuid(); d.Name = "YZK";
7、EF Core反向工程(根据以有的数据库 根据以有的数据库反向生成EF Core代码)
//在控制台上面 Scaffold-DbContextServer=.:Database=YZKHR:Trusted Connection=True:MultipleActiveResultSets=trueMicrosoft.EntityFrameworkCore.SqlServer 如果出现错误那就是Migration的那两个包没有下载,直接搞一下那2个包就完事了
8、EF Core关系之间的配置
EF Core中关系之间配置都是有套路的 (这个东西就外键,把他想像成是数据表之后的一个关联字节外键就行了) 一对多:HasOne(..).WithMany(..) 一对一:HasOne(..).withOne(..) 多对多:HasMany(..).WithMany(..) ShiHao.cs public long Id { get; set; } public YunXi TheYunXi { get; set; } public string Message { get; set; } YunXi.cs public long Id { get; set; } public string Title { get; set; } public string Message { get; set; } public List<ShiHao> ShiHao { get; set; }=new List<ShiHao>(); **一对多** ShiHaoConfig.cs public void Configure(EntityTypeBuilder<ShiHao> builder) { builder.ToTable("World_Shi"); builder.Property(a => a.Message).IsUnicode().IsRequired(); builder.HasOne<YunXi>(c => c.TheYunXi).WithMany(a=>a.ShiHao).IsRequired(); //感觉也没什么HasOne(..).WithMany(..) } Program.cs static void Main(string[] args){ using(MyDbContext ctx = new MyDbContext()) { YunXi a1 = new YunXi(); a1.Title = "帝后云曦"; a1.Message = "根据报导....."; ShiHao c1 = new ShiHao { Message="太漂亮了",TheYunXi=a1}; ShiHao c2 = new ShiHao { Message = "只能说完美的建模都很好",TheYunXi = a1 }; //上面为什么这样写看之前设定的YunXi类型的变量.直接把这个变量指定是那个就不用在下面写Add了 ctx.yunXis.Add(a1); /*a1.ShiHao.Add(c1); a1.ShiHao.Add(c2);*/ ctx.SaveChanges(); } }
8.1一对一关系数据的获取
通过Include(c=>c.??)去获取不然就拿不到
ShiHao cmt = ctx.shiHao.Include(c=>c.TheYunXi).Single(c=>c.Id==3); 记住在一对一关系中必须显式的在其中一个实体类中声明一个外键属性 class Delivery { public long Id { get; set; } public string CompanyName { get; set; } public string Number { get; set; } public Order Order1 { get;set; } public long OrderId { get; set; } } class Order { public long Id { get; set; } public string Name { get; set; } public string Address { get; set; } public Delivery Delivery1 { get; set; } //我把这个声明的外键属性写在了Order中 } class OrderConfig : IEntityTypeConfiguration<Order> { public void Configure(EntityTypeBuilder<Order> builder) { builder.ToTable("T_table"); builder.HasOne<Delivery>(e => e.Delivery1). WithOne(e => e.Order1).HasForeignKey<Delivery>(e => e.OrderId); //其中就是我通过HasForeignKey这个方法把Delivery中的OrderId指定成外键属性 //就是相当于我Delivery1这个属性 指向与Order1属性形成一对一的关系 } } static void Main(string[] args) { using(MyDbContext ctx = new MyDbContext()) { Order order = new Order(); order.Name = "书"; Delivery d1 = new Delivery(); d1.CompanyName = "顺丰快递"; d1.Number = "S1mple01"; d1.Order1 = order; ctx.Orders.Add(order); ctx.Deliverys.Add(d1); //我存放d1就可以拿到d1中的数据 同时因为外键Order1的原因所以可以拿到orderName这个属性 ctx.SaveChanges(); } }
8.2多对多配置关系
class Student { public long Id { get; set; } public string Name { get; set;} public List<Teacher> Teachers { get; set; } = new List<Teacher>(); } class Teacher { public long Id { get; set; } public string Name { get; set; } public List<Student> Students { get; set; } = new List<Student>(); } class TeacherConfig : IEntityTypeConfiguration<Teacher> { public void Configure(EntityTypeBuilder<Teacher> builder) { builder.ToTable("Teacher"); builder.HasMany<Student>(e => e.Students).WithMany(t => t.Teachers). //其中UsingEntity是配置中间关系表的一个api UsingEntity(j => j.ToTable("stu_tea")); //老师有需要多学生,学生们对应着老师 他们直接的关系我用表记录了起来 } } //这种关系之间的配置表 建一个就行了 class StudentConfig : IEntityTypeConfiguration<Student> { public void Configure(EntityTypeBuilder<Student> builder) { builder.ToTable("student"); } } using(MyDbContext ctx = new MyDbContext()) { /* Student s1= new Student { Name = "云曦" }; Student s2 = new Student { Name = "月禅" }; Student s3 = new Student { Name = "清漪" }; Teacher t1 = new Teacher { Name = "Tom" }; Teacher t2 = new Teacher { Name = "Jerry" }; Teacher t3 = new Teacher { Name = "Men" }; s1.Teachers.Add(t1); s1.Teachers.Add(t2); s2.Teachers.Add(t2); s2.Teachers.Add(t3); s3.Teachers.Add(t1); s3.Teachers.Add(t2); s3.Teachers.Add(t3); ctx.teachers.Add(t1); ctx.teachers.Add(t2); ctx.teachers.Add(t3); ctx.students.Add(s1); ctx.students.Add(s2); ctx.students.Add(s3); ctx.SaveChanges();*/ var Student = ctx.students.Include(t => t.Teachers); foreach(var t in Student) { Console.WriteLine(t.Name); foreach(var s in t.Teachers) { Console.WriteLine($"{s.Name}"); } }
8.3单项导航属性
双向导航其实就是2个实体类可以相互的去引用或者是访问对方的数据
单项导航其实就只有一方单方面的可以去引用或者访问相关联的数据(这个用的比较多)
但是单项导航有一个关键点,需要去配置反向的配置属性,就好比一方可以直接找到你,但是被找的另一方甚至连对方是谁都不知道,所以就需要配置反向属性
//单项导航 //配置反向属性,像Leave知道User的存在也可以使用它里面的数据、 //但是User连Leave这个实体在哪里都不知道所以才要去配置反向属性 public class Leave { public long Id { get; set; } public User Requester { get; set; } public User Approver { get; set; } public string Remarks { get; set; } } public class User { public long Id { get; set; } public string Name { get; set; } } //但是注意:配置的方法是不设置反向的属性,直接去配置的时候WithMany()不设置参数的可以了 class LeaveConfig : IEntityTypeConfiguration<Leave> { public void Configure(EntityTypeBuilder<Leave> builder) { builder.ToTable("T_leave"); builder.HasOne<User>(e => e.Requester).WithMany().IsRequired(); builder.HasOne<User>(e=>e.Approver).WithMany(); } }
9、lEnumerable和lQueryable的区别
第一点:延迟执行 vs. 即时执行
IEnumerable
是用于表示在内存中的集合,它执行的时候会立即从内存中获取集合的所有数据(客户端评估)。而 IQueryable
则是用于表示一个查询,它在执行时会将查询转化为特定的查询语言(如 SQL)并发送到数据库执行(服务器评估),也就是说,它支持延迟执行。
第二点:查询能力
IQueryable
比 IEnumerable
更强大,因为它提供了更多的查询操作符(例如 Where
、OrderBy
、GroupBy
等),这些操作符可以在查询中进行转换和优化,以提高查询性能。
第三点数据类型
IEnumerable
可以表示任何实现了 IEnumerable<T>
接口的对象,例如内存中的集合、数组等。而 IQueryable
通常表示与数据库进行交互的查询,例如 Entity Framework 提供的 DbSet<T>
。
第四点表达能力
IQueryable
可以让我们运行时动态地构建查询。而 IEnumerable
只能通过 LINQ 方法链或查询表达式来构建查询,这些查询会被编译为静态方法调用。
//使用的写法 IEnumerable<Student> students = ctx.students; IEnumerable<Student> sdy = students.Where(e => e.Name.Contains("云")); IQueryable<Student> sdy = ctx.students.Where(e => e.Name.Contains("云")); //功能实现其实是差不多的但是性能是一个天一个地 //而且通过日志输出,IQ用的是sql语句的查询 IE是用link语句查询 //只能说EF Core 操作能用IQ就用IQ 尽量不要去使用IE毕竟性能也是很关键的
9.1lQueryable
lQueryable它只是代表一个可以放到数据库服务器里去执行查询,它并没有立刻执行(不遍历就不执行)
lQueryable接口调用非终结方法的时候它不会去执行查询,反之立刻执行
如果一个方法的返回值是lQueryable类型那么这个方法一般就是非终结方法不然就是终结方法
lQueryable是一个等待执行的逻辑,只要我们不用终结的方法去执行那么它就可以重复使用s
终结方法; 遍历、ToArray()、ToList、Min、Max、Count... 非终结方法:GroupBy()、OrderBy()、Include、skip、Take... 重点:lQueryable的多重动态查询 static void Main(string[] args) { using (MyDbContext ctx = new MyDbContext()) { Quer("云",true,true,59); //我从主入口方法给出查询的条件 然后程序通过执行去得到IQ的sql语句 } } static void Quer(string Words, bool serachAll,bool orderBy, double Unppet) { using(MyDbContext ctx = new MyDbContext()) { //我使用IQueryable的多重动态查询 IQueryable<Student> say = ctx.students; if (serachAll) { say = say.Where(a => a.Name.Contains("云") || a.Name.Contains("漪")); //serachAll为真的时候我就查询关键字云和漪 } else { say = say.Where(a => a.Name.Contains("云")); } if (orderBy) { say = say.OrderBy(a => a.Name); } foreach (Student student in say) { ... } 在遍历的开始之前我设定好每个属性的有关的判断条件,然后再去让以Ture or false 来去动态的实现这些条件;
9.2lQueryable的分页查询功能
分页实现的关键点就是在于满足条件的数据的总共数和lQueryable的复用 其中使用Skip(3).Take(8) //跳过多少条数据.取多少条数据 static void PrintPage(int pageIndex, int pageSize) { //pageIndex页码(从1开始) pageSize一页里面有多少数据 using(MyDbContext ctx = new MyDbContext()) { IQueryable<Student> std = ctx.students.Where(e => e.Id >= 3); var items = std.Skip((pageIndex - 1)*pageSize).Take(pageSize); //这句话的逻辑就是 按我们来说是从0开始 当用户想要第一页的时候那个pageIndex的数据就是为1 //1-1=0 0*pageSize=0 所以一开始跳过第0条数据获取到1-3的数据 //当用户想要第二页的时候 pageIndex就是2 2-1=1 *pageSize //因为我上面条件的设计是获取3条的 所以 跳过前面3条数据(1,2,3) 获取后3条的数据(4,5,6) foreach(var a in items) { Console.WriteLine(a.Name); } long cout =std.LongCount(); long pageCout = (long)Math.Ceiling(cout * 1.0 / pageSize); Console.WriteLine("总页数" + pageCout); } } static void Main(string[] args) { using (MyDbContext ctx = new MyDbContext()) { PrintPage(1,3); } }
9.3lQueryable如何一次性的加载数据到内存
一次性加载数据到内存里用:lQueryable中的ToArray、toArrayAsync、Tolist tolistAsync...方法
一般来说如果是数据比较庞大的情况下或者是处理的速度比较慢就会用到这种一次性的方法
我测试过如果是数据比较小的情况下其实执行的速度都是差不多的
`还有一种使用到的情况是:多个lQueryable的遍历嵌套
foreach(var a in ctx.students.ToList()) { Console.WriteLine(a.Name); Thread.Sleep(10); }
9.4EF Core中的异步方法
这东西没什么好说的,能用异步方法就用异步方法这样对应我们整体项目的并发是有一点优化的
using (MyDbContext ctx = new MyDbContext()) { Student std = new Student { Name="栖霞"}; await ctx.AddAsync(std); await ctx.SaveChangesAsync(); }
10、执行原生的查询Sql语句的情况
当执行非查询语句相关的原生sql语句的时候
Database.ExecuteSqlInterpolatedAsync(字符拼接的形式来传参) using (MyDbContext ctx = new MyDbContext()) { await ctx.Database.ExecuteSqlInterpolatedAsync(@$"insert into student(Name) select{name}from student"); }
执行实体相关的查询原生sql语句
执行原生Sql语句是一个查询语句 而且查询的结构是对应一个实体的就可以去调用Dbset中的FromSqlInterpolated去执行查询语句
static void Main(string[] args) { string studentName = "%余%"; using (MyDbContext ctx = new MyDbContext()) { var queryable = ctx.students.FromSqlInterpolated($@"select * from student where Name like {studentName}"); foreach (var a in queryable) { Console.WriteLine(a.Id + " " + a.Name); } ctx.SaveChanges(); } }
但是使用FromSqlInterpolated去查询就只能单表查询 虽然可以使用include去进行多个表但是还是有局限性的
11、EFCore中的全局筛选器
首先了解一下什么是软删除,软删除通俗一点理解就是相当于回收站一样,本来有一条数据 它是展示出来的但是突然想让这个数据进行一个删除,但是删除之后还能找回来(主观页面上看不到那条数据但是其实数据库里面那条数据还是在的)
为了实现这个软删除 给数据添加了一个Bool类型的IsDeleted (以true or false 来去对数据的软删除) var a = ctx.students.Single(a=>a.Id==2); a.IsDeleted = true; //一开始用户指定那一条数据删除 我们就将这条数据的IsDeleted设为1 foreach (var item in ctx.students.Where(e=>e.IsDeleted==false).Take(3)) //虽然我们把它设为了1 但是数据库中这条数据还是存在的 所以我们在查询所有数据的时候 //需要添加多一条附加条件e=>e.IsDeleted==false { Console.WriteLine(item.Id+""+item.Name); }
问题来了,如果我们每次进行查询都要附加写这个条件就很麻烦 为了处理这个问题,我们开始一开始就配置一个全局的条件
studentConfig.cs class StudentConfig : IEntityTypeConfiguration<Student> { public void Configure(EntityTypeBuilder<Student> builder) { builder.ToTable("student"); => builder.HasQueryFilter(a => a.IsDeleted == false); } }
EFCore进行数据迁移时候出现Unable to create an object of type 'MyDbContext的处理方式
首先创建一个DbContextDesignTimeFactory.cs public class DbContextDesignTimeFactory : IDesignTimeDbContextFactory<MyDbContext> { public MyDbContext CreateDbContext(string[] args) { DbContextOptionsBuilder<MyDbContext> bulider = new DbContextOptionsBuilder<MyDbContext>(); bulider.UseSqlServer("Server=“”;Database=Demo1;Trusted_Connection=True;MultipleActiveResultSets=true"); return new MyDbContext(bulider.Options); } } 然后进行一下数据库迁就好了