【问题标题】:Optimizing the speed of importing data from Excel - IronXL vs. Office Interop优化从 Excel 导入数据的速度 - IronXL 与 Office Interop
【发布时间】:2022-09-29 15:03:36
【问题描述】:

我编写了一个程序,它从 .xlsx 文件中导入数据,然后根据导入的数据创建许多 XML 文件。现在,起初我尝试使用 IronXL 来做这件事,但后来我意识到如果我想发布我的程序,这不是一个免费的选择,所以我尝试使用 Office Interop 做同样的事情。所以现在我有两个代码,两者都做同样的事情并且他们做对了,但是 IronXL 方法更快(~8 秒 vs ~40 秒检查两个工作表,但总的来说我将有大约 10-15 个工作表所以时间显然会变长)。我可以以任何方式优化我的代码,还是只是 IronXL 方法会更快,而我对此无能为力? 这是我的 Office 互操作代码:

static public void LoadExcelDataInterop()
        {
            //Normally floats are imported as numbers with a comma, e.g. 12,5 rather than 12.5
            //Everywhere else in the code, the correct format is 12.5
            //When I printed the weapons\' data into a file, the format was with a comma and this caused errors
            System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
            customCulture.NumberFormat.NumberDecimalSeparator = \".\";
            System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;

            Excel.Application xlApp = new Excel.Application();
            Excel.Workbook wb = xlApp.Workbooks.Open(@\"D:\\Outward - r2modman\\Mods\\OutwardDe\\profiles\\Outward\\BepInEx\\plugins\\Outward_Mod_Weapons_ValuesToImport.xlsx\", 0, true, 5, \"\", \"\", true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, \"\\t\", false, false, 0, true, 1, 0);

            foreach (Excel.Worksheet ws in wb.Worksheets)
            {
                if (ws.Name == \"Swords_1h\" || ws.Name == \"Swords_2h\"
                    //     || ws.Name == \"Axes_1h\"    || ws.Name == \"Axes_2h\"
                    //     || ws.Name == \"Maces_1h\"   || ws.Name == \"Maces_2h\"
                    //     || ws.Name == \"Halberds\"   || ws.Name == \"Staves\"
                    //     || ws.Name == \"Spears\"     || ws.Name == \"Gauntlets\"
                    //     || ws.Name == \"Bows\"       || ws.Name == \"Shields\"
                    //     || ws.Name == \"Chakrams\"   || ws.Name == \"Daggers\"     || ws.Name == \"Pistols\"
                    )
                {

                    for (int x = 2; x > 0; x++)
                    {
                        var cell = ws.Cells[x, 2] as Excel.Range;
                        string cell_name = (string)cell.Value;

                        if (cell_name == null) { break; }
                        else
                        {
                            dict_Weapons.Add(cell_name, new SL_Weapon(wb, ws, cell));
                        }
                    }
                }
                Marshal.ReleaseComObject(ws);
            }

            wb.Close(false, null, null);
            xlApp.Quit();

            Marshal.ReleaseComObject(wb);
            Marshal.ReleaseComObject(xlApp);
        }

这是 SL_Weapon for Interop 的构造函数:

public SL_Weapon(Excel.Workbook wb, Excel.Worksheet ws, Excel.Range cell)
            {
                StatsHolder = new SL_WeaponStats
                {
                    Damage_Bonus = new float[9],
                    Damage_Resistance = new float[9],
                };
                var SH = ((SL_WeaponStats)StatsHolder);
                SH.BaseDamage = new List<SL_Damage>();
                Effects = new List<OE_Effect>();

                for (int i = 1; i <= ws.Columns.Count; i++)
                {
                    var heading = ws.Cells[1, i] as Excel.Range;
                    string headingName = (string)heading.Value;

                    var workingCell = ws.Cells[cell.Row, heading.Column] as Excel.Range;
                    var workingCell_nextCell = ws.Cells[cell.Row, heading.Column + 1] as Excel.Range;

                    if (headingName == null) { break; }
                    else if (workingCell.Value == null) { continue; }
                    else
                    {
                        if (headingName == \"Name\") { Name = (string)workingCell.Value; }
                        if (headingName == \"ID\") { Target_ItemID = New_ItemID = (int)workingCell.Value; }
                        
                        if (headingName == \"DMG Physical\") { SH.BaseDamage.Add(new SL_Damage { Damage = (float)workingCell.Value, Type = \"Physical\" }); }
                        if (headingName == \"DMG 2\") { SH.BaseDamage.Add(new SL_Damage { Damage = (float)workingCell.Value, Type = (string)workingCell_nextCell.Value }); }
                        if (headingName == \"DMG 3\") { SH.BaseDamage.Add(new SL_Damage { Damage = (float)workingCell.Value, Type = (string)workingCell_nextCell.Value }); }

                        //OPTION 1 for StatsHolder fields - doesn\'t work, throws an error (\'Object of type \'System.Single\' cannot be converted to type \'System.Int32\')
                        /*
                        FieldInfo[] fields = typeof(SL_WeaponStats).GetFields();
                        foreach (var field in fields)
                        {
                            if (headingName == field.Name) { field.SetValue(StatsHolder, (float)workingCell.Value); }
                        }
                        */

                        //OPTION 2 for StatsHolder fields
                        if (headingName == \"MaxDurability\") { SH.MaxDurability = (int)workingCell.Value; }
                        if (headingName == \"RawWeight\") { SH.RawWeight = (float)workingCell.Value; }
                        if (headingName == \"BaseValue\") { SH.BaseValue = (int)workingCell.Value; }

                        if (headingName == \"StamCost\") { SH.StamCost = (float)workingCell.Value; }
                        if (headingName == \"AttackSpeed\") { SH.AttackSpeed = (float)workingCell.Value; }
                        if (headingName == \"Impact\") { SH.Impact = (float)workingCell.Value; }
                        
                        if (workingCell_nextCell.Value != null) 
                        {
                            if (headingName == \"Effect 1\" || headingName == \"Effect 2\" || headingName == \"Effect 3\")
                            { Effects.Add(new OE_Effect { StatusEffect = (string)workingCell.Value, Buildup = (int)workingCell_nextCell.Value }); }

                        }
                    }

                    foreach (Excel.Worksheet worksheet in wb.Worksheets)
                    {
                        if (worksheet.Name == \"Damage_BonusOrRes\")
                        {
                            var item_dmgBonus = ((SL_EquipmentStats)StatsHolder).Damage_Bonus;
                            var item_dmgResistance = ((SL_EquipmentStats)StatsHolder).Damage_Resistance;

                            for (int x = 2; x < 10; x++ )
                            {
                                Excel.Range workingCell2 = worksheet.Cells[x, 1] as Excel.Range;
                                string workingCell2Name = (string)workingCell2.Value;
                                if (workingCell2Name == null) { break; }
                                else if (workingCell2.Value.ToString() == cell.Value.ToString())
                                {
                                    for (int y = 0; y < 6; y++)
                                    {
                                        if ((worksheet.Cells[x, y + 2] as Excel.Range).Value != null)
                                        {
                                            item_dmgBonus[y] = (float)(worksheet.Cells[x, y + 2] as Excel.Range).Value;
                                        }
                                        if ((worksheet.Cells[x, y + 8] as Excel.Range).Value != null)
                                        {
                                            item_dmgResistance[y] = (float)(worksheet.Cells[x, y + 8] as Excel.Range).Value;
                                        }

                                    }
                                }
                            }
                            
                        }
                    }
                }
            }

这是 IronXL 的代码:

public static void LoadExcelDataIronXL()
        {
            //Normally IronXL imports floats as numbers with a comma, e.g. 12,5 rather than 12.5
            //Everywhere else in the code, the correct format is 12.5
            //When I printed the weapons\' data into a file, the format was with a comma
            //And later when I copied it to the other Programme, I was getting errors because the format should be with a dot
            System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone();
            customCulture.NumberFormat.NumberDecimalSeparator = \".\";
            System.Threading.Thread.CurrentThread.CurrentCulture = customCulture;


            WorkBook wb = WorkBook.Load(\"D:/Outward - r2modman/Mods/OutwardDe/profiles/Outward/BepInEx/plugins/Outward_Mod_Weapons_ValuesToImport.xlsx\");

            void LoadExcel_Weapons()
            {
                foreach (var ws in wb.WorkSheets)
                {
                    if (ws.Name == \"Swords_1h\" || ws.Name == \"Swords_2h\"
                    //     || ws.Name == \"Axes_1h\"    || ws.Name == \"Axes_2h\"
                    //     || ws.Name == \"Maces_1h\"   || ws.Name == \"Maces_2h\"
                    //     || ws.Name == \"Halberds\"   || ws.Name == \"Staves\"
                    //     || ws.Name == \"Spears\"     || ws.Name == \"Gauntlets\"
                    //     || ws.Name == \"Bows\"       || ws.Name == \"Shields\"
                    //     || ws.Name == \"Chakrams\"   || ws.Name == \"Daggers\"     || ws.Name == \"Pistols\"
                       )
                    {
                        foreach (var item in ws.Columns[1])
                        {
                            if (item.IsEmpty) { break; }
                            else if (item.RowIndex == 0) { continue; }
                            else
                            {
                                dict_Weapons.Add(item.Value.ToString(), new SL_Weapon(wb, ws.Name, item));item.Value.ToString());
                            }
                        }
                    }
                }
            }
            LoadExcel_Weapons();
        }

IronXL 的 SL_weapon 的构造函数:

public SL_Weapon(WorkBook wb, string worksheetName, Cell cell)
            {
                WorkSheet ws = wb.GetWorkSheet(worksheetName);
                RangeRow row = ws.Rows[cell.RowIndex];
                StatsHolder = new SL_WeaponStats
                {
                    Damage_Bonus = new float[9],
                    Damage_Resistance = new float[9],
                };
                var SH = ((SL_WeaponStats)StatsHolder);
                SH.BaseDamage = new List<SL_Damage>();
                Effects = new List<OE_Effect>();

                foreach (var heading in ws.Rows[0])
                {
                    var headingColumn = heading.ColumnIndex;
                    var headingName = heading.ToString();

                    if (headingName == \"Name\") { Name = row.Columns[headingColumn].ToString(); }
                    if (headingName == \"ID\") { Target_ItemID = New_ItemID = row.Columns[headingColumn].IntValue; }

                    if (headingName == \"DMG Physical\") { SH.BaseDamage.Add(new SL_Damage { Damage = row.Columns[headingColumn].FloatValue, Type = \"Physical\" }); }
                    if (headingName == \"DMG 2\") { SH.BaseDamage.Add(new SL_Damage { Damage = row.Columns[headingColumn].FloatValue, Type = row.Columns[headingColumn + 1].ToString() }); }
                    if (headingName == \"DMG 3\") { SH.BaseDamage.Add(new SL_Damage { Damage = row.Columns[headingColumn].FloatValue, Type = row.Columns[headingColumn + 1].ToString() }); }
                    
                    if (headingName == \"MaxDurability\")     { SH.MaxDurability = row.Columns[headingColumn].IntValue; }
                    if (headingName == \"RawWeight\")         { SH.RawWeight = row.Columns[headingColumn].FloatValue; }
                    if (headingName == \"BaseValue\")         { SH.BaseValue = row.Columns[headingColumn].IntValue; }

                    if (headingName == \"StamCost\")          { SH.StamCost = row.Columns[headingColumn].FloatValue; }
                    if (headingName == \"AttackSpeed\")       { SH.AttackSpeed = row.Columns[headingColumn].FloatValue; }
                    if (headingName == \"Impact\")            { SH.Impact = row.Columns[headingColumn].FloatValue; }
                    

                    if (headingName == \"Effect 1\") { Effects.Add(new OE_Effect { StatusEffect = row.Columns[headingColumn].ToString(), Buildup = row.Columns[headingColumn + 1].IntValue }); }
                    if (headingName == \"Effect 2\") { Effects.Add(new OE_Effect { StatusEffect = row.Columns[headingColumn].ToString(), Buildup = row.Columns[headingColumn + 1].IntValue }); }
                    if (headingName == \"Effect 3\") { Effects.Add(new OE_Effect { StatusEffect = row.Columns[headingColumn].ToString(), Buildup = row.Columns[headingColumn + 1].IntValue }); }

                }

                var ws_DmgBonusOrRes = wb.GetWorkSheet(\"Damage_BonusOrRes\");
                var item_dmgBonus = ((SL_EquipmentStats)StatsHolder).Damage_Bonus;
                var item_dmgResistance = ((SL_EquipmentStats)StatsHolder).Damage_Resistance;

                foreach (var cell1 in ws_DmgBonusOrRes.Columns[0])
                {
                    if (cell1.IsEmpty) { break; }
                    else if (cell1.RowIndex == 0) { continue; }
                    else if (cell1.Value.ToString() == cell.ToString())
                    {
                        for (int i = 0; i < 6; i++)
                        {
                            item_dmgBonus[i] = ws_DmgBonusOrRes.Rows[cell1.RowIndex].Columns[i + 1].FloatValue;
                            item_dmgResistance[i] = ws_DmgBonusOrRes.Rows[cell1.RowIndex].Columns[i + 7].FloatValue;
                        }
                    }
                }
            }

标签: c# excel performance excel-interop ironxl


【解决方案1】:

IronXL 在内部使用 NPOI。如果您下载 nuget 包并反编译 dll,您会看到它嵌入了以下代码:NPOI、BouncyCastle、Newtonsoft、ICSharpCode、ImageSharp,可能还有其他。在我看来,他们采用了一堆开源代码并将其打包以使其看起来独立,以便他们可以为此收费。我对为自己的作品收费没有任何问题,但是当你试图出售别人的作品时,感觉有点阴暗。

无论如何...回答您有关 Excel 性能的问题。读取 Excel 数据的最快库(据我所知)是Sylvan.Data.Excel。我已经将它与其他几个流行的 .NET Excel 库和it is the fastest by quite a wide margin 进行了基准测试。我还应该指出,我是这个库的作者,所以可能想自己验证我的性能声明。

我的库将 Excel 数据公开为 DbDataReader,这是用于读取数据记录的标准 ADO.NET 抽象类。您还可以查看Sylvan.Data,它提供了一个数据绑定器,可以轻松地将 DbDataReader 绑定到 .NET 对象。

使用 Sylvan.Data.Excel 和 Sylvan.Data 您的代码可能很简单:

using var reader = ExcelDataReader.Create("mydata.xlsx");
List<Weapon> weapons = reader.GetRecords<Weapon>().ToList();

它可能会比这更复杂一些,因为看起来你的绑定代码有一些复杂性,但你不应该有任何麻烦来适应我的库。

【讨论】:

    【解决方案2】:

    IronXL 是一个多用途工具,提供整个 Excel 编辑和转换工具生态系统,而不是速度导向的数据阅读器。

    数据读取器只会根据您的要求从 XML 中读取缓存的单元格值,而不是解析或编辑。

    我们(免责声明:我为 Iron Software 工作)喜欢添加快速只读选项以进一步提高 IronXL 速度的想法,因此已将其记录为功能请求。

    IronXL 可免费用于开发,但商业用途确实需要最低 499 美元的许可证。许可信息显示在https://ironsoftware.com/csharp/excel/licensing/

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-18
      • 1970-01-01
      • 2011-08-01
      • 2022-10-30
      • 1970-01-01
      • 1970-01-01
      • 2017-09-17
      • 1970-01-01
      相关资源
      最近更新 更多