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

如何用C#高效读取合并大数据Excel工作表并验证数据

Efficiently Process Large Excel Files: Read, Merge, and Validate

Hey Brian, your current Excel Interop approach is slow because every cell access is a cross-process COM call—super inefficient for 25k-row sheets. Let's switch to a .NET library built for bulk Excel operations, like EPPlus (for .xlsx files, free for non-commercial use) or NPOI (supports .xls/.xlsx, open-source). Below is a complete, optimized solution covering all your requirements:


1. Define Proper Entity Classes

First, fix the mismatched type conversions in your original code (e.g., InfoDesc was incorrectly parsed as int) and create a combined class for merged data:

// Sheet1 data model
public class ImportSheet1
{
    public string Info { get; set; }
    public int ID { get; set; }
    public string InfoDesc { get; set; }
    public string InfoType { get; set; }
    public string DataType { get; set; }
    public string RateFormat { get; set; }
    public string InfoID => $"{Info}{ID}"; // Auto-generated composite key
}

// Sheet2 data model
public class ImportSheet2
{
    public string Info { get; set; }
    public int ID { get; set; }
    public int CertNum { get; set; }
    public decimal CertVal { get; set; } // Use decimal for currency values
    public string InfoID => $"{Info}{ID}";
}

// Merged data model with validation errors
public class CombinedData
{
    public string Info { get; set; }
    public int ID { get; set; }
    public string InfoDesc { get; set; }
    public string InfoType { get; set; }
    public string DataType { get; set; }
    public string RateFormat { get; set; }
    public int CertNum { get; set; }
    public decimal CertVal { get; set; }
    public List<string> ValidationErrors { get; set; } = new List<string>();
}

2. Bulk Read Excel Data

EPPlus reads the entire worksheet into an in-memory 2D array, eliminating slow per-cell COM calls:

using OfficeOpenXml;
using System.IO;

public List<ImportSheet1> ReadSheet1(string filePath)
{
    var sheet1List = new List<ImportSheet1>();
    
    using (var package = new ExcelPackage(new FileInfo(filePath)))
    {
        var worksheet = package.Workbook.Worksheets["Sheet1"];
        var dataRange = worksheet.Dimension;
        if (dataRange == null) return sheet1List;
        
        // Load all data into a 2D array in one go
        var data = worksheet.Cells[1, 1, dataRange.Rows, dataRange.Columns].Value as object[,];
        
        // Skip header row (start at row 2)
        for (int row = 2; row <= dataRange.Rows; row++)
        {
            sheet1List.Add(new ImportSheet1
            {
                Info = Convert.ToString(data[row, 1]),
                ID = TryParseInt(data[row, 2], out int id) ? id : 0,
                InfoDesc = Convert.ToString(data[row, 3]),
                InfoType = Convert.ToString(data[row, 4]),
                DataType = Convert.ToString(data[row, 5]),
                RateFormat = data[row, 6] != null ? Convert.ToString(data[row, 6]) : null
            });
        }
    }
    return sheet1List;
}

public List<ImportSheet2> ReadSheet2(string filePath)
{
    var sheet2List = new List<ImportSheet2>();
    
    using (var package = new ExcelPackage(new FileInfo(filePath)))
    {
        var worksheet = package.Workbook.Worksheets["SHEET2"];
        var dataRange = worksheet.Dimension;
        if (dataRange == null) return sheet2List;
        
        var data = worksheet.Cells[1, 1, dataRange.Rows, dataRange.Columns].Value as object[,];
        
        for (int row = 2; row <= dataRange.Rows; row++)
        {
            sheet2List.Add(new ImportSheet2
            {
                Info = Convert.ToString(data[row, 1]),
                ID = TryParseInt(data[row, 2], out int id) ? id : 0,
                CertNum = TryParseInt(data[row, 3], out int certNum) ? certNum : 0,
                CertVal = TryParseDecimal(data[row, 4], out decimal certVal) ? certVal : 0
            });
        }
    }
    return sheet2List;
}

// Helper methods to avoid conversion exceptions
private bool TryParseInt(object value, out int result)
{
    result = 0;
    return value != null && int.TryParse(value.ToString(), out result);
}

private bool TryParseDecimal(object value, out decimal result)
{
    result = 0;
    return value != null && decimal.TryParse(value.ToString(), out result);
}

3. Merge Data Efficiently

Use a dictionary for O(1) lookups instead of nested loops or slow LINQ joins:

public List<CombinedData> CombineData(List<ImportSheet1> sheet1, List<ImportSheet2> sheet2)
{
    // Create a dictionary for fast Sheet2 lookups
    var sheet2Lookup = sheet2.ToDictionary(item => item.InfoID);
    var combinedList = new List<CombinedData>();
    
    // Merge Sheet1 items with matching Sheet2 data
    foreach (var sheet1Item in sheet1)
    {
        var combined = new CombinedData
        {
            Info = sheet1Item.Info,
            ID = sheet1Item.ID,
            InfoDesc = sheet1Item.InfoDesc,
            InfoType = sheet1Item.InfoType,
            DataType = sheet1Item.DataType,
            RateFormat = sheet1Item.RateFormat
        };
        
        if (sheet2Lookup.TryGetValue(sheet1Item.InfoID, out var sheet2Item))
        {
            combined.CertNum = sheet2Item.CertNum;
            combined.CertVal = sheet2Item.CertVal;
        }
        else
        {
            combined.ValidationErrors.Add($"No matching Sheet2 entry for InfoID: {sheet1Item.InfoID}");
        }
        
        ValidateCombinedData(combined);
        combinedList.Add(combined);
    }
    
    // Add Sheet2 items that don't exist in Sheet1
    foreach (var sheet2Item in sheet2)
    {
        if (!sheet1.Any(item => item.InfoID == sheet2Item.InfoID))
        {
            var combined = new CombinedData
            {
                Info = sheet2Item.Info,
                ID = sheet2Item.ID,
                CertNum = sheet2Item.CertNum,
                CertVal = sheet2Item.CertVal,
                ValidationErrors = { $"No matching Sheet1 entry for InfoID: {sheet2Item.InfoID}" }
            };
            
            ValidateCombinedData(combined);
            combinedList.Add(combined);
        }
    }
    
    return combinedList;
}

4. Validate Row Data

Implement your custom validation rules to check for empty fields, invalid data types, etc.:

private void ValidateCombinedData(CombinedData data)
{
    // Check required fields
    if (string.IsNullOrEmpty(data.Info))
    {
        data.ValidationErrors.Add("Info field cannot be empty");
    }
    
    if (data.ID <= 0)
    {
        data.ValidationErrors.Add("ID must be a positive integer");
    }
    
    // Validate data type consistency
    if (!string.IsNullOrEmpty(data.DataType) && data.DataType.Equals("Numeric", StringComparison.OrdinalIgnoreCase))
    {
        if (string.IsNullOrEmpty(data.RateFormat) || !data.RateFormat.Contains("$$"))
        {
            data.ValidationErrors.Add($"Invalid RateFormat for Numeric DataType: {data.RateFormat}");
        }
        
        if (data.CertVal <= 0)
        {
            data.ValidationErrors.Add("CertVal must be greater than 0 for Numeric DataType");
        }
    }
    
    if (data.CertNum <= 0)
    {
        data.ValidationErrors.Add("CertNum must be a positive integer");
    }
}

5. Usage Example

string excelFilePath = @"C:\your-file-path.xlsx";
var sheet1Data = ReadSheet1(excelFilePath);
var sheet2Data = ReadSheet2(excelFilePath);
var mergedData = CombineData(sheet1Data, sheet2Data);

// Print rows with validation errors
var errorRows = mergedData.Where(row => row.ValidationErrors.Any());
foreach (var row in errorRows)
{
    Console.WriteLine($"InfoID: {row.Info}{row.ID} | Errors: {string.Join(", ", row.ValidationErrors)}");
}

Why This Is Faster

  • Bulk Data Loading: Reads the entire worksheet into memory in one operation, avoiding thousands of slow COM calls.
  • Dictionary Lookups: Merging uses hash tables for O(1) lookups, reducing time complexity from O(n*m) to O(n+m).
  • Safe Type Conversions: Helper methods prevent costly exceptions during parsing.

Notes

  • For .xls files, replace EPPlus with NPOI (open-source, supports both formats).
  • EPPlus requires a commercial license for business use; check their licensing terms if needed.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:01:16