我正在尝试将一个大型对象列表插入到我的数据库中(一次大约 30,000 条记录),并根据各列的复合键判断该记录是否为重复行。下面是使这更清楚一点的代码:
await {db_context}.AddRangeAsync(Metrics.Where(x =>!MetricsInDb.AsEnumerable().Any(y =>
x.CreativeId == y.CreativeId
&& x.LineItemId == y.LineItemId
&& x.Date.Date == y.Date.Date
&& x.City == y.City
&& x.Country == y.Country
&& x.Metro == y.Metro
&& x.State == y.State)));
为了进一步解释,我有两个列表。
Metrics
是我要插入的对象列表。
MetricsInDb
是我正在比较的另一个对象列表。
在 .Any()
中,基本上我想说的是,如果所有这些列都匹配,那么它是重复的。不要插入重复的行。
对我来说,逻辑似乎是合理的。我不确定在如此大的复合键上是否有更好的方法来做到这一点。
我最初在这里有一个 .AsParallel()
.... Metrics.AsParallel().Where(x => !MetricsInDb.......)
,我认为这是问题所在,我删除了它,显然运行几次后它仍然插入重复项。
任何和所有提示都会非常有帮助。提前致谢!
答案 0 :(得分:2)
复合键给这样的操作带来了挑战,但是我看到这种方法的真正问题是:
4.10.1
这会将数据库中的所有指标记录加载到内存中。随着表的增长,这在性能和资源使用方面将变得不可持续。
对于较大的批量操作,我的第一选择可能是不使用 EF,而是使流程脱机/后台运行。
谈到使用 EF 进行批量操作时,我使用的方法是首先将所有处理记录导入到一个空的临时表中,然后您可以在那里执行临时表和真实数据之间的连接。暂存记录可以根据组合键声明与 Metric 行的关系。这两个实体(Metrics 和 StagingMetrics)注册在一个有界上下文中,是一个仅知道这两个实体及其关系的上下文。
Metrics.Where(x =>!MetricsInDb.AsEnumerable().Any( ...
获取要添加的项目:
modelBuilder.Entity<StagingMetrics>()
.HasOptional(x => x.Metric)
.WithMany()
.HasForeignKey(x => new
{
x.CreativeId,
x.LineItemId,
x.Date.Date,
x.City,
x.Country,
x.Metro,
x.State
});
从那里我们可以将其转换回公制。 (它们是相同的,但需要将其转换为度量标准)
var newMetrics = stagingDbContext.StagingMetrics.Where(x => x.Metric == null);
由此,我们可以通过 StagingDbContext 或主应用程序 DbContext 添加新的指标。要考虑的最后一步是截断可以由 SQL 命令完成的临时表:
var newMetrics = stagingDbContext.StagingMetrics.Where(x => x.Metric == null)
.Select(x => new Metric
{
CreativeId = x.CreativeId,
LineItemId = x.LineItemId,
Date = x.Date,
City = x.City,
Country = x.Country,
Metro = x.Metro,
State = x.State
}).ToList();
我会考虑在第一次运行进程时截断表格。
这种方法的一个考虑因素是 StagingDbContext 应限定在此操作的范围内,以避免代码在截断后引用 StagingMetrics 表中的行。
所以更完整的导入方法流程可能如下所示:
stagingDbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE StagingMetrics");
其中 StagingDbContextFactory 是返回 DbContext 新实例的工厂类,AppDbContext 是注入的主应用程序 DbContext。
这不会是一个可以被逐次触发的过程,例如给定多个调用的 Web 请求的一部分可能会尝试在前一个调用完成之前截断临时表。如果这可以作为上传或来自用户的类似请求的一部分执行,则将请求记录到处理队列,并由后台工作人员确保一次只处理一个导入。