我正在开发一个应用程序,我需要从数据库中获取大量数据,进行一些数据操作,然后将数据插入另一个数据库中。
但是,我正在努力寻找检查源数据库中的记录是否已存在于目标数据库中的最佳方法。如果不存在,我需要添加一条新记录。如果存在,我需要更新现有记录。
我的代码看起来像这样(简化版):
List<Data> data_from_db = new();
var existingData = await _context.Data.ToListAsync();
var sourceData = await _dbAccess.LoadData<Data, dynamic>(storedProcedure: "sp_ApiGetAllData", new { }, "Db");
data_from_db = sourceData.ToList();
//loop through data. If the datapoint is already present in db, it will be updated. If not, it will be added as a new datapoint.
foreach (var datapoint in data_from_db)
{
//check if price is already present in db.
var existingDatapoint = existingData.Find(x => x.ItemId== datapoint.ItemId);
if (existingDatapoint != null)
{
//Update datapoint
_context.Entry(existingDatapoint).State = EntityState.Modified;
processedIds.Add(existingDatapoint.Id);
}
else
{
//Create new datapoint
_context.Data.Add(newDatapoint);
}
}
await _context.SaveChangesAsync();
这很好用。然而,当数据库有 +400K 行时,这个过程会变得非常缓慢。 具体来说,就是“查找”功能花费了很多时间。当然,这是有道理的,因为它将在列表中进行 400k * 400k 次搜索。
有没有更好的方法来处理这个问题?
更新: 该应用程序必须进行一些复杂的价格计算(这就是我在原始帖子中对其进行简化的原因)。 但总而言之:我得到价格、折扣信息和最小值。来自源数据库的金额,计算价格,然后将计算出的价格插入目标数据库。
棘手的部分出现在我想检查价格信息是否已经存在时,因为单个价格是商品编号、折扣组和最低价格的组合。金额。
所以实际上 find 语句看起来像这样:
var existingPrice = existingPrices.Find(x => x.ItemNumber == priceEntry.ItemNumber && x.DiscountGroupId == priceEntry.DiscountGroupId && x.MinAmount == priceEntry.MinAmount);
以上 3 个参数本身都不足以确定价格。 一个项目可以有很多基于折扣组的不同价格,但每个折扣组也可以有多个价格基于订购给定产品的数量(最小数量)。
是的,有一种更有效的方法来处理这个问题。您可以将此任务委托给数据库,而不是将所有数据加载到内存中并在您的应用程序中执行检查。实现此目的的一种方法是使用批量插入/更新库,如 EF Core 批量扩展,它针对这些类型的操作进行了优化。这是您如何使用它的示例:
1- 安装 NuGet 包 EFCore.BulkExtensions:
Install-Package EFCore.BulkExtensions
2- 更新您的代码以使用 BulkInsertOrUpdate 方法:
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
// ... other using statements ...
// ...
List<Data> data_from_db = new();
var sourceData = await _dbAccess.LoadData<Data, dynamic>(storedProcedure: "sp_ApiGetAllData", new { }, "Db");
data_from_db = sourceData.ToList();
// Use BulkInsertOrUpdate to insert or update the data in the destination database
await _context.BulkInsertOrUpdateAsync(data_from_db);
此方法将显着提高性能,因为它将检查和更新/插入记录的任务委托给数据库本身。它使用临时表执行此操作并批量执行操作,这比为每一行执行单独的命令更快。
请注意,具体实现可能因您使用的数据库提供商而异。如果您使用的是 SQL Server,EF Core 批量扩展应该运行良好。如果您使用不同的提供程序,您可能需要找到一个类似的库或自己实现批量插入/更新功能。
使用
ValueTuple
,您可以创建一个 Dictionary
将价格的三个标识符映射到您正在更新的数据库中的现有价格。 C# 编译器会根据其所有项中的值(包括哈希值)自动为 ValueTuple
执行相等操作。然后你可以只查找现有价格,或者如果找不到则创建一个新价格:
var existingPrices = await _context.Prices.ToListAsync();
var sourcePrices = await _dbAccess.LoadData<Prices, dynamic>(storedProcedure: "sp_ApiGetAllData", new { }, "Db");
var existingPriceMap = existingPrices.ToDictionary(p => (p.ItemNumber, p.DiscountGroupId, p.MinAmount));
//loop through data. If the datapoint is already present in db, it will be updated. If not, it will be added as a new datapoint.
foreach (var priceEntry in sourcePrices)
{
//check if price is already present in db.
if (existingPriceMap.TryGetValue((priceEntry.ItemNumber, priceEntry.DiscountGroupId, priceEntry.MinAmount), out var existingPrice))
{
//Update datapoint
_context.Entry(existingPrice).State = EntityState.Modified;
processedIds.Add(existingPrice.Id);
}
else
{
//Create new datapoint
_context.Data.Add(newDatapoint);
}
}
await _context.SaveChangesAsync();
我不知道 Compiler 是否优化了这里的任何内容,但我对 find 操作的想法是确保 existing_data 按 id 排序,然后执行二分查找