在使用 EF 保存之前向列表中添加一百万条记录时抛出内存不足异常

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

尝试使用 EF Core 将一百万条记录保存到 SQL Server 表中。首先从暂存表中读取一百万条记录并将其存储到列表中,然后使用 EF Core 的

AddRangeAsync
函数保存列表中的数据。这是代码块

    var importJobValidations = new List<ImportJobValidation>();
    importJobValidations.AddRange(
        (from stg in stagingUnits.AsEnumerable()
        select new ImportJobValidation
        {
            Guid = Guid.NewGuid(),
            StudyProductStagingID = stg.Id,
            IsValidationSuccessful = !errors.Exists(e => e.StagingId == stg.Id),
            Type = "UPLOAD",
            IsActive = true,
            CreatedBy = userId,
            CreatedOn = DateTime.UtcNow
        }));
    await _supplyManagementHangFireContext.AddRangeAsync(importJobValidations);
    await _supplyManagementHangFireContext.SaveChangesAsync();

stagingUnits
是一个 IQueryable。上面的代码块抛出一个
Out of memory exception
。 在这种情况下修复内存不足异常的最佳方法是什么。

c# list entity-framework exception out-of-memory
2个回答
0
投票

一个相对快速的“修复”是:将工作分成可管理的块(例如,每个块 10k 条记录) - 插入那么多,然后完全使用新的数据上下文重新开始。

Enumerable.Chunk
扩展方法可能会让这变得微不足道。


如果要添加那么多数据,您可能更愿意放弃一些抽象级别并查看

SqlBulkCopy
,也许使用
FastMember
或 DapperAOT 之类的工具将
IEnumerable<T>
转换为适合的
IDataReader
流媒体使用;例如(类似于这里):

using var reader = TypeAccessor.CreateDataReader(stagingUnits.AsEnumerable(),
    /* TODO: property names to include */);

using var table = new SqlBulkCopy(connection)
{
    DestinationTableName = "YourTableHere",
    ColumnMappings =
    {
        // TODO: specify column-property map
    }
};
table.EnableStreaming = true;
table.WriteToServer(reader);
return table.RowsCopied;

0
投票

我可以建议对结果进行分页并批量保存记录:

var batchSize = 1000;
var batchCount = 1000000 / batchSize;

for (var i = 0; i < batchCount; i++)
{
    var query = from stg in stagingUnits.AsEnumerable()
    select new ImportJobValidation
    {
        Guid = Guid.NewGuid(),
        StudyProductStagingID = stg.Id,
        IsValidationSuccessful = !errors.Exists(e => e.StagingId == stg.Id),
        Type = "UPLOAD",
        IsActive = true,
        CreatedBy = userId,
        CreatedOn = DateTime.UtcNow
    };
    
    var batch = await query.Skip(i * batchSize).Take(batchSize).ToArrayAsync();
    // AddRangeAsync is not recommended to be used.
    _supplyManagementHangFireContext.AddRange(batch);
    await _supplyManagementHangFireContext.SaveChangesAsync();
}

AddRangeAsync
不建议在标准操作中使用。 来自文档

© www.soinside.com 2019 - 2024. All rights reserved.