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

Asp.Net生成大规模KML文件时遇OutOfMemoryException问题求助

解决60万条地理数据生成KML内存不足问题及Azure Blob分块实现

核心问题分析

你的代码当前存在三个关键内存瓶颈:

  • 一次性加载60万条数据到exportedData集合,内存占用陡增
  • 所有Placemark(包括全量汇总的documentAll)都在内存中构建完成后才写入流,kmlFile.Save(stream)时触发内存溢出
  • 为每个Placemark重复创建Style对象,造成大量内存浪费

解决方案步骤

1. 数据库分页加载数据,避免一次性全量读取

不要把所有数据一次性加载到内存,通过数据库分页(比如EF Core的Skip/Take、SQL的OFFSET/FETCH)分批获取,每次处理1000-10000条,处理完释放内存再取下一批。

2. 复用Style对象,削减内存开销

将Style定义在Document级别,每个Placemark通过StyleUrl引用,而非重复创建:

// 在Document初始化时创建一次Style
var styleId = $"style_{provider.ProviderId}_{technology.TechCode}";
var style = new Style { Id = styleId };
style.Polygon = new PolygonStyle
{
    ColorMode = ColorMode.Normal,
    Color = new Color32(byte_Color_A, byte_Color_B, byte_Color_G, byte_Color_R)
};
style.Balloon = new BalloonStyle
{
    BackgroundColor = new Color32(byte_Color_A, byte_Color_B, byte_Color_G, byte_Color_R)
};
document.AddStyle(style);

// Placemark通过URL引用Style
placemark.StyleUrl = new Uri($"#{styleId}", UriKind.Relative);

3. 直接写入Azure Blob,跳过MemoryStream

使用Azure Blob存储的AppendBlobClient分块写入KML内容,无需在内存生成完整文件。关键实现如下:

依赖安装

先安装Azure Blob存储NuGet包:

Install-Package Azure.Storage.Blobs

分块写入KML到Azure Blob示例

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using SharpKml.Dom;
using SharpKml.Engine;

public async Task ExportToKmlToAzureBlob(string connectionString, string containerName, string exportBy, string geometry)
{
    var blobServiceClient = new BlobServiceClient(connectionString);
    var containerClient = blobServiceClient.GetBlobContainerClient(containerName);
    await containerClient.CreateIfNotExistsAsync();

    // 初始化全量KML文档头部并写入Blob
    var kmlHeader = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<kml xmlns=""http://www.opengis.net/kml/2.2"">
<Document>
  <name>All_{exportBy}.kml</name>";
    var allBlobClient = containerClient.GetBlockBlobClient($"All_{exportBy}.kml");
    await allBlobClient.UploadAsync(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(kmlHeader)), overwrite: true);
    var appendBlobClient = allBlobClient.AsAppendBlobClient();

    // 分页获取数据(示例用EF Core,根据你的数据库访问方式调整)
    int pageSize = 10000;
    int pageIndex = 0;
    bool hasMoreData = true;

    while (hasMoreData)
    {
        var exportedData = await _dbContext.YourGeoData
            .Skip(pageIndex * pageSize)
            .Take(pageSize)
            .ToListAsync();

        if (exportedData.Count == 0)
        {
            hasMoreData = false;
            break;
        }

        // 处理当前页数据,生成Placemark的XML片段
        var placemarkXmls = new List<string>();
        foreach (var kmlData in exportedData)
        {
            var placemark = CreatePlacemark(kmlData, geometry);
            var serializer = new KmlSerializer();
            using var writer = new StringWriter();
            serializer.Serialize(writer, placemark, true);
            placemarkXmls.Add(writer.ToString());
        }

        // 将当前页的Placemark片段追加到Blob
        var content = string.Join("\n", placemarkXmls);
        using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(content));
        await appendBlobClient.AppendBlockAsync(stream);

        pageIndex++;
    }

    // 写入KML文档尾部
    await appendBlobClient.AppendBlockAsync(new MemoryStream(System.Text.Encoding.UTF8.GetBytes("</Document></kml>")));
}

private Placemark CreatePlacemark(dynamic kmlData, string geometry)
{
    var placemark = new Placemark();
    // 复用你原代码中创建Geometry、ExtendedData的逻辑
    // 注意:此处Style通过StyleUrl引用全局定义,不再重复创建
    return placemark;
}

4. 拆分大文件为多个小KML

如果需要保留按Provider和Technology拆分的文件,同样对每个子文件采用分页+分块写入Azure Blob的方式,避免在内存中构建完整Document。

5. 禁用全量汇总文件(可选)

如果documentAll不是业务必需,直接删除这部分逻辑,可大幅降低内存占用。

额外优化建议

  • 替换dynamic为强类型实体,减少反射开销和内存占用
  • 优化坐标解析逻辑,避免重复字符串分割、替换操作
  • 考虑使用KMZ格式压缩KML文件,减少存储和传输体积(SharpKml可通过KmzFile类实现)

内容的提问来源于stack exchange,提问作者Hamlet Poghosyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:24:54