Entity Framework로 테이블 렌더링

어떤 이유로 데이터베이스에서 PDF 문서로 데이터를 내보내는 것이 최근에 인기 있는 HTML에서 PDF 변환 방식 없이 더 편리할 때가 있습니다.

이 문서에서는 Aspose.PDF for .NET을 사용하여 PDF 문서를 생성하는 방법을 보여줍니다.

Aspose.PDF로 PDF 생성의 기초

Aspose.PDF에서 가장 중요한 클래스 중 하나는 Document class입니다. 이 클래스는 PDF 렌더링 엔진입니다. PDF 구조를 표현하기 위해 Aspose.PDF 라이브러리는 Document-Page 모델을 사용합니다. 여기서:

  • Document - 페이지 컬렉션을 포함한 PDF 문서의 속성을 포함합니다.
  • Page - 특정 페이지의 속성과 이 페이지와 관련된 다양한 요소 컬렉션을 포함합니다.

따라서 Aspose.PDF로 PDF 문서를 생성하려면 다음 단계를 따라야 합니다:

  1. Document 객체를 생성합니다.
  2. Document 객체에 페이지(Page 객체)를 추가합니다.
  3. 페이지에 배치할 객체를 생성합니다(예: 텍스트 조각, 테이블 등).
  4. 생성된 항목을 페이지의 해당 컬렉션에 추가합니다(우리의 경우 단락 컬렉션이 될 것입니다).
  5. 문서를 PDF 파일로 저장합니다.

가장 일반적인 문제는 테이블 형식으로 데이터를 출력하는 것입니다. Table class는 테이블을 처리하는 데 사용됩니다. 이 클래스는 테이블을 생성하고 문서에 배치할 수 있는 기능을 제공합니다. RowsCells를 사용합니다. 따라서 테이블을 생성하려면 필요한 수의 행을 추가하고 적절한 수의 셀로 채워야 합니다.

다음 예제는 4x10 테이블을 생성합니다.

Table 객체를 초기화할 때 최소한의 스킨 설정이 사용되었습니다:

결과적으로 우리는 동일한 너비의 열을 가진 4x10 테이블을 얻습니다.

Table 4x10

ADO.NET 객체에서 데이터 내보내기

Table 클래스는 ADO.NET 데이터 소스와 상호작용하기 위한 메서드를 제공합니다 - ImportDataTableImportDataView. 첫 번째 메서드는 DataTable에서 데이터를 가져오고, 두 번째는 DataView에서 가져옵니다. 이 객체들이 MVC 템플릿에서 작업하기에 그리 편리하지 않다는 전제를 두고 간단한 예제로 제한하겠습니다. 이 예제(50행)에서는 ImportDataTable 메서드를 호출하고 DataTable 인스턴스와 헤더 플래그 및 데이터 출력을 위한 초기 위치(행/열)와 같은 추가 설정을 매개변수로 받습니다.

Entity Framework에서 데이터 내보내기

현대 .NET에 더 관련성이 높은 것은 ORM 프레임워크에서 데이터를 가져오는 것입니다. 이 경우, 간단한 목록이나 그룹화된 데이터에서 데이터를 가져오기 위한 확장 메서드로 Table 클래스를 확장하는 것이 좋습니다. 가장 인기 있는 ORM 중 하나인 Entity Framework에 대한 예를 들어 보겠습니다.

public static class PdfHelper
{
    private static void ImportEntityList<TSource>(this Aspose.Pdf.Table table, IList<TSource> data)
    {
        var headRow = table.Rows.Add();

        var props = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            headRow.Cells.Add(prop.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute dd ? dd.Name : prop.Name);
        }

        foreach (var item in data)
        {
            // Add row to table
            var row = table.Rows.Add();
            // Add table cells
            foreach (var t in props)
            {
                var dataItem = t.GetValue(item, null);
                if (t.GetCustomAttribute(typeof(DataTypeAttribute)) is DataTypeAttribute dataType)
                    switch (dataType.DataType)
                    {

                        case DataType.Currency:
                            row.Cells.Add(string.Format("{0:C}", dataItem));
                            break;
                        case DataType.Date:
                            var dateTime = (DateTime)dataItem;
                            if (t.GetCustomAttribute(typeof(DisplayFormatAttribute)) is DisplayFormatAttribute df)
                            {
                                row.Cells.Add(string.IsNullOrEmpty(df.DataFormatString)
                                    ? dateTime.ToShortDateString()
                                    : string.Format(df.DataFormatString, dateTime));
                            }
                            break;
                        default:
                            row.Cells.Add(dataItem.ToString());
                            break;
                    }
                else
                {
                    row.Cells.Add(dataItem.ToString());
                }
            }
        }
    }

    private static void ImportGroupedData<TKey, TValue>(this Aspose.Pdf.Table table, IEnumerable<Models.GroupViewModel<TKey, TValue>> groupedData)
    {
        var headRow = table.Rows.Add();
        var props = typeof(TValue).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            headRow.Cells.Add(prop.GetCustomAttribute(typeof(DisplayAttribute)) is DisplayAttribute dd ? dd.Name : prop.Name);
        }

        foreach (var group in groupedData)
        {
            // Add group row to table
            var row = table.Rows.Add();
            var cell = row.Cells.Add(group.Key.ToString());
            cell.ColSpan = props.Length;
            cell.BackgroundColor = Aspose.Pdf.Color.DarkGray;
            cell.DefaultCellTextState.ForegroundColor = Aspose.Pdf.Color.White;

            foreach (var item in group.Values)
            {
                // Add data row to table
                var dataRow = table.Rows.Add();
                // Add cells
                foreach (var t in props)
                {
                    var dataItem = t.GetValue(item, null);

                    if (t.GetCustomAttribute(typeof(DataTypeAttribute)) is DataTypeAttribute dataType)
                        switch (dataType.DataType)
                        {
                            case DataType.Currency:
                                dataRow.Cells.Add(string.Format("{0:C}", dataItem));
                                break;
                            case DataType.Date:
                                var dateTime = (DateTime)dataItem;
                                if (t.GetCustomAttribute(typeof(DisplayFormatAttribute)) is DisplayFormatAttribute df)
                                {
                                    dataRow.Cells.Add(string.IsNullOrEmpty(df.DataFormatString)
                                        ? dateTime.ToShortDateString()
                                        : string.Format(df.DataFormatString, dateTime));
                                }
                                break;
                            default:
                                dataRow.Cells.Add(dataItem.ToString());
                                break;
                        }
                    else
                    {
                        dataRow.Cells.Add(dataItem.ToString());
                    }
                }
            }
        }
    }
}

데이터 주석 속성은 모델을 설명하는 데 자주 사용되며 테이블을 생성하는 데 도움을 줍니다. 따라서 ImportEntityList에 대한 다음 테이블 생성 알고리즘이 선택되었습니다:

  • 12-18행: 헤더 행을 구축하고 “DisplayAttribute가 있으면 해당 값을 가져오고, 그렇지 않으면 속성 이름을 가져온다"는 규칙에 따라 헤더 셀을 추가합니다.
  • 50-53행: 데이터 행을 구축하고 “DataTypeAttribute가 정의되어 있으면 추가 디자인 설정이 필요한지 확인하고, 그렇지 않으면 데이터를 문자열로 변환하여 셀에 추가한다"는 규칙에 따라 행 셀을 추가합니다.

이 예제에서는 DataType.Currency(32-34행) 및 DataType.Date(35-43행)에 대해 추가 사용자 정의가 이루어졌지만 필요에 따라 다른 사용자 정의를 추가할 수 있습니다. ImportGroupedData 메서드의 알고리즘은 이전 것과 거의 동일합니다. 그룹화된 데이터를 저장하기 위해 추가 GroupViewModel 클래스가 사용됩니다.

using System.Collections.Generic;
public class GroupViewModel<K,T>
{
    public K Key;
    public IEnumerable<T> Values;
}

우리가 그룹을 처리하므로 먼저 키 값에 대한 행을 생성하고(66-71행), 그 다음에 이 그룹의 행을 생성합니다.