Entity Framework Core - SQL 更新缓存计划问题

问题描述 投票:0回答:1

我有一个简单的应用程序,用于监听来自服务总线的事件。根据传入事件,它将使用 Entity Framework Core 更新数据库中的记录。应用程序将在查询数据库并更新实体属性之前批量传入消息。

以下只是示例代码:

var dbResults = _context.SampleDbSet
    .Where(s => s.Active)
    .ToListAsync();

var dbResultsLookup = dbResults
    .ToDictionary(
        entity => $"{entity.Id}_{entity.Name}",
        v => v
    );

foreach (var message in incomingMessages)
{
    if (dbResultsLookup.TryGetValue($"{message.Id}_{message.Name}", out var dbEntity))
    {
        dbEntity.PropertyOne = message.PropertyOne;
        dbEntity.LastChangeTime = DateTimeOffset.Now;
    }
}

await _context.SaveChangesAsync(cancel);

SQL 缓存计划中发生的情况是:

  1. 每个批量更新最终都会在
    dm_exec_cached_plans
    中得到一个唯一的准备好的计划,这是因为有时
    PropertyOne
    没有被修改,实体框架不会将其包含在参数化更新中。示例如下:
    @p76 decimal(9,2)
    仅在
    PropertyOne
    更新时添加。
(
    @p1 int,@p2 int,@p0 datetimeoffset(7),@p4 int,@p5 int,@p3 datetimeoffset(7),
    @p7 int,@p8 int,@p6 datetimeoffset(7),@p10 int,
    @p11 int,@p9 datetimeoffset(7),@p13 int,@p14 int,@p12 datetimeoffset(7),
    @p16 int,@p17 int,@p15 datetimeoffset(7),@p19 int,@p20 int,@p18 datetimeoffset(7),@p22 int,@p23 int,@p21 datetimeoffset(7),
    @p25 int,@p26 int,@p24 datetimeoffset(7),@p28 int,@p29 int,@p27 datetimeoffset(7),@p31 int,@p32 int,@p30 datetimeoffset(7),
    @p34 int,@p35 int,@p33 datetimeoffset(7),@p37 int,@p38 int,@p36 datetimeoffset(7),@p40 int,@p41 int,@p39 datetimeoffset(7),
    @p43 int,@p44 int,@p42 datetimeoffset(7),@p46 int,@p47 int,@p45 datetimeoffset(7),

@p77 int,@p78 int,@p75 datetimeoffset(7),@p76 decimal(9,2)

)SET NOCOUNT ON;

UPDATE [Sample_Table] SET [LastChangeTime] = @p0  WHERE [Id] = @p1 AND [Name] = @p2;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p3  WHERE [Id] = @p4 AND [Name] = @p5;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p6  WHERE [Id] = @p7 AND [Name] = @p8;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p9  WHERE [Id] = @p10 AND [Name] = @p11;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p12  WHERE [Id] = @p13 AND [Name] = @p14;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p15  WHERE [Id] = @p16 AND [Name] = @p17;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p18  WHERE [Id] = @p19 AND [Name] = @p20;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p21  WHERE [Id] = @p22 AND [Name] = @p23;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p24  WHERE [Id] = @p25 AND [Name] = @p26;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p27  WHERE [Id] = @p28 AND [Name] = @p29;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p30  WHERE [Id] = @p31 AND [Name] = @p32;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p33  WHERE [Id] = @p34 AND [Name] = @p35;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p36  WHERE [Id] = @p37 AND [Name] = @p38;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p39  WHERE [Id] = @p40 AND [Name] = @p41;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p42  WHERE [Id] = @p43 AND [Name] = @p44;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p45  WHERE [Id] = @p46 AND [Name] = @p47;
SELECT @@ROWCOUNT;
UPDATE [Sample_Table] SET [LastChangeTime] = @p75, [PropertyOne] = @p76  WHERE [Id] = @p77 AND [Name] = @p78;
SELECT @@ROWCOUNT;

  1. 因为每个批处理执行都可以创建一个唯一的参数化查询,所以它会破坏 SQL Server 查询计划,从而导致其他一些查询计划被清除,以便为该随机查询计划腾出空间。

我还没研究过如何使用存储过程+TVP,如果没有存储过程,还有什么办法

  1. 即使
    PropertyOne
    未更新,我们仍然希望 EF Core 将其包含在参数化查询中?
  2. 不将这样的更新计划存储到SQL查询缓存中吗? (因为 EF Core 会生成随机查询)无论是来自 EF Core 库还是 SQL 数据库本身都可以做到这一点吗?
entity-framework entity-framework-6
1个回答
0
投票

您可以关闭 AutoDetectChanges 以始终获得相同的 SQL 语句。

另一方面,这将提高 SaveChanges 的性能,因为它不必遍历图表来查找更改:

// Switch off autodetect changes:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
foreach (var message in incomingMessages)
{
    if (dbResultsLookup.TryGetValue($"{message.Id}_{message.Name}", out var dbEntity))
    {
        dbEntity.PropertyOne = message.PropertyOne;
        dbEntity.LastChangeTime = DateTimeOffset.Now;
        // Set fields to be updated:
        _context.Entry(dbEntity).Property(x => x.PropertyOne).IsModified = true;
        _context.Entry(dbEntity).Property(x => x.LastChangeTime).IsModified = true;
    }
}
await _context.SaveChangesAsync(cancel);
// Restore autodetect changes:
_context.ChangeTracker.AutoDetectChangesEnabled = true;
© www.soinside.com 2019 - 2024. All rights reserved.