You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于C#和EF Core高效读取.txt文件并存储到SQL数据库

大文本文件导入EF Core的内存优化方案

针对你处理500-800万行固定格式文本文件导入SQL数据库时内存占用过高的问题,以下是具体的优化建议和代码调整方案:

核心问题分析

当前代码的主要内存开销来源:

  • 所有实体属性均存储为字符串,即使数字类型也不例外,字符串的内存占用远高于值类型
  • EF Core上下文长期跟踪批量导入的实体,导致内存无法及时释放
  • 批量大小设置过大(10万条),单次内存峰值过高
  • BatchSizeCounter类存在逻辑错误(Decrement方法实际是递增计数)

具体优化方案

1. 优化实体属性类型

将数字类型的属性从string改为对应的值类型(int/long/short等),这会大幅降低单条记录的内存占用。

修改BAS实体示例:

public class BAS
{
    // 数字类型改为对应值类型
    public int Prop1 { get; set; }
    public int Prop2 { get; set; }
    public short Prop3 { get; set; }
    public long Prop4 { get; set; }
    public byte Prop5 { get; set; }
    // ... 其余数字属性按实际类型调整

    // 文本类型保留string
    public string Prop11 { get; set; }
    public string Prop27 { get; set; }
    public string Prop29 { get; set; }
    // ... 其余文本属性
}

2. 优化字符串解析逻辑

利用Span直接解析值类型,避免创建不必要的字符串,减少内存分配。

修改ParseRecord方法:

private static BAS ParseRecord(string line)
{
    var span = line.AsSpan();
    var record = new BAS();

    // 数字属性直接用Span解析为值类型,无需ToString
    int.TryParse(span.Slice(0, 3), out record.Prop1);
    int.TryParse(span.Slice(3, 3), out record.Prop2);
    short.TryParse(span.Slice(6, 2), out record.Prop3);
    long.TryParse(span.Slice(8, 6), out record.Prop4);
    byte.TryParse(span.Slice(14, 1), out record.Prop5);
    // ... 其余数字属性同理

    // 文本属性按需转换为字符串
    record.Prop11 = span.Slice(26, 1).ToString();
    record.Prop27 = span.Slice(81, 10).ToString();
    record.Prop29 = span.Slice(99, 10).ToString();
    // ... 其余文本属性

    return record;
}

3. 修复BatchSizeCounter逻辑错误

修正方法命名和计数逻辑:

public class BatchSizeCounter
{
    private int _count;
    private readonly int _batchSize;

    public BatchSizeCounter(int batchSize)
    {
        _batchSize = batchSize;
    }

    public bool IsBatchComplete => _count >= _batchSize;

    // 方法名改为Increment,符合计数逻辑
    public void Increment()
    {
        _count++;
    }

    public void Reset()
    {
        _count = 0;
    }
}

在ProcessFileAsync中同步修改调用:

records.Add(record);
batchSize.Increment(); // 替换原Decrement调用

4. 控制EF Core上下文内存占用

每次批量插入后,清除上下文的实体跟踪,或者使用短生命周期的临时上下文,避免内存积累:

方案A:清除上下文跟踪

private async Task ProcessBatchAsync(List<BAS> records)
{
    await _context.BAS.AddRangeAsync(records);
    await _context.SaveChangesAsync();
    // 清除已跟踪的实体,释放内存
    _context.ChangeTracker.Clear();
}

方案B:使用临时上下文(更推荐)

private async Task ProcessBatchAsync(List<BAS> records)
{
    // 创建临时上下文,用完即释放
    using var tempContext = new YourDbContext(); // 替换为你的DbContext构造方式
    await tempContext.BAS.AddRangeAsync(records);
    await tempContext.SaveChangesAsync();
}

5. 调整批量大小

将批量大小从10万降低到2万-5万,减少单次内存峰值:

private const int BatchSize = 20_000;

6. 并行处理优化:生产者-消费者模式

通过Channel实现解析和插入的并行执行,提升处理效率的同时平衡内存占用:

public async Task ProcessFileAsync(string filePath)
{
    // 限制通道容量,避免生产者过快导致内存暴涨
    var channel = Channel.CreateBounded<BAS>(new BoundedChannelOptions(10_000)
    {
        FullMode = BoundedChannelFullMode.Wait
    });

    // 生产者:读取文件并解析记录
    var producer = Task.Run(async () =>
    {
        using var reader = new StreamReader(filePath);
        string line;
        while ((line = await reader.ReadLineAsync()) != null)
        {
            var record = ParseRecord(line);
            await channel.Writer.WriteAsync(record);
        }
        channel.Writer.Complete();
    });

    // 消费者:批量插入数据库
    var consumer = Task.Run(async () =>
    {
        var batch = new List<BAS>(BatchSize);
        await foreach (var record in channel.Reader.ReadAllAsync())
        {
            batch.Add(record);
            if (batch.Count >= BatchSize)
            {
                await ProcessBatchAsync(batch);
                batch.Clear();
            }
        }
        // 处理剩余记录
        if (batch.Any())
        {
            await ProcessBatchAsync(batch);
        }
    });

    await Task.WhenAll(producer, consumer);
}

7. 第三方批量插入扩展(可选)

使用EFCore.BulkExtensions库替代原生AddRangeAsync,该库支持高效批量插入,内存占用更低:

private async Task ProcessBatchAsync(List<BAS> records)
{
    using var tempContext = new YourDbContext();
    await tempContext.BulkInsertAsync(records);
}

优化效果预期

  • 实体属性类型优化可减少约60%-70%的单条记录内存占用
  • 上下文跟踪控制可避免内存持续积累
  • 生产者-消费者模式可提升整体处理速度,同时控制内存峰值

内容的提问来源于stack exchange,提问作者Andrés Quesada Cascante

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 06:17:34