1. 前言
EF Core 8.0.3是当前最新的版本,针对EFCore ORM(对象关系映射)框架的测试就从来没有停止过。它能够将数据库中的数据映射到应用程序中的对象模型,从而简化了对数据库的操作。通过 EF Core,可以使用面向对象的方式来进行数据库操作,而不必编写复杂的 SQL 查询语句。
随着版本的不断更新,EF Core 在性能和功能方面都得到了持续的优化和增强。新版本不仅修复了 bug,还引入了更多的特性和改进,以提升用户体验和开发效率。微软开发团队也在不断努力优化 EF Core 的性能,使其能够更好地应对各种数据库操作场景。
在处理大数据量下的数据库操作时,性能优化成为开发人员关注的焦点之一。针对大数据量的查询、插入、更新和删除操作,需要考虑如何有效利用 EF Core 提供的功能和优化策略,以确保操作的效率和性能。通过合理的设计和优化,充分发挥 EF Core 在大数据量操作下的优势,提升系统的性能和响应速度。
为此,我们今天来试验:探究EFCore8.0同时更新万条记录的SQL生成和执行效率
2. 启动docker数据库
1、启动docker Mysql 容器
2、测试数据库是否正常连接,我这里使用的是vscode插件Database Client,可以查看数据库运行状态。
3. 构建配置项目
1、新增控制台项目
2、引入EFCore相关包,引入日志包的目的是为了查看生成的sql语句
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup>
</Project>
3、配置Entity和Context
[Table("tb_point")]
public class Point
{
[Key]
public string Uid { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
public class MyDbContext : DbContext
{
private static ILoggerFactory loggerFactory = LoggerFactory.Create(b=>b.AddConsole());
public DbSet<Point> Points { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql("server=localhost;port=3306;database=mydb;user=root;password=root;", new MariaDbServerVersion(new Version(8, 0, 27)));
optionsBuilder.UseLoggerFactory(loggerFactory);
}
}
注意这里的日志配置。在Context文件中调用日志组件.
4、使用的是Code first 模式。当数据库不存在是,自动构建数据库和表结构
if (!context.Database.CanConnect())
{
context.Database.EnsureCreated();
}
4. 插入万条数据
构造并插入万条记录
static List<Point> GeneratePoints(int count)
{
Random random = new Random();
List<Point> points = new List<Point>();
for (int i = 0; i < count; i++)
{
Point point = new Point
{
Uid = Guid.NewGuid().ToString(),
X = random.NextDouble(),
Y = random.NextDouble(),
Z = random.NextDouble()
};
points.Add(point);
}
return points;
}
private static void Insert()
{
using (var context = new MyDbContext())
{
if (!context.Database.CanConnect())
{
context.Database.EnsureCreated();
}
List<Point> points = GeneratePoints(10000);
context.Points.AddRange(points);
context.SaveChanges();
}
}
这个时候,我们运行程序,将生成的万条记录,写入数据库中,这个时候,我们可以查看这万条记录是如何由efcore转换为sql语句的。
从输出的图中,我们可以看出,生成了42条记录为一个Command命令。同时插入1万条记录。生成了接近238个Command指令,每条个Command执行耗时1ms。
5. 查询万条数据
查询万条数据的代码如下
private static List<Point> Query()
{
using (var context = new MyDbContext())
{
return context.Points.Take(10000).ToList();
}
}
我们看到的查询语句是:
EFCore 提示我们需要加入Limiting配置或者Orderby等排序操作。执行耗时27ms。生成sql语句
SELECT `t`.`Uid`, `t`.`X`, `t`.`Y`, `t`.`Z`
FROM `tb_point` AS `t`
LIMIT @__p_0 //是我们Take(10000)的参数
6. 更新万条数据
更新万条数据代码如下
static void RefreshPoints(List<Point> points)
{
Trace.WriteLine(points.Count.ToString());
Random random = new Random();
foreach (var point in points)
{
// 模拟刷新数据,这里使用随机数
point.X = random.NextDouble();
point.Y = random.NextDouble();
point.Z = random.NextDouble();
}
}
private static void Update(List<Point> points)
{
RefreshPoints(points);
Stopwatch st = new Stopwatch();
st.Start();
using (var context = new MyDbContext())
{
context.Points.UpdateRange(points);
context.SaveChanges();
}
st.Stop();
Trace.WriteLine(st.ElapsedMilliseconds.ToString());
}
我们看到的Update语句:
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@p3='?' (Size = 95), @p0='?' (DbType = Double), @p1='?' (DbType = Double),
。。。。太长省略。。。。
@p166='?' (DbType = Double)], CommandType='Text', CommandTimeout='30']
UPDATE `tb_point` SET `X` = @p0, `Y` = @p1, `Z` = @p2
WHERE `Uid` = @p3;
SELECT ROW_COUNT();
UPDATE `tb_point` SET `X` = @p4, `Y` = @p5, `Z` = @p6
WHERE `Uid` = @p7;
SELECT ROW_COUNT();
。。。。太长省略。。。。
UPDATE `tb_point` SET `X` = @p164, `Y` = @p165, `Z` = @p166
WHERE `Uid` = @p167;
SELECT ROW_COUNT();
这里我们就可知,生成了42条记录为一个Update命令。生成了接近238个Update Command指令,每条个Command执行耗时1ms。
万条记录的刷新耗时为1263ms
总结
通过Microsoft.Extensions.Logging.Debug在控制台输出完整调试日志,可以直观的看到生成的sql语句,以及数据库在插入和更新大量数据时,sql语句是如何生成的。这对我们观察和使用EFCore,优化性能等指明了优化方向。