Entity Framework 根据复合键插入重复项
Entity Framework inserting duplicates based on a composite key
我正在尝试将大量对象列表插入我的数据库(一次大约 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.......)
我认为这是问题所在,但显然不是在运行几次后它仍在插入重复项。
任何和所有提示都会非常有用。提前致谢!
复合键给这样的操作增加了挑战,但是我看到这种方法的真正问题是:
Metrics.Where(x =>!MetricsInDb.AsEnumerable().Any( ...
这会将数据库中的所有指标记录加载到内存中。随着 table 的增长,这在性能和资源使用方面将变得不可持续。
对于较大的批量操作,我的第一选择可能是 不 使用 EF 而是采用过程 offline/background.
当涉及到使用 EF 的批量操作时,我使用的方法是首先将所有处理记录导入一个空的暂存区 table,然后您可以从那里执行暂存区之间的连接 table 和真实数据。登台记录可以根据复合键声明返回到度量标准行的关系。这两个实体(Metrics 和 StagingMetrics)在有界上下文中注册,上下文只知道这两个实体及其关系。
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);
从那里我们可以将其转换回度量标准。 (它们是相同的,但需要进行转换,以便可以将其写为 Metric)
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 或主应用程序 DbContext 添加新指标。要考虑的最后一步是截断暂存 table,这可以通过 SQL 命令完成:
stagingDbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE StagingMetrics");
我会考虑在第一个 运行 进程时截断 table。
这种方法的一个考虑因素是 StagingDbContext 应该限定在这个操作范围内,以避免代码在截断之后引用 StagingMetrics table 中的行的可能性。
因此,更完整的导入方法流程可能如下所示:
using (var stagingDbContext = StagingDbContextFactory.Create())
{
stagingDbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE StagingMetrics");
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();
if (newMetrics.Any())
{
AppDbContext.Metrics.AddRange(newMetrics);
AppDbContext.SaveChanges();
}
// Can truncate the Staging table here as well, or leave the contents to debug if there was an issue.
}
其中 StagingDbContextFactory 是工厂 class 到 return DbContext 的新实例,AppDbContext 是注入的主应用程序 DbContext。
这不是一个可以零碎触发的过程,例如 Web 请求的一部分,因为多个调用可能会尝试在上一个调用完成之前截断暂存 table。如果这可以作为上传或类似用户请求的一部分执行,则将请求记录到后台工作人员的处理队列中,确保一次只处理一个导入。
我正在尝试将大量对象列表插入我的数据库(一次大约 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.......)
我认为这是问题所在,但显然不是在运行几次后它仍在插入重复项。
任何和所有提示都会非常有用。提前致谢!
复合键给这样的操作增加了挑战,但是我看到这种方法的真正问题是:
Metrics.Where(x =>!MetricsInDb.AsEnumerable().Any( ...
这会将数据库中的所有指标记录加载到内存中。随着 table 的增长,这在性能和资源使用方面将变得不可持续。
对于较大的批量操作,我的第一选择可能是 不 使用 EF 而是采用过程 offline/background.
当涉及到使用 EF 的批量操作时,我使用的方法是首先将所有处理记录导入一个空的暂存区 table,然后您可以从那里执行暂存区之间的连接 table 和真实数据。登台记录可以根据复合键声明返回到度量标准行的关系。这两个实体(Metrics 和 StagingMetrics)在有界上下文中注册,上下文只知道这两个实体及其关系。
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);
从那里我们可以将其转换回度量标准。 (它们是相同的,但需要进行转换,以便可以将其写为 Metric)
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 或主应用程序 DbContext 添加新指标。要考虑的最后一步是截断暂存 table,这可以通过 SQL 命令完成:
stagingDbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE StagingMetrics");
我会考虑在第一个 运行 进程时截断 table。
这种方法的一个考虑因素是 StagingDbContext 应该限定在这个操作范围内,以避免代码在截断之后引用 StagingMetrics table 中的行的可能性。
因此,更完整的导入方法流程可能如下所示:
using (var stagingDbContext = StagingDbContextFactory.Create())
{
stagingDbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE StagingMetrics");
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();
if (newMetrics.Any())
{
AppDbContext.Metrics.AddRange(newMetrics);
AppDbContext.SaveChanges();
}
// Can truncate the Staging table here as well, or leave the contents to debug if there was an issue.
}
其中 StagingDbContextFactory 是工厂 class 到 return DbContext 的新实例,AppDbContext 是注入的主应用程序 DbContext。
这不是一个可以零碎触发的过程,例如 Web 请求的一部分,因为多个调用可能会尝试在上一个调用完成之前截断暂存 table。如果这可以作为上传或类似用户请求的一部分执行,则将请求记录到后台工作人员的处理队列中,确保一次只处理一个导入。