实体框架 ChangeTracker 流并保存到查询

Posted

技术标签:

【中文标题】实体框架 ChangeTracker 流并保存到查询【英文标题】:Entity Framework ChangeTracker Stream and Save to Query Afterwards 【发布时间】:2020-02-03 21:09:18 【问题描述】:

我想验证某个实体何时在实体框架(带有数据库)中被修改/更新/插入/删除。 (例如 ProductType 实体表)

** 希望更改跟踪器被保存并稍后查询,因为“如果在 DbContext 对象被销毁之前未保存跟踪更改,则它们将丢失。”只需将 ChangeTracker 保留最多几个小时进行分析,并在需要时保存在 MessageQueue 中。 MemoryCache 也可以。

1) 有人推荐使用 LoggerFactory:

optionsBuilder.UseLoggerFactory(loggerFactory) 

https://www.entityframeworktutorial.net/efcore/logging-in-entityframework-core.aspx

但是,它可能很难解析,因为更新、修改、删除可以通过连接、别名、括号以及通常由 EF 生成的复杂 SQL 语句来完成。所以文本解析可能不准确。

INSERT INTO [Students] ([DateOfBirth], [GradeId], [Height], [Photo], [Stud
entName], [Weight])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
SELECT [StudentID]
FROM [Students]
WHERE @@ROWCOUNT = 1 AND [StudentID] = scope_identity();
info: Microsoft.EntityFrameworkCore.Database.Command[200101]
Executed DbCommand (68ms) [Parameters=[@p0='' (DbType = DateTime2), @p1=''
(DbType = Int32), @p2='0', @p3='' (Size = 8000) (DbType = Binary), @p4='Steve'
(Size = 4000), @p5='0'], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Students] ([DateOfBirth], [GradeId], [Height], [Photo], [Stud
entName], [Weight])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
SELECT [StudentID]
FROM [Students]
WHERE @@ROWCOUNT = 1 AND [StudentID] = scope_identity();
A data reader was disposed.
dbug: Microsoft.EntityFrameworkCore.Database.Transaction[2002

2) 选项 2 是 ChangeTracker, 其他人建议在 SaveChanges 语句之前使用 ChangeTracker,因为它更干净(见下面的查询)。但是在每个 SaveChanges 语句之前执行此操作,考虑到我们每秒有 500 个事务会影响应用程序的性能速度。

那么有什么方法可以在保存更改后将 ChangeTracker 历史记录流式传输并保留到日志中,这样可以更轻松地查询哪些实体发生了更改?最好在事务完成后查找,而不阻塞事务。

var Entries = context.ChangeTracker
           .Entries()
           .Where(x => x.State == EntityState.Modified || x.State == EntityState.Deleted|| x.State == EntityState.Added)
           .Select(x =>x.Entity)
           .ToList();

https://entityframework.net/change-tracker

【问题讨论】:

same question from you,考虑到这个是重复的。 【参考方案1】:

@DerrikRodgers,检查 Entity 是否为某种类型的实例所需的时间与事务时间不可比。如果您提供了代码,每秒 500 次交易绝对可以正常工作。

public void ReportChanges()

  var entities=ChangeTracker
           .Entries()
           .Where(x => x.Entity is Product)
           .Where(x => x.State == EntityState.Modified || x.State == EntityState.Deleted || x.State == EntityState.Added)
           .GroupBy(x=>x.State, x => x.Entity as Product)
           .ToList();
  ... // Producer/Consumer to do not block current thread and control concurrency

通过综合测试获得性能基线,如下所示:

var cc = new ApplicationDbContext();
for (int i = 0; i < 100; i++)
    cc.Users.Add(new ApplicationUser()  Id = i );

var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++)
    cc.ReportChanges();
sw.Stop();
Console.WriteLine($"1000 times to report 100 entities took sw.ElapsedMillisecondsms. Rate 1000*100/(sw.ElapsedMilliseconds/1000.0) items/s");
Console.ReadKey();

// 1000 times to report 100 entities took 461ms. Rate 216919.739696312 items/s

另一个问题是您将如何处理过滤后的实体。那件事可能很慢。例如,如果您尝试将它们全部记录下来并且不在记录器中使用异步接收器/目标,那么这将很慢。在这种情况下,您可以实现Producer/Consumer 模式并将过滤后的实体通过管道传递给另一个消费者,该消费者将在不同的线程中分派它们并执行长时间的操作。 Rx.NET 在这种情况下可能会很有帮助。

您可以只启动Task 而不是生产者/消费者,但这可能会导致线程池不足,我建议明确控制“慢”操作的并行性。

【讨论】:

嗨,谢谢,我们怎么知道跟踪更改对于每秒 500 个事务在性能和速度方面是否合适?很高兴你提出来,从人们那里听到不同的东西,欣赏它 EF 已经为您跟踪更改,在这里您只添加和开销来迭代这些更改,找到您需要和处理的内容。实际上每秒有多少事务并不重要,因为开销是在 SaveChanges() 方法上添加的,因此您可以测量 1 次调用的开销并将其近似为您的负载。内存中的操作很快。使用不同的负载配置文件编写测试并测量开销。 谢谢,您有像 Producer/Consumer 或 Rx.Net 的吗?什么是更好的性能,现在学习 rx.net,谢谢!给分 这里还有一个问题,如果你知道响应式,整个周末都在学习东西,几个月前开始了 .net 编程,***.com/questions/58263480/… 给出了答案并接受了答案,也请随意竖起大拇指,谢谢

以上是关于实体框架 ChangeTracker 流并保存到查询的主要内容,如果未能解决你的问题,请参考以下文章

EF ChangeTracker 访问被跟踪实体及其导航集合

检查是不是有任何待保存的更改要保存

使用实体框架恢复数据库中的更改

如何阻止实体框架尝试保存/插入子对象?

实体框架代码优先 - 保存实体时设置属性的最佳方法是啥

首先使用实体​​框架代码保存单个实体对象