카테고리 보관물: .Net / C#

OpenXML을 이용한 엑셀파일 읽기 성능 향상

개요

C#을 이용해서 엑셀파일을 읽어들이는 방법이 여러가지가 있습니다. 필자는 주로 OpenXML 을 이용합니다. 파일을 읽어 가공을 하던 중 자료의 양에 비해 속도가 지나치게 떨어지는 현상을 발견했습니다. 대상이 되는 파일이 많아서 아주 많은 시간이 소요될 것으로 예상되었습니다. 아무리 생각해도 정상적인 상황이 아니라는 판단이 들어 더 분석해 보기로 했습니다.

성능 저하 원인

결과부터 이야기하면 텍스트 형태의 셀 자료를 가져오는 부분때문에 발생한 문제였습니다. OpenXML 을 이용해서 엑셀을 읽어오는 글을 보면 셀의 자료를 가져오는 부분이 다음과 같은 형태로 구현되어 있습니다.

string value = cell.CellValue.InnerText;

if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
{
    return doc.WorkbookPart.SharedStringTablePart.SharedStringTable.ChildElements.GetItem(int.Parse(value)).InnerText;
    //return doc.WorkbookPart.SharedStringTablePart.SharedStringTable.ElementAt(int.Parse(value)).InnerText;
}

속도가 심각하게 저하되는 부분이 5, 6행 입니다. 엑셀을 읽을 때 행 단위로 반복하게 됩니다. 그 안에서 각 컬럼(셀)의 값을 읽어오게 됩니다. 셀이 텍스트 자료인 경우 1행의 결과가 숫자로 나타납니다. 그래서 3행의 조건을 통해 실제 텍스트 값을 가져오게 됩니다.

자료의 개수가 많지 않은 경우는 문제가 되지 않습니다. 그러나 많아지게 되면 심각한 속도저하가 발생합니다.

SharedStringTable

SharedStringTable 은 텍스트 값을 효율적으로 저장하기 위해 만들어진 객체입니다. 엑셀같은 형식의 자료 특성상 같은 텍스트가 반복될 여지가 많기 때문에 중복을 최소화 해서 관리합니다.

어찌보면 당연한 것인데 반복문 안에서 크기가 큰 SharedStringTable 객체를 계속 참조해서 속도 저하가 일어나는 것 입니다.

해결책(Dictionary 이용)

읽어들인 엑셀파일의 SharedStringTable 을 반복문이 시작되기 전 Dictionary 객체에 담아놓고 참조하면 성능이 크게 향상됩니다. 다음의 코드가 설명한 방식으로 구현된 것 입니다.

var dictionary = new Dictionary<int, string>();

var excelFile = @"your_excel_file_path";

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(excelFile, false))
{
    var sharedStringTablePart = spreadsheetDocument.WorkbookPart.SharedStringTablePart;
    using (OpenXmlReader reader = OpenXmlReader.Create(sharedStringTablePart))
    {
        int i = 0;
        while (reader.Read())
        {
            if (reader.ElementType == typeof(SharedStringItem))
            {
                SharedStringItem ssi = (SharedStringItem)reader.LoadCurrentElement();
                dictionary.Add(i++, ssi.Text != null ? ssi.Text.Text : string.Empty);
            }
        }
    }

    .
    .
    .

    foreach (Row row in rows)
    {
        foreach (Cell cell in row.Descendants<Cell>())
        {
            string value = cell.CellValue.InnerText;

            if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
            {
                var cellValue = dictionary[int.Parse(value)];
                //your code
            }
        }
    }
}

1행에서 SharedStringTable 의 값을 담아둘 Dictionary 변수를 선언합니다. 7~19행 까지 SharedStringTable 객체를 읽어서 변수에 추가합니다. 33행에서 텍스트 값을 SharedStringTable 객체가 아닌 Dictionary 변수에서 참조해서 가져옵니다. 이렇게 하면 무거운 SharedStringTable 객체를 반복문 내에서 참조하지 않아 금방 처리됩니다.

OpenXML 로 엑셀처리 시 성능 문제로 어려움을 겪으신 분들 도움이 되시기를 바랍니다.

C# 윈폼 프로그램 정보 입력 및 제목 표시(WinForm program information and display title)

개요

윈도우 기반으로 작성된 프로그램을 선택하고 마우스 오른쪽 버튼을 클릭하고 속성을 선택하면 자세한 정보를 볼 수 있습니다. 자세히 탭을 선택하면 다음과 같은 정보를 확인할 수 있습니다. 이 정보를 입력하는 방법과 이것을 이용해서 윈폼 프로그램의 이름과 버전 정보를 표시하는 방법을 알아보도록 하겠습니다.

실행파일의 자세히 탭
실행파일의 자세히 탭

어셈블리 정보 설정

비주얼 스튜디오로 윈폼 프로젝트를 열고 프로젝트 > [프로젝트명] 속성을 클릭합니다. 그러면 다음과 같은 화면이 나타납니다.

프로젝트 속성 화면
프로젝트 속성 화면

어셈블리 정보를 클릭하면 다음과 같은 화면이 나타나고 적절한 내용을 입력하면 됩니다.

어셈블리 정보 대화상자
어셈블리 정보 대화상자

빌드 후 파일정보를 확인해 보면 변경된 것을 알 수 있습니다.

변경된 속성 정보
변경된 속성 정보

코드내에서 활용

이 정보를 바탕으로 폼의 제목 표시줄에 제품 이름과 버전이 나타나도록 다음과 같이 코드를 작성합니다. 필요한 부분에 추가하시면 됩니다.

private void Form1_Load(object sender, EventArgs e)
{
    this.Text = Application.ProductName + " Ver " + Application.ProductVersion;
}

프로그램을 실행 해 보면 제목 표시줄이 입력한 정보로 나타나는 것을 확인할 수 있습니다.

입력된 어셈블리 정보에 따라 나타나는 제품명과 버전

프로그램을 변경하면서 버전 번호를 변경해 주면 쉽게 버전을 확인할 수 있습니다.