当前位置: 首页 > news >正文

02020507 EF Core高级07-悲观并发控制、乐观并发控制、EF Core连接MySQL、RowVersion

02020507 EF Core高级07-悲观并发控制、乐观并发控制、EF Core连接MySQL、RowVersion

1. EF Core悲观并发控制(3-38)

1.1 并发控制的概念
1、并发控制:避免多个用户同时操作资源造成的并发冲突问题。举例:统计点击量。
2、最好的解决方案:非数据库解决方案。
3、数据库层面的两种策略:悲观、乐观。
1.2 悲观并发控制
1、悲观并发控制一般采用行锁、表锁等排他锁对资源进行锁定,确保同时只有一个使用者操作被锁定的资源。
2、EF Core没有封装悲观并发控制的使用,需要开发人员编写原生SQL语句来使用悲观并发控制。不同数据库的语法不一样。

2. MySQL悲观并发控制

class House
{public long Id { get; set; }public string Name { get; set; }public string Owner { get; set; }
}
MySQL方案: select * from T_Houses where Id=1 for update
如果有其他的查询操作也使用for update来查询Id=1的这条数据的话,那些查询就会被挂起,一直到针对这条数据的更新操作完成从而释放这个行锁,代码才会继续执行。
2.1 安装MySQL数据库
  • 参考04020101小结
2.2 新建数据库
图片链接丢失
2.3 创建项目并创建数据表
  • 创建.NET Core控制台应用程序PessimisticDemo
// 安装MySQL的依赖包
1. install-package pomelo.entityframeworkcore.mysql -version 5.0.0
2. install-package microsoft.entityframeworkcore.design -version 5.0.7
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// House.cs
namespace PessimisticDemo
{class House{public long Id { get; set; }public string Name { get; set; }public string Owner { get; set; }}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// HouseConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace PessimisticDemo
{class HouseConfig : IEntityTypeConfiguration<House>{public void Configure(EntityTypeBuilder<House> builder){builder.ToTable("T_Houses");builder.Property(h => h.Name).IsRequired(); // Name属性不为空}}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PessimisticDemo
{class MyDbContext : DbContext{public DbSet<House> Houses { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){base.OnConfiguring(optionsBuilder);var connStr = "server=localhost; user = root; password = 123456; database = mysqldemo";var serverVersion = new MySqlServerVersion(new Version(5, 7, 36));optionsBuilder.UseMySql(connStr, serverVersion);// optionsBuilder.LogTo(Console.WriteLine);}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);}}
}    
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 迁移数据库
PM> add-migration init
Build started...
Build succeeded.
The Entity Framework tools version '5.0.4' is older than that of the runtime '5.0.7'. Update the tools for the latest features and bug fixes.
To undo this action, use Remove-Migration.
PM> update-database
Build started...
Build succeeded.
The Entity Framework tools version '5.0.4' is older than that of the runtime '5.0.7'. Update the tools for the latest features and bug fixes.
Applying migration '20251006064559_init'.
Done.
PM> 
  • 查看如下数据表并添加数据
图片链接丢失
2.4 实现(会有并发问题)
1、锁是和事务相关的,因此通过BeginTransactionAsync()创建一个事务,并且在所有操作完成后调用CommitAsync()提交事务。
2、var h1 = await ctx.Houses.FromSqlInterpolated($"select * from T_Houses where Id=1 for update").SingleAsync();
3、定位到编译完成的exe目录下,运行两个exe程序的实例,分别输入姓名tom和jim。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
using System;
using System.Linq;
using System.Threading;namespace PessimisticDemo
{class Program{static void Main(string[] args){Console.WriteLine("请输入您的名字:");string name = Console.ReadLine();using (MyDbContext ctx = new MyDbContext()){var h01 = ctx.Houses.Single(h => h.Id == 1);if(!string.IsNullOrEmpty(h01.Owner)){if(h01.Owner == name){Console.WriteLine("房子已经被抢到了");}else{Console.WriteLine("房子已经被[" + h01.Owner + "]占了");}return;}h01.Owner = name;Thread.Sleep(10000); // 延时,造成两次输入都占用Console.WriteLine("恭喜你,房子抢到了");ctx.SaveChanges();Console.ReadKey();}}}
}
  • 运行生成的.exe文件,使用2个窗口。等待之后都会显示“房子已经被抢到了"
图片链接丢失
  • 等待10s之后的输出结果
图片链接丢失
  • 查看数据表
"Id"	"Name"	"Owner"
"1"	"1-1-502"	"tom"
"2"	"1-1-101"	
"3"	"2-1-101"	说明:程序显示2个都抢到了,但是数据库中只有tom抢到了。这里就会产生并发的问题。
2.5 实现(无并发问题)
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;namespace PessimisticDemo
{class Program{static void Main(string[] args){Console.WriteLine("请输入您的名字:");string name = Console.ReadLine();using (MyDbContext ctx = new MyDbContext())using(var tx = ctx.Database.BeginTransaction()) // step1 先启动事务{// var h01 = ctx.Houses.Single(h => h.Id == 1);var h01 = ctx.Houses.FromSqlInterpolated($"select * from houses where Id =1 for update").Single(); // step3 加锁if (!string.IsNullOrEmpty(h01.Owner)){if(h01.Owner == name){Console.WriteLine("房子已经被抢到了");}else{Console.WriteLine("房子已经被[" + h01.Owner + "]占了");}return;}h01.Owner = name;Thread.Sleep(10000); // 延时,造成两次输入都占用Console.WriteLine("恭喜你,房子抢到了");ctx.SaveChanges(); // step4 解锁tx.Commit(); // step2 提交事务Console.ReadKey();}}}
}
  • 继续2.4中操作,查看数据表
"Id"	"Name"	"Owner"
"1"	"1-1-502"	"tom"
"2"	"1-1-101"	
2.6 悲观并发控制的缺点
1、悲观并发控制的使用比较简单;
2、锁是独占、排他的,如果系统并发量很大的话,会严重影响性能,如果使用不当的话,甚至会导致死锁。
3、不同数据库的语法不一样。

3. 乐观并发控制(视频3-39)

3.1 乐观并发控制思路
// 对tom来说
update houses set owner = 'tom' where id = 1 and owner = ''说明:当owner值为空,当影响行数1。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 对jerry来说
update houses set owner = 'jerry' where id = 1 and owner = ''说明:当owner值不为空,影响行数为0。此时会报错:并发修改失败。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
综上:上述是乐观并发控制原理
3.2 乐观控制并发原理
Update T_Houses set Owner=新值
where Id=1 and Owner=旧值
举例子。当Update的时候,如果数据库中的Owner值已经被其他操作者更新为其他值了,那么where语句的值就会为false,因此这个Update语句影响的行数就是0,EF Core就知道“发生并发冲突”了,因此SaveChanges()方法就会抛出DbUpdateConcurrencyException异常。
3.3 乐观并发控制EF Core配置
1、把被并发修改的属性使用IsConcurrencyToken()设置为并发令牌。
2、builder.Property(h => h.Owner).IsConcurrencyToken();
3、catch(DbUpdateConcurrencyException ex)
{var entry = ex.Entries.First();var dbValues = await entry.GetDatabaseValuesAsync();string newOwner = dbValues.GetValue<string>(nameof(House.Owner));Console.WriteLine($"并发冲突,被{newOwner}提前抢走了");
}
3.4 乐观并发控制示例
  • 数据库中信息
图片链接丢失
  • 新建项目:OptimisticDemo
// House.cs
namespace OptimisticDemo
{class House{public long Id { get; set; }public string Name { get; set; }public string Owner { get; set; }}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// HouseConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace OptimisticDemo
{class HouseConfig : IEntityTypeConfiguration<House>{public void Configure(EntityTypeBuilder<House> builder){builder.ToTable("T_Houses");builder.Property(h => h.Name).IsRequired(); // Name属性不为空builder.Property(h => h.Owner).IsConcurrencyToken(); // 设置并发令牌}}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace OptimisticDemo
{class MyDbContext : DbContext{public DbSet<House> Houses { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){base.OnConfiguring(optionsBuilder);var connStr = "server=localhost; user = root; password = 123456; database = mysqldemo";var serverVersion = new MySqlServerVersion(new Version(5, 7, 36));optionsBuilder.UseMySql(connStr, serverVersion);optionsBuilder.LogTo(Console.WriteLine); // 查看日志}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);}}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// OptimisticDemo.csproj
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net5.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="microsoft.entityframeworkcore.design" Version="5.0.7"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference><PackageReference Include="pomelo.entityframeworkcore.mysql" Version="5.0.0" /></ItemGroup></Project>
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;namespace OptimisticDemo
{class Program{static void Main(string[] args){Console.WriteLine("请输入您的名字:");string name = Console.ReadLine();using (MyDbContext ctx = new MyDbContext()){var h01 = ctx.Houses.Single(h => h.Id == 1);if (!string.IsNullOrEmpty(h01.Owner)){if (h01.Owner == name){Console.WriteLine("房子已经被抢到了");}else{Console.WriteLine("房子已经被[" + h01.Owner + "]占了");}Console.ReadKey();return;}h01.Owner = name;Thread.Sleep(10000);Console.WriteLine("恭喜你,房子抢到了");try{ctx.SaveChanges();}catch (DbUpdateConcurrencyException ex){Console.WriteLine($"并发冲突");}Console.ReadLine();}}}
}
  • 结果演示
图片链接丢失
  • 查看数据库信息
图片链接丢失
3.5 查看并发冲突的值
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;namespace OptimisticDemo
{class Program{static void Main(string[] args){Console.WriteLine("请输入您的名字:");string name = Console.ReadLine();using (MyDbContext ctx = new MyDbContext()){var h01 = ctx.Houses.Single(h => h.Id == 1);if (!string.IsNullOrEmpty(h01.Owner)){if (h01.Owner == name){Console.WriteLine("房子已经被抢到了");}else{Console.WriteLine("房子已经被[" + h01.Owner + "]占了");}Console.ReadKey();return;}h01.Owner = name;Thread.Sleep(10000);Console.WriteLine("恭喜你,房子抢到了");try{ctx.SaveChanges();}catch (DbUpdateConcurrencyException ex){Console.WriteLine($"并发冲突");var entry1 = ex.Entries.First();string newValue = entry1.GetDatabaseValues().GetValue<string>("Owner");Console.WriteLine("被[" + newValue + "]抢先了");}Console.ReadLine();}}}
}
  • 结果演示
图片链接丢失
3.6 乐观并发总结
1、首先把数据库中Id=1这一行数据中的Owner列的值清空,然后仍然像上一节一样运行两个exe程序的实例,分别输入姓名tom和jim。
2、使用乐观并发控制是在更新的时候检查一下,因此并没有性能问题,并不会产生死锁等问题。
3、开发中推荐使用乐观并发,而不用悲观的锁来实现。

4. 乐观并发控制:RowVersion(视频3-40)

4.1 如果有很多字段都有并发控制
// SQLServer的RowVersion
1、SQLServer数据库可以用一个byte[]类型的属性做并发令牌属性,然后使用IsRowVersion()把这个属性设置为RowVersion类型,这样这个属性对应的数据库列就会被设置为ROWVERSION类型。对于ROWVERSION类型的列,在每次插入或更新行时,数据库会自动为这一行的ROWVERSION类型的列生成新值。
2、在SQLServer中,timestamp和rowversion是同一种类型的不同别名而已。
3、RowVersion是SQL Server特有,其它数据库貌似没有。
4.2 RowVersion原理
class House
{public long Id { get; set; }public string Name { get; set; }public string Owner { get; set; }public byte[] RowVer { get; set; }
}
builder.Property(h => h.RowVer).IsRowVersion();
4.3 RowVersion示例数据库准备
  • 使用SQL Server示例
// OptimisticDemo.csproj
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net5.0</TargetFramework></PropertyGroup><ItemGroup><!-- 使用MySQL start--><PackageReference Include="microsoft.entityframeworkcore.design" Version="5.0.7"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference><PackageReference Include="pomelo.entityframeworkcore.mysql" Version="5.0.0" /><!-- 使用MySQL end--><!-- 使用SQL Server start--><PackageReference Include="microsoft.entityframeworkcore.sqlserver" Version="5.0.4" /><PackageReference Include="microsoft.entityframeworkcore.tools" Version="5.0.4"><PrivateAssets>all</PrivateAssets><IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets></PackageReference><!-- 使用SQL Server end--></ItemGroup></Project>
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// House.cs
namespace OptimisticDemo
{class House{public long Id { get; set; }public string Name { get; set; }public string Owner { get; set; }public byte[] RowVersion { get; set; } // 设置RowVersion,这里可以取其它属性名。}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// HouseConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace OptimisticDemo
{class HouseConfig : IEntityTypeConfiguration<House>{public void Configure(EntityTypeBuilder<House> builder){builder.ToTable("T_Houses");builder.Property(h => h.Name).IsRequired(); // Name属性不为空// builder.Property(h => h.Owner).IsConcurrencyToken(); // 设置并发令牌builder.Property(b => b.RowVersion).IsRowVersion(); // 与实体类的RowVersion对应,也可以理解为令牌}}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace OptimisticDemo
{class MyDbContext : DbContext{public DbSet<House> Houses { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){base.OnConfiguring(optionsBuilder);/* 连接MySQL startvar connStr = "server=localhost; user = root; password = 123456; database = mysqldemo";var serverVersion = new MySqlServerVersion(new Version(5, 7, 36));optionsBuilder.UseMySql(connStr, serverVersion);连接MySQL end */// 连接SQL Serverstring connStr = "Server=.; Database=CoreDataDB; Trusted_Connection=True; MultipleActiveResultSets=true"; // 连接SQL ServeroptionsBuilder.UseSqlServer(connStr);// optionsBuilder.LogTo(Console.WriteLine); // 查看日志}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);}}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 迁移数据库SQL Server
PM> add-migration init
Build started...
Build succeeded.
The Entity Framework tools version '5.0.4' is older than that of the runtime '5.0.7'. Update the tools for the latest features and bug fixes.
To undo this action, use Remove-Migration.
PM> update-database
Build started...
Build succeeded.
The Entity Framework tools version '5.0.4' is older than that of the runtime '5.0.7'. Update the tools for the latest features and bug fixes.
Applying migration '20251006145439_init'.
Done.
PM> 
  • 查看数据库
图片链接丢失
  • 数据库添加数据
图片链接丢失
  • 查看二进制数据
// step1 → SSMS新建查询
select * from T_Houses
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// step2 → 查询结果
1	8-1-101	NULL	0x00000000000036BC说明:
1. 0x00000000000036BC为二进制数据,由SQL Server自动生成。
2. 对数据行做任何改变,自动生成的二进制数据就会改变。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// step3 → 改变Name值为:7-1-101
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// step4 → 查询结果
Id	Name	Owner	RowVersion
1	7-1-101	NULL	0x00000000000036BD说明:二进制数据自动改变了。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
总结:可以将RowVersion的二进制值视为一个“版本号”
4.4 Program.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;namespace OptimisticDemo
{class Program{static void Main(string[] args){Console.WriteLine("请输入您的名字:");string name = Console.ReadLine();using (MyDbContext ctx = new MyDbContext()){var h01 = ctx.Houses.Single(h => h.Id == 1);if (!string.IsNullOrEmpty(h01.Owner)){if (h01.Owner == name){Console.WriteLine("房子已经被抢到了");}else{Console.WriteLine("房子已经被[" + h01.Owner + "]占了");}Console.ReadKey();return;}h01.Owner = name;Thread.Sleep(10000);Console.WriteLine("恭喜你,房子抢到了");try{ctx.SaveChanges();}catch (DbUpdateConcurrencyException ex){Console.WriteLine($"并发冲突");var entry1 = ex.Entries.First();string newValue = entry1.GetDatabaseValues().GetValue<string>("Owner");Console.WriteLine("被[" + newValue + "]抢先了");}Console.ReadLine();}}}
}说明:此处代码实现3.5中一样。
  • 结果演示
图片链接丢失
  • 查看数据库
// SSMS查看
Id	Name	Owner	RowVersion
1	7-1-101	jerry	0x00000000000036BE
4.5 RowVersion的优点
builder.Property(h => h.Owner).IsConcurrencyToken(); // 设置并发令牌,适用于单个属性
builder.Property(b => b.RowVersion).IsRowVersion(); // 适用于一个数据行多个属性,只要任何属性改变都能检查到。
4.6 RowVersion在其它数据库中的问题
1、在MySQL等数据库(某些旧版本,貌似5.6版本及以下版本)中虽然也有类似的timestamp类型,但是由于timestamp类型的精度不够,并不适合在高并发的系统。
2、非SQLServer中,可以将并发令牌列的值更新为Guid的值。
3、修改其他属性值的同时,使用h1.RowVer = Guid.NewGuid()手动更新并发令牌属性的值。
4.7 RowVersion总结
1、乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观并发控制而不是悲观锁。
2、如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可; 
3、如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据的时候,手动更新这一列的值。如果用的是SQLServer数据库,那么也可以采用RowVersion列,这样就不用开发者手动来在每次更新数据的时候,手动更新并发令牌的值了。

结尾

书籍:ASP.NET Core技术内幕与项目实战

视频:https://www.bilibili.com/video/BV1pK41137He

著:杨中科

ISBN:978-7-115-58657-5

版次:第1版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

http://www.hskmm.com/?act=detail&tid=28957

相关文章:

  • linux防火墙操作命令
  • 02020506 EF Core高级06-EF Core批量删除更新插入、全局筛选器、软删除、全局筛选的性能问题
  • 机器学习社会影响与导航系统研究
  • ubuntu24.04 desktop 安装vnc远程桌面(亲测)
  • 完整教程:游标查询在对话历史场景下的独特优势
  • [论文笔记] A Contemporary Survey of Large Language Model Assisted Program Analysis
  • 251011
  • 一种整理HTML和JS代码的方法
  • 元推理框架,是人类文明的《神农本草经》,源于自指自洽的觉悟与洗礼
  • SSL/TLS加密算法:守护网络通信的安全框架
  • 未来计划
  • 【程序员必看】MySQL数据类型全解析:选错类型性能直接掉80%!
  • NOIP2023
  • 理解WPF Stylet中Command=“{s:Action 方法名}“的设计与实现 - 实践
  • 2025环氧地坪漆厂家推荐:常州新禾,品质保证施工无忧!
  • 概率论习题
  • 2025上海经侦律师TOP5榜单:专业法律服务与高效解决方案
  • 概率论部分习题
  • 2025家居ERP推荐:赛思软件助力企业高效管理!
  • 2025彩钢瓦保养优质厂家推荐,江苏承优建筑工程专业服务!
  • 优维科技一面
  • 2025家纺摄影公司最新推荐榜:创意视觉与专业服务的完美结合
  • 2025磁力泵加工厂推荐中正化工,专业定制高效耐用产品!
  • 线段树分治
  • 2025双氧水供应厂家推荐:苏州市岚昱化工品质卓越选择!
  • 2025婚纱照拍摄推荐,南通造物摄影有限公司专业团队打造梦幻
  • 2025上海保洁公司最新推荐榜:高效清洁与贴心服务的优质选择
  • 10.11
  • 「解题报告」蓝桥杯2013省AB 错误票据
  • 2025精密弹簧优质厂家推荐:蓝侨盈科技,精准弹性解决方案!