我全新安装了 Visual Studio 2022(社区版)。我试图弄清楚为什么我的使用 EF Core 的导入器如此慢,但后来注意到实际上并不是代码本身慢,而是 VS 调试器导致它变慢。
我做了一个最小的代码测试来重现该问题(请不要批评/更改代码,这纯粹是一个突出问题的准系统示例)。现实世界的代码并非如此,但它遇到了同样的问题。
public class TestItem
{
[Key]
public Guid Id { get; set; }
public int TestNum { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<TestItem> TestItems { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseNpgsql("Host=localhost;Database=efdebugtest;Username=postgres;Password=...");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TestItem>().HasIndex(r => r.TestNum);
}
}
internal class Program
{
static async Task Main(string[] args)
{
void L(string str) => Console.WriteLine(str);
using var ctx = new AppDbContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.MigrateAsync();
var sw = new Stopwatch();
sw.Start();
var items = Enumerable.Range(0, 100_000).Select(i => new TestItem()
{
Id = Guid.NewGuid(),
TestNum = i,
});
L($"Items Generated: {sw.Elapsed.TotalSeconds}s");
sw.Restart();
await ctx.AddRangeAsync(items);
L($"Items Added: {sw.Elapsed.TotalSeconds}s");
sw.Restart();
await ctx.SaveChangesAsync();
L($"Items Saved: {sw.Elapsed.TotalSeconds}s");
ConcurrentBag<int> foundItems = new();
var testChunks = Enumerable.Range(0, 200_000).OrderBy(_ => Guid.NewGuid()).Take(20_000).Chunk(2500);
await Parallel.ForEachAsync(testChunks, async (chunk, cancellationToken) =>
{
using var threadCtx = new AppDbContext();
var threadSw = new Stopwatch();
threadSw.Start();
foreach (var num in chunk)
{
var dbEntry = await threadCtx.TestItems.FirstOrDefaultAsync(ti => ti.TestNum == num);
if (dbEntry != null)
{
foundItems.Add(dbEntry.TestNum);
}
}
L($"Thread finished search: {threadSw.Elapsed.TotalSeconds}s");
});
}
}
如果我在附加调试器的调试模式下运行它,速度会非常慢,每个“搜索”线程需要 17-18 秒。
Items Generated: 0.0003297s
Items Added: 0.6718225s
Items Saved: 4.2752014s
Thread finished search: 17.2668618s
Thread finished search: 17.2820979s
...
Thread finished search: 17.4786316s
Thread finished search: 17.4891402s
如果我在调试模式下运行它没有附加调试器,它的速度很快,每个“搜索”线程花费的时间不到1秒:
Items Generated: 0.0002435s
Items Added: 0.6201017s
Items Saved: 4.025146s
Thread finished search: 0.8171215s
Thread finished search: 0.8336687s
...
Thread finished search: 0.8410876s
Thread finished search: 0.8419168s
如果我将
FirstOrDefaultAsync
更改为常规 FirstOrDefault
:
var dbEntry = threadCtx.TestItems.FirstOrDefault(ti => ti.TestNum == num);
速度很快(< 1 second per search thread) both with and without the debugger attached.
有没有人遇到过这个问题或者知道什么 VS 调试器设置可能会导致它?理想情况下,我不想纯粹为了保持调试器正常运行而更改为使用非异步 FirstOrDefault,因为当未连接调试器时它可以正常工作。
我尝试禁用诊断工具(选项 -> 调试 -> 常规 -> 调试时启用诊断工具),但这没有什么区别。
可能值得注意的是,如果您只是检查某个项目是否存在,则替换速度会快得多:
var dbEntry = await threadCtx.TestItems.FirstOrDefaultAsync(ti => ti.TestNum == num);
if (dbEntry != null)
{
foundItems.Add(dbEntry.TestNum);
}
这样:
var itemExists = await threadCtx.TestItems.AnyAsync(ti => ti.TestNum == num);
if (itemExists)
{
foundItems.Add(num);
}
原始代码每次都会获取并跟踪整个实体,而如果找到匹配项,您所保留的只是“TestNum”值。
如果您确实需要从检索到的 TestItem 中获取其他值,则使用
Select()
从找到的行中获取所需的 ID/值会更有效。如果您确实想做一些事情,例如将实体本身存储在 List
中,则使用:
var testItem = await threadCtx.TestItems
.AsNoTracking()
.FirstOrDefaultAsync(ti => ti.TestNum == num);
...确保它已分离并且 DbContext 不会跟踪它。