如何通过映射 Excel 工作表列标题将数据从数据表插入到现有 Excel 电子表格中?

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

我有带有列标题的宏 Excel 电子表格,有些列是计算列。

我需要做的是从数据库获取数据并填充此文件。

我在查询后检索到的数据表没有 Excel 文件中的所有列,因为 Excel 文件具有计算列。

所以我需要将数据表中的列映射到Excel文件并加载数据。 我需要确保在加载新数据之前从文件中删除现有数据,因为该文件需要每周创建一次。

我从未使用过 OpenXML Document 和 EPPlus。

尝试#1:使用 EPPlus

private static void OtehrMethod(DataTable dataTable, string filePath)
{
    // using EPPlus
    var package = new ExcelPackage(new FileInfo(filePath));

    ExcelWorksheet workSheet = package.Workbook.Worksheets["MySheet"];

    foreach (DataRow row in dataTable.Rows)
    {
        int i = 1;
        object cellValue = workSheet.Cells[2, i].Value;

        workSheet.Cells[1, 1].Value = Conver.ToInt(row["Id"]);
        // break;
        //workSheet.Cells[2, i].Value =row["First_Name"].ToString();
        //workSheet.Cells[3, i].Value = row["Last_Name"].ToString();
        //workSheet.Cells[4, i].Value = row["Job_Title"].ToString();
        //workSheet.Cells[5, i].Value = row["Skills"].ToString();
        i++;
    }

    package.Save();
}

尝试#2:使用 Open XML

private static void SomeMethod()
{
    string filePath = ConfigurationManager.AppSettings["ExcelFilePath"];
    string workingSheetName = ConfigurationManager.AppSettings["WorkingSheetName"];

    using (SpreadsheetDocument document = SpreadsheetDocument.Open(filePath, true))
    {
        // WorkbookPart workbook = document.WorkbookPart;
        WorkbookPart workbookPart = document.WorkbookPart;
        Workbook workbook = document.WorkbookPart.Workbook;

        int sheetIndex = 0;

        foreach (WorksheetPart worksheetpart in workbook.WorkbookPart.WorksheetParts)
        {
            Worksheet worksheet = worksheetpart.Worksheet;

            string sheetName = workbookPart.Workbook.Descendants<Sheet>().ElementAt(sheetIndex).Name;

            if (sheetName.ToUpper() == workingSheetName.ToUpper())
            {
                IEnumerable<Row> rows = worksheet.GetFirstChild<SheetData>().Descendants<Row>();

                foreach (Row row in rows)
                {
                    // How do I map Excel sheet column with data table column and insert the values into Excel ?

                    // Column["FirstName"] = DTRow["FirstName"]
                    //Column["LastName"] = DTRow["LastName"]
                }
            }

            sheetIndex++;
        }
    }
    // throw new NotImplementedException();
}
openxml epplus
2个回答
2
投票

首先,EPPlus 是 OpenXML 的包装器。因此,以下概念适用于两者。学习曲线可能很陡峭,所以我将从 Vincent Tan 程序开始供您检查:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;

namespace ExcelOpenXmlSetCellValue
{
    class Program
    {
        static void Main(string[] args)
        {
            string sFile = "ExcelOpenXmlSetCellValue.xlsx";
            if (File.Exists(sFile))
            {
                File.Delete(sFile);
            }
            try
            {
                BuildWorkbook(sFile);
                Console.WriteLine("Program end");
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                Console.ReadLine();
            }
        }

        private static void BuildWorkbook(string filename)
        {
            using (SpreadsheetDocument xl = SpreadsheetDocument.Create(filename, SpreadsheetDocumentType.Workbook))
            {
                WorkbookPart wbp = xl.AddWorkbookPart();
                WorksheetPart wsp = wbp.AddNewPart<WorksheetPart>();
                Workbook wb = new Workbook();
                FileVersion fv = new FileVersion();
                fv.ApplicationName = "Microsoft Office Excel";
                Worksheet ws = new Worksheet();
                SheetData sd = new SheetData();

                Row r;
                Cell c;

                r = new Row();
                // For the 2nd row. I know, it's obvious, but it's recommended
                // that you assign a value. While Excel might be able to handle
                // a Row class without the RowIndex assigned (in a certain case),
                // other applications might find that difficult, in particular,
                // your own application.
                r.RowIndex = 2;
                c = new Cell();
                // "D" for 4th column. The "2" is equal to the RowIndex of the Row
                // that the Cell class is in. Note that the number part has to be
                // equal to the RowIndex or disaster will happen. Or worse, Excel
                // spits out the "file corrupted" error and your user sees it.
                c.CellReference = "D2";
                // by default, an unassigned DataType means CellValues.Number
                //c.DataType = CellValues.Number;
                c.CellValue = new CellValue("9.81");
                r.Append(c);
                sd.Append(r);

                // What if you need more Cell's in a Row? Blank lines added for readability.
                r = new Row();
                r.RowIndex = 5;

                c = new Cell();
                // this way, the number part is always right, but you need to
                // assign the RowIndex first. This will make sense when you have
                // to append many Cell classes. This is just an alternative to
                // assigning the cell reference.
                c.CellReference = "F" + r.RowIndex;
                // There are 2 other string types, the SharedString and InlineString.
                // They are discussed in another chapter. The CellValues.String type
                // provides the most straightforward way of including text strings.
                c.DataType = CellValues.String;
                c.CellValue = new CellValue("Is");
                r.Append(c);

                c = new Cell();
                c.CellReference = "G" + r.RowIndex;
                c.DataType = CellValues.String;
                c.CellValue = new CellValue("that in");
                r.Append(c);

                c = new Cell();
                c.CellReference = "H" + r.RowIndex;
                c.DataType = CellValues.String;
                c.CellValue = new CellValue("metres per second squared?");
                r.Append(c);

                // This is for row 5
                sd.Append(r);

                // And now, to show you sometimes the code can be rearranged a little.
                // But because you need to append the Cell class to the Row class,
                // it makes sense (logically and conceptually) to initialise a new Row first.
                c = new Cell();
                c.CellReference = "F7";
                c.DataType = CellValues.String;
                c.CellValue = new CellValue("It'd better be. Just got used to the metric system.");
                r = new Row();
                r.RowIndex = 7;
                r.Append(c);
                sd.Append(r);

                ws.Append(sd);
                wsp.Worksheet = ws;

                wsp.Worksheet.Save();
                Sheets sheets = new Sheets();
                Sheet sheet = new Sheet();
                sheet.Name = "Sheet1";
                sheet.SheetId = 1;
                sheet.Id = wbp.GetIdOfPart(wsp);
                sheets.Append(sheet);
                wb.Append(fv);
                wb.Append(sheets);

                xl.WorkbookPart.Workbook = wb;
                xl.WorkbookPart.Workbook.Save();
                xl.Close();
            }
        }
    }
}

那么他书中的摘录可能会在概念上有所帮助:

因此 SheetData 类包含 Row 类作为子类。每个 Row 类都包含 Cell 类作为子类。 Row 类代表一行 在电子表格中。 Cell 类代表电子表格中的一个单元格。 有时,显而易见的事情仍然需要说。

建议您为 RowIndex 属性赋值 行类。这基本上是该 Row 类的行号。如果 它是第 4 行,分配 4。 RowIndex 属性是可选的 开放 XML 规范,但电子表格应用程序可能没有 处理这个问题的方法。即使 Excel 也可以处理空白的 RowIndex 属性 在特殊条件下正确(我们将在稍后讨论) 本章)。

对于 Cell 类,您需要注意以下属性: 

  • DataType - 单元格值的数据类型。您很可能会处理 CellValues.Number 和 CellValues.String。
  • CellReference - “A1 格式”的单元格引用。这意味着列字母后跟行索引。例如,“C5”表示 第 3 列,第 5 行。有“R1C1 格式”(“R”然后是行索引, 然后是“C”,然后是列索引,为上一个提供“R5C3” 示例),但我们不涉及这一点。默认为“A1 格式” 优秀。
  • CellValue - 操作发生的位置。您可能会注意到 CellValue 实际上是一个类,它的主要值是一个字符串。这 意味着您也以字符串形式存储数字值(DataType 属性决定数据类型)。
  • CellFormula - 您有单元格公式的地方。但你可以在另一章中读到它。以上3个属性是常用的 手柄。

还有一件事。 Row 类必须附加到 SheetData 类按 RowIndex 升序排列。必须附加 Cell 类 按升序排列到其 Row 类。这是没有商量余地的。 否则 Excel 会因错误呕吐而窒息。

希望这足以让您走上正轨。如果没有,我会回来查看更多问题。

在回答您的其他问题时,对于计算列,请在为计算列构建的单元格的右侧构建计算。


2
投票

更加清晰。您处于我没有亲自编码的领域,但每个工作表的 SheetData 将是清除数据的位置。下面是 Vincent Tan 的另一个程序,展示了如何在现有工作表中添加和更改数据:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;

namespace ExcelOpenXmlFromTemplate
{
    class Program
    {
        static void Main(string[] args)
        {
            // Switch between an Excel file with the styles and stuff
            // and an actual Excel template file.
            // I suggest you check out the accompanying template files first
            // to compare how the original and final files will look like.

            //string sFileTemplate = "FinancialYearReport.xlsx";
            string sFileTemplate = "FinancialYearReportTemplate.xltx";
            string sFile = "FinancialYearReportFinal.xlsx";
            if (File.Exists(sFile))
            {
                File.Delete(sFile);
            }

            if (sFileTemplate.ToLower().EndsWith(".xlsx"))
            {
                File.Copy(sFileTemplate, sFile);
            }
            else
            {
                // ends with .xltx
                // Excel template files work differently. You can't work on it
                // directly and then save the result to a different file.
                // More details on this in the accompanying PDF.
                byte[] ba = File.ReadAllBytes(sFileTemplate);
                using (MemoryStream ms = new MemoryStream())
                {
                    ms.Write(ba, 0, ba.Length);
                    using (SpreadsheetDocument xl = SpreadsheetDocument.Open(ms, true))
                    {
                        xl.ChangeDocumentType(SpreadsheetDocumentType.Workbook);
                        xl.Close();
                    }
                    File.WriteAllBytes(sFile, ms.ToArray());
                }
            }

            try
            {
                BuildWorkbookFromTemplate(sFile);
                Console.WriteLine("Program end");
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                Console.ReadLine();
            }
        }

        private static void BuildWorkbookFromTemplate(string filename)
        {
            string sSheetname = "FYReport";
            // take note that we're opening a file, and not creating a file as before
            using (SpreadsheetDocument xl = SpreadsheetDocument.Open(filename, true))
            {
                WorkbookPart wbp = xl.WorkbookPart;

                // Get the worksheet with the required name.
                // To be used to match the ID for the required sheet data
                // because the Sheet class and the SheetData class aren't
                // linked to each other directly.
                Sheet s = null;
                if (wbp.Workbook.Sheets.Elements<Sheet>().Count(nm => nm.Name == sSheetname) == 0)
                {
                    // no such sheet with that name
                    xl.Close();
                    return;
                }
                else
                {
                    s = (Sheet)wbp.Workbook.Sheets.Elements<Sheet>().Where(nm => nm.Name == sSheetname).First();
                }

                WorksheetPart wsp = (WorksheetPart)xl.WorkbookPart.GetPartById(s.Id.Value);
                SheetData sd = (SheetData)wsp.Worksheet.GetFirstChild<SheetData>();

                // We will update in 2 ways.
                // 1) Get the required cell, do the updates on it, then save the worksheet.
                // 2) Create a Cell with our data, then throw it to the SheetData, then save the worksheet.
                //    If the required cell already exists, we overwrite it. If not, we create it.

                // The difference between the 2 is that, if your worksheet doesn't have the required cell,
                // it becomes more difficult, because you'll have to also append it to the row.
                // More details explained in the accompanying PDF.

                // I left the saving of the worksheet out of the UpdateCell() function
                // because I want to be consistent with the GetCell() version of updating.
                // So wsp.Worksheet.Save(); is done after every cell modification.
                // Otherwise I'd put it at the end of the update function.
                // The code is written for you to understand what's going on, not for optimisation.

                int iPreviousYear = DateTime.Now.Year - 1;
                int iTheYearBeforeThat = iPreviousYear - 1;

                Cell c = null;

                // main title
                c = GetCell(sd, "A", 1);
                if (c != null)
                {
                    if (c.DataType == CellValues.SharedString)
                    {
                        c.DataType = CellValues.String;
                    }
                    c.CellValue = new CellValue(string.Format("Financial Year Report For {0}/{1}", iTheYearBeforeThat, iPreviousYear));
                    // I was going to show you how to work with an existing value like so:
                    // c.CellValue = new CellValue(string.Format("{0} {1}/{2}", c.CellValue.Text.Trim(), iTheYearBeforeThat, iPreviousYear));
                    // But the existing value is a string, and Excel had kindly saved it into the SharedString table
                    // when I created the template file.
                    // The shared string concept makes things a little more interesting if you really want to work with it.
                    // For now, it's what-you-see-is-what-you-get, which is much simpler.
                    wsp.Worksheet.Save();
                }

                // description of the year before the last
                c = GetCell(sd, "A", 4);
                if (c != null)
                {
                    if (c.DataType == CellValues.SharedString)
                    {
                        c.DataType = CellValues.String;
                    }
                    c.CellValue = new CellValue(string.Format("Year {0}", iTheYearBeforeThat));
                    wsp.Worksheet.Save();
                }

                // description of last year
                // I'll show you the difference in code for the 2nd method of updating
                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "A13";
                c.CellValue = new CellValue(string.Format("Year {0}", iPreviousYear));
                UpdateCell(sd, c, 13);
                wsp.Worksheet.Save();

                // We will update the revenue and operational costs of "the year before the last" FY.
                // You would probably retrieve the values from a database and fill it here.
                // I don't have any meaningful data anyway, so I'm gonna cheat with a randomiser...
                // I'll use the 2nd method of updating for convenience.
                Random rd = new Random();
                int i;

                for (i = 5; i <= 8; ++i)
                {
                    // revenue values
                    c = new Cell();
                    c.DataType = CellValues.Number;
                    c.CellReference = "B" + i.ToString();
                    // range from 8.0 to 12.0
                    c.CellValue = new CellValue((rd.NextDouble() * 4.0 + 8.0).ToString("f2"));
                    UpdateCell(sd, c, (uint)i);
                    wsp.Worksheet.Save();

                    // operational cost values
                    c = new Cell();
                    c.DataType = CellValues.Number;
                    c.CellReference = "C" + i.ToString();
                    // range from 0.5 to 1.5
                    c.CellValue = new CellValue((rd.NextDouble() + 0.5).ToString("f2"));
                    UpdateCell(sd, c, (uint)i);
                    wsp.Worksheet.Save();
                }

                // Previous financial year's revenue and operational cost values
                for (i = 14; i <= 17; ++i)
                {
                    // revenue values
                    c = new Cell();
                    c.DataType = CellValues.Number;
                    c.CellReference = "B" + i.ToString();
                    // range from 9.0 to 14.0
                    // We've got to have increased revenue, right? :)
                    c.CellValue = new CellValue((rd.NextDouble() * 5.0 + 9.0).ToString("f2"));
                    UpdateCell(sd, c, (uint)i);
                    wsp.Worksheet.Save();

                    // operational cost values
                    c = new Cell();
                    c.DataType = CellValues.Number;
                    c.CellReference = "C" + i.ToString();
                    // range from 0.4 to 1.4
                    // We've got to have decreased costs, right? :)
                    c.CellValue = new CellValue((rd.NextDouble() + 0.4).ToString("f2"));
                    UpdateCell(sd, c, (uint)i);
                    wsp.Worksheet.Save();
                }

                // this section is to show you that UpdateCell() works for
                // out of order cells in the same row. Go ahead and mix up
                // the order of the 5 cells (A25, B25, C25, D25 and E25) in
                // the code and check that they are still ordered correctly in the file.
                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "B25";
                c.CellValue = new CellValue("2nd");
                UpdateCell(sd, c, 25u);
                wsp.Worksheet.Save();

                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "E25";
                c.CellValue = new CellValue("5th");
                UpdateCell(sd, c, 25u);
                wsp.Worksheet.Save();

                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "C25";
                c.CellValue = new CellValue("3rd");
                UpdateCell(sd, c, 25u);
                wsp.Worksheet.Save();

                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "A25";
                c.CellValue = new CellValue("1st");
                UpdateCell(sd, c, 25u);
                wsp.Worksheet.Save();

                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "D25";
                c.CellValue = new CellValue("4th");
                UpdateCell(sd, c, 25u);
                wsp.Worksheet.Save();

                // Credits!
                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "A27";
                // well, we wouldn't want upper management to think this
                // financial report was *easy* to generate, right? Claim credit!
                c.CellValue = new CellValue("Generated by Vincent");
                UpdateCell(sd, c, 27u);
                wsp.Worksheet.Save();

                c = new Cell();
                c.DataType = CellValues.String;
                c.CellReference = "E27";
                // you could show upper management that you're working hard,
                // and print a time that's in the wee hours of the morning...
                // But shame on you...
                c.CellValue = new CellValue(string.Format("Generated at {0}", DateTime.Now.ToString("dd MMM yyyy HH:mm:ss")));
                UpdateCell(sd, c, 27u);
                wsp.Worksheet.Save();

                // we have formulae and charts to update in the template
                xl.WorkbookPart.Workbook.CalculationProperties.ForceFullCalculation = true;
                xl.WorkbookPart.Workbook.CalculationProperties.FullCalculationOnLoad = true;

                xl.WorkbookPart.Workbook.Save();
                xl.Close();
            }
        }

        private static Cell GetCell(SheetData sd, string ColumnName, UInt32 RowIndex)
        {
            // There's a small chance that the row has no RowIndex assigned.
            // You should know that the property RowIndex is optional.
            // This means the following will fail.
            // But we can't do much about a missing RowIndex in the template.
            Row r = null;
            Cell c = null;
            if (sd.Elements<Row>().Count(ri => ri.RowIndex == RowIndex) > 0)
            {
                r = (Row)sd.Elements<Row>().Where(ri => ri.RowIndex == RowIndex).First();
            }

            if (r != null)
            {
                string sCellReference = ColumnName.ToUpper() + RowIndex.ToString();
                if (r.Elements<Cell>().Count(cr => cr.CellReference.Value == sCellReference) > 0)
                {
                    c = (Cell)r.Elements<Cell>().Where(cr => cr.CellReference.Value == sCellReference).First();
                }
            }

            return c;
        }

        private static void UpdateCell(SheetData sd, Cell CellData, UInt32 RowIndex)
        {
            // There's a small chance that the row has no RowIndex assigned.
            // You should know that the property RowIndex is optional.
            // This means the following will fail.
            // But we can't do much about a missing RowIndex in the template.
            Row r = null;
            if (sd.Elements<Row>().Count(ri => ri.RowIndex == RowIndex) == 0)
            {
                // There's no row at this index, so it also means there's no cell
                // with the data required. So just create the row with the cell.
                r = new Row();
                r.RowIndex = RowIndex;
                r.Append(CellData);
                sd.Append(r);
            }
            else
            {
                r = (Row)sd.Elements<Row>().Where(ri => ri.RowIndex == RowIndex).First();
                // we will assume the cell's CellReference is consistent with the given RowIndex
                if (r.Elements<Cell>().Count(cr => cr.CellReference.Value == CellData.CellReference.Value) > 0)
                {
                    // there's an existing cell
                    Cell c = (Cell)r.Elements<Cell>().Where(cr => cr.CellReference.Value == CellData.CellReference.Value).First();
                    c.DataType = CellData.DataType;
                    // reset to "normal" string value if it's a shared string.
                    // Otherwise, we'll have to go change the value in the SharedStringTable class,
                    // and that's out of scope for now. Let's focus on updating cells, shall we?
                    // We are also assuming we're not stupid enough to set the given cell's
                    // data type to be SharedString...
                    if (c.DataType == CellValues.SharedString)
                    {
                        c.DataType = CellValues.String;
                    }
                    c.CellValue = new CellValue(CellData.CellValue.Text);

                    // set any other additional properties you want
                }
                else
                {
                    // no such cell

                    IEnumerable<Cell> iec = r.Elements<Cell>();
                    // in case there are no cells in the row
                    if (iec.Count() == 0)
                    {
                        r.Append(CellData);
                    }
                    else
                    {
                        bool bFound = false;
                        // cells need to be in order of their CellReference values
                        foreach (Cell c in iec)
                        {
                            // We assume the existing Cells are already in order.
                            // This means the moment our CellData's CellReference is before
                            // a particular existing Cell's CellReference, we can insert before
                            // that particular existing Cell.
                            if (string.Compare(CellData.CellReference.Value, c.CellReference.Value) < 0)
                            {
                                bFound = true;
                                r.InsertBefore(CellData, c);
                                break;
                            }
                        }
                        // well, we have to insert our CellData *somewhere*.
                        // If not found in the loop above, then it must have the highest CellReference.
                        // So we just append it at the end of the row.
                        if (!bFound)
                        {
                            r.Append(CellData);
                        }
                    }

                    // we don't append the row to the SheetData variable because
                    // the row already exists. We just need to append the cell.
                }
            }
        }
    }
}

Vincent 的示例使用设置为模板的工作簿,不涉及清除单元格,但他确实展示了如何覆盖单元格。对我来说,他似乎涵盖了很多可能出错的事情。我会尝试先什么都不覆盖单元格,看看是否可行。

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