C#팁

LinqToExcel 로 Excel CSV 데이타 읽어 오기 Lookvv 평점: 10.0/10 (1명 참여) 조회: 6874
LinqToExcel 에 대해 알아보도록 하겠습니다.
이름 그대로 Excel 데이타를 linq 를 이용하여 읽어오는 라이브러리 입니다.

LiqToExcel 은 내부에서 Ado.Net 을 이용해서 Excel 데이타를 읽어옵니다. 그렇기 때문에 속도도 어느정도 보장이 됩니다.
또한, CSV 파일도 지원합니다. 설명은 Excel 을 대상으로 하겠지만 CSV 파일도 똑같이 적용하면 됩니다.
대신 Ado.Net 을 이용하기 때문에 linq 의 where 절은 제한 적입니다. Entity FrameWork 나 Linq To SQL 수준으로
생각하면 안됩니다. 그 부분에 대해서는 아래에서 설명할 것입니다.
또한, Excel 파일의 문서 데이타만 읽어올 수가 있습니다. 차트 같은 것은 못 읽어 옵니다.

Linq 를 이용하니깐 닷넷 버전 3.0 이나 3.5 버전 이상에서만 사용가능합니다. 닷넷 3.0 버전 사용하는 사람있나요?
주로 Linq 이용할려면 3.5 이상 버전을 사용할거니깐 3.5 이상 버전만 가능하다고 보면 되겠네요

LinqToExcel 설치하기 위해서는 Nuget에서 LinqToExcel 로 검색하셔서 설치하시면 되고요
수동으로 설치하고자 한다면 홈페이지 http://code.google.com/p/linqtoexcel/
에서 x86 용이나  x64 용 라이브러리를 다운받아서 직접 레퍼런스 추가하면 됩니다.

레퍼런스 추가하셨다면 네임스페이스를 추가합니다.

using LinqToExcel;

칼럼이라고 칭하면 엑셀테이블의 칼럼을 의미하는것이고 프로퍼티라고 칭하면 c# class 의 property 를 의미하는 것입니다.

일단, 엑셀테이블 구성을 먼저 보도록 하죠.
워크 시트 이름을 "회원 정보"라고 이름 짓고 아래의 테이블을 기입하였습니다.
테이블 위치는 아무 곳이나 하여도 잘 읽어 옵니다. 엑셀의  워크 시트 중간쯤에 기입하셔도 됩니다.

name        회원     나이        addr        회원 성별        birth
김하나        10                서울시        남            1999년04월05일
이하나        20                인천시        여            2000년05월06일
박하나        21                대구시        남            2001년05월07일
최하나        30                부산시        여            2002년06월08일

칼럼 이름을 잘 보시면 지저분하죠? 한글도 있고 영어도 있고...
한글 칼럼명도 잘 보세요. "회원 나이" 처럼 일부러 중간에 공백을 추가했습니다.
왜 이렇게 구성하였냐면 엑셀의 테이블 칼럼명과  c# 클래스의 프로퍼티와 매핑을 시키는 방법을 익히기 위해서입니다.
db프로그래밍을  해 보셨다면 금방 이해가 갈 겁니다.

LinqToExcel 은 엑셀의 테이블 칼럼명과  클래스의 프로퍼티와 매핑을 시켜서 엑셀테이블의 데이타를
클래스의 프로퍼티에 세팅시켜줍니다.

엑셀의 칼럼명에 한글이나 공백이 포함된 경우,또는 c# 의 프로퍼티이름으로  적합하지 않는 텍스트가 있는 경우  
어떻게 처리하는지 보여줄려고 일부러 저렇게 구성하였습니다.

이제 클래스를 구성해 보도록 하죠.
클래스의 프로퍼티에 있는  comment를  보시면 엑셀 데이블의 칼럼과 프로퍼티가 어떻게 매핑할 계획인지 알 수 있습니다.
엑셀 테이블의 칼럼명에 있는 문자와 잘 비교해 보세요. 대소문자 차이까지도요


enum SexType { Man, Woman };

class MyAddr
{

//엑셀테이블의 "name" 칼럼과 매핑
    public string name { get; set; }

//엑셀테이블의 "회원 나이"칼럼과 매핑    
    public int Age { get; set; }

//엑셀테이블의 "회원 나이"칼럼의 데이타가 19살을 초과한다면  true로 세팅    
    public bool IsAdult { get; set; }

//엑셀테이블의 "회원 성별" 칼럼의 데이타가 "남" 인경우 SexType.Man 으로 세팅
//엑셀테이블의 "회원 성별" 칼럼의 데이타가 "여" 인경우 SexType.Woman 으로 세팅    
    public SexType sex { get; set; }

//엑셀테이블의 "addr" 칼럼과 매핑      
    public string Addr { get; set; }

//엑셀테이블의 "birth" 칼럼과 매핑
    public DateTime birth { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} {2} {3} {4} {5}", name, Age, IsAdult, sex, Addr, birth);
    }
}

이제 LinqToExcel 이 엑셀 데이타를 클래스의 프로퍼티와 어떻게 매핑시키는 지 알아보죠

엑셀테이블의 칼럼명과 프로퍼티명이  대소문자까지 동일하면 자동으로 매핑이 됩니다.

위의 클래스에서 보면 프로퍼티 public string name { get; set; } 과 엑셀테이블의 칼럼명 "name"
은 대소문자 까지  동일하죠? 그렇기 때문에 자동으로 엑셀테이블의 칼럼명 name 이 string 타입으로 변환이 됩니다.
대소문자 까지 일치 하여야 자동으로 매핑이 됩니다. 만일 프로퍼티명이 Age 이고 엑셀테이블의
칼럼명이 age 인 경우 대소문자가 틀리기 때문에 수동으로 매핑시켜 주어야 합니다.

두번째 프로퍼티 public int Age { get; set; }  경우 엑셀테이블의 칼럼명 "회원 나이" 와 매핑이 되어야 합니다.
당연히 두개의 이름이 틀리니깐 수동으로 매핑을 시켜 주어야 겠죠.

먼저 엑셀 파일을 읽어 옵니다. 파라미터로 엑셀 파일명을 줍니다. 확장자는  xls , xlsx, csv 가 지원됩니다.
var excel = new ExcelQueryFactory("member.xlsx");

다음 매핑을 시켜보죠. 프로퍼티 Age 와 엑셀테이블 칼럼명 "회원 나이" 를 매핑시킵니다.
excel.AddMapping<MyAddr>(x => x.Age, "회원 나이");
이제 엑셀테이블의 칼럼명 "회원 나이" 의 데이타가 int 타입 Age 프로퍼티에 매핑됩니다.

다음 public bool IsAdult { get; set; } 프로퍼티에서  IsAdult 는 엑셀테이블 칼럼명 "회원 나이"의 데이타를 읽어와서 19 살을 초과하면
true 로 세팅합니다. 당연히 19 살 이하라면 false 로 세팅될거고요.

잘 보세요. 이전의 Age 프로퍼티도 "회원 나이" 와 매핑시켰습니다. 두개이상의  프로퍼티에 동일한 엑셀테이블의 칼럼명을 매핑시킬수가 있습니다.
excel.AddMapping<MyAddr>(x => x.IsAdult, "회원 나이");
 
이제 엑셀테이블 칼럼명 "회원 나이" 에 있는 데이타를 읽어와서 프로퍼티 IsAdult 를 세팅시켜 보도록 하죠

excel.AddTransformation<MyAddr>(item => item.IsAdult,
                 cellvalue => int.Parse(cellvalue) > 19);
 
프로퍼티 IsAdult 는 엑셀칼럼명 "회원 나이" 와 매핑시켜놓았기 때문에
cellvalue 는 엑셀칼럼 "회원 나이" 의 cell 값이 됩니다. 타입은 항상 string 입니다.
이제 string 을 int.Parse 로 int 타입으로 변환후 19 보다 크다면 true 를 리턴하고
이 값은 프로퍼티 IsAdult 에 세팅됩니다.

즉, excel 칼럼명 "회원 나이" 데이타가  19 보다 크다면 프로퍼티 IsAdult 는 true 가 되고
19 보다 작다면 프로퍼티 IsAdult 는 false가 됩니다.
 

다음 public SexType sex { get; set; } 프로퍼티를 살펴보죠. SexType 은 enum 타입 입니다.
enum은 이렇게 구성했습니다. enum SexType { Man, Woman };

엑셀테이블의 칼럼명 "회원 성별" 을 읽어와서 "남" 이면 SexType.Man 으로 세팅하고
"여" 라면 SexType.Woman 으로 세팅할려 합니다.

먼저 매핑을 시켜야 겠죠. 엑셀테이블의 칼럼명 "회원 성별" 과 매핑시켰습니다.
excel.AddMapping<MyAddr>(x => x.sex, "회원 성별");

이제 세팅 시켜 보죠
excel.AddTransformation<MyAddr>(item => item.sex,
                cellvalue => (cellvalue == "남") ? SexType.Man : SexType.Woman);
 
프로퍼티 sex 는 엑셀테이블의 칼럼명 "회원 성별" 과 매핑시켜 놓았기 때문에
cellvalue 는 엑셀칼럼 "회원 성별" 의 cell 값이 됩니다. cellvalue는 항상 string 입니다.
이제 엑셀칼럼 "회원 성별"의 cell 값이 "남" 이라면 enum타입 SexType.Man 을 리턴하고
이 값은 프로퍼티 sex 에 세팅됩니다. "여" 라면 SexType.Woman 이 세팅됩니다.

즉, excel 칼럼 "회원 성별" 이  "남" 이라면 프로퍼티 sex는 SexType.Man 이 되고
"여" 라면 프로퍼티 sex 는 SexType.Woman 이 됩니다.

다음  public string Addr { get; set; } 프로퍼티를 살펴보죠. 엑셀테이블의 칼럼명 "addr"과 매핑해야 합니다.
그런데 대소문자가 틀리네요. 자동으로 매핑이 안되니깐  수동으로 매핑시켜주어야 합니다.
 excel.AddMapping<MyAddr>(x => x.Addr, "addr");
뭐 이제 따로 설명 안 드려도 감이 올겁니다.
 
또,public DateTime birth { get; set; } 를 알아보죠. 엑셀테이블의 칼러명 "birth" 와 대소문자가 까지 같군요
네. 자동으로 매핑이 되어서 엑셀데이블의 칼럼명  "birth" 의 데이타가  DateTime 으로 변환됩니다.
따로 매핑시켜 주지 않아도 됩니다.

이제 매핑과 변환이 다 끝났습니다.

이제 출력을 시켜야 겠죠.

var item in excel.Worksheet<MyAddr>("회원 정보");
Worksheet 메쏘드의 파라미터 "회원 정보" 는 엑셀의 워크시트 이름입니다.
만일 파라미터 없이  excel.Worksheet<MyAddr>() 로 호출하면 첫번째 워크시트가 선택됩니다.

엑셀파일명  "member.xlsx" 에서 워크시트 이름 "회원 정보" 를 읽어와서 클래스 MyAddr 의
프로퍼티에 엑셀데이타가 세팅됩니다.

foreach (var item in excel.Worksheet<MyAddr>("회원 정보"))
{
    Console.WriteLine(item);
}

전체 소스는 아래와 같습니다.

먼저 엑셀의 데이타 형태 입니다.

name        회원     나이        addr        회원 성별        birth
김하나        10                서울시        남            1999년04월05일
이하나        20                인천시        여            2000년05월06일
박하나        21                대구시        남            2001년05월07일
최하나        30                부산시        여            2002년06월08일

여기서 부터 c# 에서 LinqToExcel 을 사용하는 소스 입니다.

using LinqToExcel;

enum SexType { Man, Woman };

class MyAddr
{
    public string name { get; set; }
    public int Age { get; set; }
    public bool IsAdult { get; set; }
    public SexType sex { get; set; }
    public string Addr { get; set; }
    public DateTime birth { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} {2} {3} {4} {5}", name, Age, IsAdult, sex, Addr, birth);
    }
}
    
static void Main(string[] args)
{
        var excel = new ExcelQueryFactory("member.xlsx");
        excel.AddMapping<MyAddr>(x => x.Age, "회원 나이");
        excel.AddMapping<MyAddr>(x => x.IsAdult, "회원 나이");
        excel.AddMapping<MyAddr>(x => x.sex, "회원 성별");
        excel.AddMapping<MyAddr>(x => x.Addr, "addr");
             
        excel.AddTransformation<MyAddr>(item => item.IsAdult,
             cellvalue => (int.Parse(cellvalue) > 19));

        excel.AddTransformation<MyAddr>(item => item.sex,
            cellValue => (cellValue == "남") ? SexType.Man : SexType.Woman);
        
        foreach (var item in excel.Worksheet<MyAddr>("회원 정보"))
        {
            Console.WriteLine(item);
        }
          Console.ReadLine();
}

이제 대충 감이 잡히죠.

이제 한개의  워크시트에 여러개의 테이블이 존재 할 때를 알아보죠
워크시트 명은  "회원 정보" 로 하고 여러 개의 테이블이 존재합니다. 그럴때는 Range 를 이용해서
범위를 지정할 수가 있습니다.

WorksheetRange 메쏘드에서 첫번째 파라미터는 Range 의 시작위치
두번째 파라미터는 종료위치, 마지막 파라미터는 워크시트 이름 입니다

마지막 파라미터를 생략하면 첫번째 워크시트를 읽어옵니다.

즉, 아래 코드는 첫번째 워크시트에서 C4 와 G8 사이에 있는 테이블을 읽어  오란 의미입니다.
excel.WorksheetRange<MyAddr>("C4","G8")

아래 코드는 "회원 정보" 워크시트에서 C4 와 G8 사이에 있는 테이블을 읽어오라는 의미겠죠.
foreach (var item in excel.WorksheetRange<MyAddr>("C4","G8","회원 정보"))
{
    Console.WriteLine(item);
}

LinqToExcel 로 쿼리문을 작성해 보죠.

아래의 코드 의미를 몰라도 됩니다 단순히 SQL 문의 WHERE 절과 어떻게 매칭이 되나 보여줄려고 작성한겁니다.
repo.AddMapping(x => x.State, "Providence");
repo.AddMapping(x => x.Employees, "Employee Count");

var largeCompanies = from c in repo.Worksheet()
where c.Employees > 200 && c.State == "IN"
select c;

위의 code 는 LinqToExcel 내부에서 아래의 코드로 변환이 일어납니다.
SELECT * FROM [Sheet1$] WHERE (([Employee Count] > 200) AND ([Providence] = "IN"))

단순하죠? 이렇게 단순하게 매칭이 되기 때문에 일반 linq 문이나 entity framework 정도로 예상하시고
프로그래밍하시면 안됩니다. 예외나 만족하는 아이템 갯수가 0 이 될 수 있습니다.
한마디로 entity framework 나 Linq To SQL 처럼 똑똑하지가 않습니다.

그렇기 때문에 LinqToExcel 을 사용할때 조심해야 합니다. where 에 제약 사항이 많습니다.
만일 where 절에서 예외가 발생하던지 예상하지 못한 결과가 나올경우 ToList() 를 이용해서 List<T> 으로 변환시킨후
where 절을 적용시켜 주면 왠만한 것은  해결됩니다.
 
from el in excel.Worksheet<MyAddr>("회원 정보") 대신  
from el in excel.Worksheet<MyAddr>("회원 정보").ToList() 를 사용하는 것입니다.
위의 경우 ToList(); 메쏘드는 List<MyAddr> 을 리턴해주기 때문에 LinqToExcel 의 쿼리문대신 일반적인 linq 와
확장메쏘드 까지 사용가능해 집니다.

지금 부터 문제가 발생하는 경우를 좀 더 자세히  살펴보도록 하죠.

앞서 작성한 소스 코드를 이용해서 linq문 쿼리문을 작성해 보겠습니다.
아래의  linq 문은  예외가 발생하던지  Count 가 0 이 됩니다.
뭐가 문제 인것 같습니까?
결론은 AddTransformation 메쏘드를  이용한 프로퍼티는 where 에서 인식을 하지 못합니다.

다시 말하지만 아래 코드는 문제가 발생하는 코드입니다.
var manQuery2 = from el in excel.Worksheet<MyAddr>("회원 정보")
                           where el.IsAdult == true
                           select el;
Console.WriteLine(manQuery2.Count());//Count() 가 0 이 됩니다.

이제 해결법을 알아보도록 하겠습니다.

첫번째 방법은 앞서 살펴본 AddMapping 메쏘드를 이용하는 방법입니다.
아래 코드에서  Age 프로퍼티는 AddTransformation 메쏘드를 이용하지 않았죠
반면 IsAdult 프로퍼티는 AddTransformation 메쏘드를 이용했고요.
excel.AddMapping<MyAddr>(x => x.Age, "회원 나이");
excel.AddMapping<MyAddr>(x => x.IsAdult, "회원 나이");

이제 linq 에 적용해 보죠. where 절에서 IsAdult 를 사용하지 말고 Age 를 이용합니다.
 var manQuery = from el in excel.Worksheet<MyAddr>("회원 정보")
                           where el.Age > 19
                           select el;
 foreach (var item in manQuery)
 {
     Console.WriteLine(item);
 }
예상대로 제대로 작동합니다. IsAdult 대신 Age 로 처리하였습니다.

두번째 방법은 ToList() 를 사용해서 즉시 실행시켜 List<MyAddr> 로 변환시키고 나서
AddTransformation 메쏘드를 적용한 IsAdult 를 사용하는 방법입니다.

excel.Worksheet<MyAddr>("회원 정보") 대신 excel.Worksheet<MyAddr>("회원 정보").ToList() 를 사용한 후에  
where 절에 IsAdult 를 적용했습니다.
var manQuery2 = from el in excel.Worksheet<MyAddr>("회원 정보").ToList()
                       where el.IsAdult == true
                       select el;

Console.WriteLine(manQuery2.Count());
foreach (var item in manQuery2)
{
    Console.WriteLine(item);
}
 역시 제대로 작동합니다.
 
앞서 매핑한  public SexType sex { get; set; }  프로퍼티도 AddTransformation 메쏘드를 이용하였죠
아래처럼 작성하면 manQuery3 는 Count 가 0 이던지 Exception 이 발생합니다.
var manQuery3 = from el in excel.Worksheet<MyAddr>("회원 정보")
                           where el.sex ==SexType.Man
                           select el;
 
Console.WriteLine(manQuery3.Count());
foreach (var item in manQuery3)
{
    Console.WriteLine(item);
}
역시 해결법으로 엑셀테이블 칼럼 "회원 성별" 을 매핑하는 프로퍼티를 하나 만들어 줍니다.
public string TextSex { get; set; }

다음 매핑을 시킵니다.
excel.AddMapping<MyAddr>(x => x.TextSex, "회원 성별");
var manQuery4 = from el in excel.Worksheet<MyAddr>("회원 정보")
                            where el.TextSex == "남"
                            select el;
foreach (var item in manQuery4)
{
    Console.WriteLine(item);
}

역시 두번째 방법으로 ToList() 를 이용하여 enum 타입 sex 를 이용하는 방법입니다.
var manQuery3 = from el in excel.Worksheet<MyAddr>("회원 정보").ToList()
               where el.sex ==SexType.Man
               select el;
foreach (var item in manQuery3)
{
    Console.WriteLine(item);
}
 
LinqToExcel 은 내부에서 Ado.Net 을 이용한다고 하였죠.
ConnectionString 에서 Provider 는 2 종류가 있습니다. Jet 와 ACE 죠.
Jet 은 엑셀2007 이전 버전을 지원하고 ACE 는 엑셀2007 이후 버전을 지원합니다.

Microsoft.Jet.OLEDB.4.0
Microsoft.ACE.OLEDB.12.0
LinqToExcel 은 해당 Provider 를 자동으로 인식해서 적용해 줍니다.
간혹 제대로 Provider 를 인식하지 못해서  "External table is not in the expected format." 이란 예외 메시지가 발생한다면
코드 에서 Provider 를 바꾸어 줄수 있습니다.

아래 코드처럼 DatabaseEngine 프로퍼티로 Jet 엔진에서 Ace엔진으로 바꾸어 줍니다.
var excel = new ExcelQueryFactory("FileName");
excel.DatabaseEngine = DatabaseEngine.Ace;

이제 엑셀 테이블의 데이타가 공백인 경우는 어떻게 될까요?
아래의 엑셀 테이블을 보면 "회원 나이" 칼럼 과 "addr",  "birth" 칼럼에 공백란이 존재하죠.

name        회원 나이    addr    회원 성별    birth
김하나    10                서울시    남            1999년04월05일
이하나    20                인천시    여    
박하나                    대구시    남            2001년05월07일
최하나    30                            여            2002년06월08일
 
이를 LinqToExcel 이 매핑하면 default(T) 를 이용하기 때문에 디폴트값이 됩니다.
앞서 엑셀테이블 칼럼 "회원 나이"는  public int Age { get; set; } 와 매핑되었죠. 그러니깐 공백란은
int 의 디폴트값인 0 이 됩니다.

역시 addr 는 string 프로퍼티와 매핑되니깐 디폴트값 null 이 될것이고
birth 는 디폴트 값 0001년01월01일 이 됩니다.
 
기존의 IsAdult 의 AddTransformation 메쏘드도 공백이 있다면  예외를 발생시킵니다.
공백이면 null(DBNull) 로 인식하기 때문에 Int.Parse() 는 파싱을 하지 못해서 예외를 발생시키게 됩니다.
 
물론 아래처럼 바꾸어 주면 됩니다. 파싱에 성공하고 19 살 이상이라면 true 로 세팅하고  이하라면 false 로 세팅합니다.
값이 null 이라서 파싱에 실패하면 false 로 세팅합니다.
excel.AddTransformation<MyAddr>(item => item.IsAdult,
cellvalue =>
{
    int _age;
    if (int.TryParse(cellvalue, out _age))
    {
        return _age > 19 ? true : false;
    }
    else
        return false;

});

하지만, 민감한 데이타는 0 과  공백을 구별해야하는 경우도 있습니다.
이때는 Nullable Type 과 매핑시켜야 합니다.
 
아래의 클래스 프로퍼티에 int 와 DateTime 을 Nullable Type 으로 바꾸어 주었습니다.
이제 공백란은 디폴트 값이 아닌 null 값을 가지게 될겁니다.
 
 class MyAddr
 {
     public string name { get; set; }
     
     //Nulllable Type 설정
     public int? Age { get; set; }
     public bool IsAdult { get; set; }
     public string TextSex { get; set; }
     public SexType sex { get; set; }
     public string Addr { get; set; }
    //Nulllable Type 설정
     public DateTime? birth { get; set; }

     public override string ToString()
     {
         return string.Format("{0} {1} {2} {3} {4} {5}", name, Age, IsAdult, sex, Addr, birth);
     }
 }
 
역시 AddTransformation 에서 예외를 발생시킬겁니다 Int.Parse 로 null 을 파싱할려 할거니깐요.
이렇게 변경시켜주면 됩니다.
excel.AddTransformation<MyAddr>(item => item.IsAdult,
cellvalue =>
{
    int _age;
    if (int.TryParse(cellvalue, out _age))
    {
        return _age > 19 ? true : false;
    }
    else
        return false;

});

앞서 말한것처럼 LinqToExcel 에서 where 절은 단순히 Ado.Net 의 SQL 문 Where절에 매칭된다고 했죠.
그래서 Nullable 타입을 사용할때도 문제가 발생합니다.

역시 where 절에서 Nullable 타입 Age 를 인식하지 못해서 예외가 발생합니다.
아래의 코드는 예외가 발생하는 코드 입니다.
var manQuery = from el in excel.Worksheet<MyAddr>("회원 정보")
                           where el.Age > 19
                           select el;
 
해결책은 만능 ToList() 를 사용해서 LinqToExcel 을 일반  linq 로 변환하는 것입니다.
이제 where 절에서 Nullable 타입을 제대로 인식합니다.
var manQuery = from el in excel.Worksheet<MyAddr>("회원 정보").ToList()
                           where el.Age > 19
                           select el;

Console.WriteLine(manQuery.Count());
foreach (var item in manQuery)
{
    Console.WriteLine(item);
}
 
정리를 하면 LinqToExcel 에서 where 절이 이상이 없는 것 같은데 예외가 발생하던지 아님 Count 가 0 이 된다면
ToList() 로 일반 linq 로 바꾸어 보자가 되겠죠.

또하나 ToList() 를 사용하는 경우를 보죠. 이건 아직 실험해보지 않았습니다. 이런경우가 드물고 이젠 귀찮아서.....
이런 경우도 있다는 것만 알아두세요. 이쯤 적고 보니 강좌를 괜히 했다고 후회중이네요 너무 귀찮군요.

Provider 로  Microsoft.Jet.OLEDB.4.0 을  사용하는 excel 버전이 있죠.
Jet 엔진을 이용하여 엑셀을 읽어올때 Top 8 Row 법칙을 알아두세요.
엑셀테이블의 칼럼명이 "AA" 라고 하고 이 칼럼의 데이타가 8 번째까지 공백이고 8 번째 이후 부터  데이타가 있는경우
Jet 엔진을 이용해서 데이타를 읽어오면 9 번째 이후의 데이타를 읽어오지 못합니다.
Jet 엔진은 row 을 8 번째 까지 읽어 보고 공백이라면 이후에도 공백이라고 예상해 버립니다.
그래서 9 번째 이후의 데이타를 읽어 오지 않습니다. 아마 퍼포먼스 때문에 그렇게 했겠죠.

이때도 역시 from el in excel.Worksheet<MyAddr>("회원 정보") 대신 from el in excel.Worksheet<MyAddr>("회원 정보").ToList() 로
읽어오면 된다고 하네요. 이런 경우도 있다는 것만 알아두세요. 아님 직접 해보시고  결과를 알려주세요.

ToList() 를 사용하기로 결정하였다면 매번 ToList() 로 엑셀의 데이타를 한꺼번에 읽어 올 필요없이 ToList() 로  List<MyAddr>로 변환후  인스턴스변수에
저장해 놓고 사용하는 것이 좋겠죠.

이제 DataGridView 에 바인딩 시키는  경우를 보도록 합시다.

using LinqToExcel;

public partial class Form1 : Form
{
        //ToList() 로 생성된 List<MyAddr> 을 저장시키기 위해 만들었습니다.
        List<MyAddr> excelData;
        public Form1()
        {
            InitializeComponent();
        }
        //버튼 이벤트 핸들러
        private void button1_Click(object sender, EventArgs e)
        {
            var excel = new ExcelQueryFactory("member.xlsx");
            excel.AddMapping<MyAddr>(x => x.Age, "회원 나이");
            excel.AddMapping<MyAddr>(x => x.IsAdult, "회원 나이");
            excel.AddMapping<MyAddr>(x => x.TextSex, "회원 성별");
            excel.AddMapping<MyAddr>(x => x.sex, "회원 성별");
            excel.AddMapping<MyAddr>(x => x.Addr, "addr");
            
            excel.AddTransformation<MyAddr>(item => item.IsAdult,
                 cellvalue => (int.Parse(cellvalue) > 19));

            excel.AddTransformation<MyAddr>(item => item.sex,
                cellValue => (cellValue == "남") ? SexType.Man : SexType.Woman);

            //excel 데이타의 row 가 많은 경우 Take 를 사용하여 적당한 수의 row만 가져와서
            //List<MyAddr> 로 변환시킨후 인스턴스 변수 excelData 에 저장시켜 놓습니다.
            excelData = excel.Worksheet<MyAddr>("회원 정보").Take(2).ToList();

            //그리고 List<MyAddr> 을 DataGridView1 에 바인딩 시킵니다.
            dataGridView1.DataSource = excelData;

            //그리고 나서 linq 로 또다른 조건의 데이타를 가져오고자 한다면 또 엑셀파일에서 불러올 필요없이
            //인스턴스 변수 List<MyAddr> excelData; 를 이용해서 linq 에 적용시킵니다.
        }
    }

    enum SexType { Man, Woman };
    class MyAddr
    {
        public string name { get; set; }
        public int Age { get; set; }
        public bool IsAdult { get; set; }
        public string TextSex { get; set; }
        public SexType sex { get; set; }
        public string Addr { get; set; }
        public DateTime birth { get; set; }
        public override string ToString()
        {
            return string.Format("{0} {1} {2} {3} {4} {5}", name, Age, IsAdult, sex, Addr, birth);
        }
    }

이제 Class 를 만들어서 Strong Type 으로 접근하기 귀찮고 단순히 데이타를 읽어오고 싶다는 사람은
아래와 같이 하면 됩니다. Ado.Net 의 Connected Access 방법과 비슷합니다.

var excel = new ExcelQueryFactory("member.xlsx");

//Worksheet 메쏘드는 Generic 버전이 아닙니다.
//Cast<T>() 를 이용해서 원하는  타입으로 Cast 시켜 줍니다. 사실 Cast<T>() 까지 사용하지 않아도 됩니다.
//하지만 좀 더 명확하게 타입을 결정하기 위해 저는 Cast<T>() 를 사용하였습니다.
foreach (var row in excel.Worksheet("회원 정보"))
{
    Console.WriteLine("{0} {1} {2}",
        row["회원 나이"].Cast<int>(),
        row["addr"].Cast<string>(),
        row["birth"].Cast<DateTime>()
        );
}

//역시 마찬가지로 Cast<T>() 를 이용해서 해당 타입으로 Cast 시킨후 비교합니다.
var data = from row in excel.Worksheet("회원 정보")
           where row["회원 나이"].Cast<int>() > 19
           select row;

foreach (var item in data)
{
    Console.WriteLine("{0} {1} {2}",
       item["회원 나이"].Cast<int>(),
       item["addr"].Cast<string>(),
       item["birth"].Cast<DateTime>()
       );
}

만일 공백란을 처리하고 싶다면 역시 Cast<int?>() 처럼 Nullable Type 을 처리해주면 됩니다.

엑셀 파일을 읽어서 xml 로 출력시켜 보도록 하죠.
엑셀테이블은 아래와 같이 구성되고 워크시트 이름은 "회원 정보" 입니다.

name        회원 나이    addr        회원 성별    birth
김하나    10                서울시    남                1999년04월05일
이하나    20                인천시    여                2000년05월05일
박하나    21                대구시    남                2001년05월07일
최하나    30                부산시    여                2002년06월08일

이제 소스 입니다.

using LinqToExcel;
using System.Xml.Linq;

enum SexType { Man, Woman };

class MyAddr
{
    public string name { get; set; }
    public int Age { get; set; }
    public bool IsAdult { get; set; }
    public string TextSex { get; set; }
    public SexType sex { get; set; }
    public string Addr { get; set; }
    public DateTime birth { get; set; }
    public override string ToString()
    {
        return string.Format("{0} {1} {2} {3} {4} {5}", name, Age, IsAdult, sex, Addr, birth);
    }
}

static void Main(string[] args)
{
    var excel = new ExcelQueryFactory("member.xlsx");
    excel.AddMapping<MyAddr>(x => x.Age, "회원 나이");
    excel.AddMapping<MyAddr>(x => x.IsAdult, "회원 나이");
    excel.AddMapping<MyAddr>(x => x.TextSex, "회원 성별");
    excel.AddMapping<MyAddr>(x => x.sex, "회원 성별");
    excel.AddMapping<MyAddr>(x => x.Addr, "addr");

    excel.AddTransformation<MyAddr>(item => item.IsAdult,
         cellvalue => (int.Parse(cellvalue) > 19));
    
    excel.AddTransformation<MyAddr>(item => item.sex,
        cellValue => (cellValue == "남") ? SexType.Man : SexType.Woman);

    XElement xl = new XElement("ExcelData",
        from ex in excel.Worksheet<MyAddr>("회원 정보")
        where ex.Age > 19
        select
        new XElement("MemberAddress",
            new XElement("Name", ex.name),
            new XElement("Age", ex.Age),
            new XElement("Addr", ex.Addr),
            new XElement("Birth", ex.birth),
            new XElement("성별",ex.sex)
        ));

    xl.Save("exceldata.xml");
 
    Console.ReadLine();
}

최종 xml 형태 입니다.

<?xml version="1.0" encoding="utf-8"?>
<ExcelData>
  <MemberAddress>
    <Name>이하나</Name>
    <Age>20</Age>
    <Addr>인천시</Addr>
    <Birth>2000-05-05T00:00:00</Birth>
    <성별>Woman</성별>
  </MemberAddress>
  <MemberAddress>
    <Name>박하나</Name>
    <Age>21</Age>
    <Addr>대구시</Addr>
    <Birth>2001-05-07T00:00:00</Birth>
    <성별>Man</성별>
  </MemberAddress>
   <MemberAddress>
    <Name>최하나</Name>
    <Age>30</Age>
    <Addr>부산시</Addr>
    <Birth>2002-06-08T00:00:00</Birth>
    <성별>Woman</성별>
  </MemberAddress>
</ExcelData>


그 밖에 몇가지  메쏘드가 더  있습니다. 가령 엑셀파일의 모든  워크시트 이름 읽어오기 , 칼럼명 읽어오기
index 로 접근하기 등등요.

또한, 지금까지 설명한 엑셀테이블 형태는 칼럼명이 있는 경우 였습니다. LinqToExcel 은 칼럼명이 없는
데이타도 읽어올수 있습니다. WorksheetNoHeader() 와 WorksheetRangeNoHeader() 메쏘드를 사용하면 됩니다.
사용방법은 앞서 설명한 Class 없이 읽어오는 방법과 비슷합니다. 대신 칼럼명이 없기 때문에 index 로 읽어와야 합니다.

설명하지 않은 메쏘드들은  LinqToExcel Wiki 에 가셔서 보시면 됩니다.
Wiki 주소는 아래와 같습니다. 보시면 메쏘드 사용법이 나옵니다. 제가 설명한 부분을 읽고 보시면
거의 다 이해 될겁니다. 사실 더 이상은 허리도 아프고 귀찮고 해서 생략할려고 합니다.

https://github.com/paulyoder/LinqToExcel
참고로 index 로 워크시트를 읽어올때 주의점은 엑셀에 있는 워크시트 순서대로가  아닙니다.
워크시트 이름을 정렬한  후 이름 정렬 순으로 index 를 읽어 온다는 점 주의하세요.

이번 강좌 준비하면서 새로운 사실을 알았네요. 아는 분은 다 아시겠지만 저는 이제서야 알게 되었습니다.
엑셀은 6만5536(2의 16승) 행만 읽어 올수가 있는 줄 알았는데 엑셀 2007 버전 부터는 100만(2의 20승) 행으로 늘어났다고 하네요.

이쯤에서 마무리 하겠습니다. 간단하게 한다는 것이 너무 길어졌습니다.
태그 : Linq LinqToExcel
작성자 정보
Lookvv
Level 47
 [EXP.54/100]

메일:  비공개
글등록 +12 72 덧글등록 +3 2514
자기소개
안녕하세요.
글 공유하기 |
  tweet facebook
2013-07-12 오전 8:14:25
나도한마디
사용자
지송닷넷            [2013-07-12]
Level 99
 [EXP.만랩]
잘읽었습니다.
사용자
테디킴            [2013-07-12]
Level 29
 [EXP.46/50]
ㅎㅎ 좋은 정보 감사합니다~
사용자
무거나            [2015-07-01]
Level 2
 [EXP.11/16]
감사합니다~
태그로 엮인글
[C#.NET Q&A] LINQ datatable group by 질문있습니다. [1]+1  빠라밤바
[C#.NET Q&A] C# 현업 개발자분들 LINQ 많이 사용하시나요??[2]+3  빠라밤바
[C#.NET Q&A] LINQ WHERE 조건에 따라 쿼리실행[2]  devker
[C#.NET Q&A] LINQ로 DataTable에 있는 데이터를 지울려고 하니 예외가 발생합니다.[3]  devker
[구인&교육정보] 안녕하세요. 엄준일 입니다. 한번씩 읽어주세요.  땡초
[C#.NET Q&A] Stored Procedure vs Linq 둘다 공부해야 할까요?[2]+1  손성수
[C#.NET Q&A] LINQ의 First()와 FirstOrDefault()의 차이가 무엇인가요?[4]+1  찾아라야근의열쇠
[C#.NET Q&A] linq 질문드립니다.[2]  훈스훈스훈스
[C#.NET Q&A] linq 질문입니다.[2]+1  llje85
[C#.NET Q&A] 배열내의 모든 값을 특정 값으로 나누기[1]+1  아이프리드
글리스트
C# 람다식(Lambda __EXPRESSION__) - 문형식 람다식 예제[1]  오엔제이
Reactive Extensions라이브러리 1 -Time관련[1]  Lookvv
zero padding, 숫자 앞에 0 추가하여 자릿수 맞추기 예) 00099[1]  안떠니
string 에서 ESC 문자 제거하기  안떠니
C# 디버그 레벨별로 콘솔창에 띄우는 코드[1]  커니커니
C# 윈폼도 띄우고 콘솔도 띄우기[2]  커니커니
class전체에 exception 처리를 할 수 있을까요??[5]  꿀물이필요해
TabLayout 병합하기[2]+2 파일첨부 테디킴
code snippet 만들기[2]+2  Lookvv
Entity Framework의 실행 쿼리 트레이스 툴  오차진
 ★현재글->   LinqToExcel 로 Excel CSV 데이타 읽어 오기[3]  Lookvv
Visual Studio 2010 Remote Debugger 사용법[7] 파일첨부 히말라야
C# label 컨트롤 & ampersand 특수문자 입력...[2]+1  이선호
iTunes 현재 선택한 노래의 가사를 검색하는 프로그램 개발[1]+1  김선우
윈도의 타이틀바 없을 때의 창 이동기능[7]+1  박달동
안녕하세용~~ 혹시나 해서 Font Size가 자동으로 조절되는 코드 올립니당.[7]+1  테디킴
How to create an instance of the class in MainWindow of WPF/C#.[5]+6  안떠니
EF 성능 개선 팁[1]  peter
윈도우 폼 종료 질문드립니다.[4]  C#
별도의 메일서버없이 gmail을 이용해서 메일을 보내자..[9]+5  한호정
TestCommon 소개[4]+1  하늘아래