DataTable 내용을 CSV로 내보낼 때, 외부 라이브러리 없이 문자열을 직접 쌓아 만드는 방법입니다. 머리글 줄을 먼저 넣고 각 행을 쉼표로 이어 붙이면 되는데, 값 안에 쉼표가 들어 있을 때만 조심하면 됩니다.

    Private Function CSVBuilder(dt As DataTable, headers As List(Of String), columns As List(Of String)) As String
        Dim sCSV = New StringBuilder(String.Join(",", headers))
        sCSV.Append(Environment.NewLine)

        Dim view As New DataView(dt)
        Dim tDt As DataTable = view.ToTable(True, columns.ToArray)

        For Each row As DataRow In tDt.Rows
            '-- Handle comma
            sCSV.Append(String.Join(",", (From rw In row.ItemArray Select If(rw.ToString.Trim.Contains(","), String.Format("""{0}""", rw.ToString.Trim), rw.ToString.Trim))))
            sCSV.Append(Environment.NewLine)
        Next

        Return sCSV.ToString
    End Function

호출은 이렇게 합니다. D 가 원본 DataTable입니다.

광고
        T = CSVBuilder(D,
                                     "상품코드,상품명,바코드,제조년월,제조원,원산지,주의사항,신고번호,제형,용량(매수)".Split(","c).ToList,
                                     "상품코드,상품명,바코드,제조년월,제조원,원산지,주의사항,신고번호,제형,용량(매수)".Split(","c).ToList)

동작 흐름

함수는 인자를 셋 받습니다. headers 는 CSV 첫 줄에 쓸 열 제목, columns 는 DataTable에서 뽑아낼 실제 열 이름입니다. 예제에서는 둘을 같은 문자열로 넘기는데, DB 컬럼명과 보고서에 쓸 한글 제목이 다를 때 둘을 따로 줄 수 있게 나눠 둔 구조입니다.

StringBuilder 를 머리글로 시작해서 줄바꿈을 붙이고, DataView.ToTable(True, columns) 로 지정한 열만 가진 새 DataTable을 만듭니다. 이러면 원본에 열이 더 많아도 원하는 열만, 지정한 순서대로 추려집니다. 각 행은 row.ItemArray 로 값 배열을 꺼내, 값에 쉼표가 있으면 "..." 로 감싸고 없으면 그대로 두어 쉼표로 잇습니다. 값은 Trim 으로 앞뒤 공백을 떼고요.

알아 둘 두 가지

하나는 ToTable 의 첫 인자 True 입니다. 이건 "중복 행 제거(distinct)"를 뜻해서, 뽑은 열들의 값이 완전히 같은 행이 둘 이상이면 하나로 합쳐집니다. 상품 목록이라면 같은 내용의 행이 사라져서 건수가 안 맞을 수 있으니, 모든 행을 그대로 내보내려면 False 로 바꿔야 합니다.

다른 하나는 CSV 이스케이프가 완전하지 않다는 점입니다. 이 코드는 쉼표만 따옴표로 감싸고, 값 안에 든 큰따옴표나 줄바꿈은 처리하지 않습니다. CSV 규칙상 값에 큰따옴표가 있으면 "" 로 두 번 쓰고 전체를 따옴표로 감싸야 하며, 줄바꿈이 든 값도 따옴표로 감싸야 합니다. 상품명에 따옴표나 줄바꿈이 섞여 있으면 열이 밀릴 수 있으니, 아래처럼 세 조건을 모두 따지는 게 안전합니다.

Private Function CsvField(s As String) As String
    s = If(s, "")
    If s.Contains(",") OrElse s.Contains("""") OrElse s.Contains(vbLf) OrElse s.Contains(vbCr) Then
        Return """" & s.Replace("""", """""") & """"
    End If
    Return s
End Function

마지막으로 저장할 때 인코딩을 맞춰야 합니다. 한글이 포함된 CSV를 엑셀에서 바로 열 거라면, UTF-8로 저장하되 BOM을 붙여야(New UTF8Encoding(True)) 엑셀이 한글을 안 깨뜨립니다. BOM이 없으면 엑셀이 시스템 기본 인코딩으로 열어 깨지는 경우가 있습니다.

CsvField를 붙이고 파일로 저장하기

CsvField 를 쓰려면 CSVBuilder 의 행 처리 줄을 아래처럼 바꾸면 됩니다. Convert.ToString 은 값이 DBNull 이면 빈 문자열을 돌려주기 때문에, DB에서 가져온 빈 칸이 섞여 있어도 예외 없이 지나갑니다.

            sCSV.Append(String.Join(",", row.ItemArray.Select(Function(v) CsvField(Convert.ToString(v).Trim()))))

만든 문자열은 BOM 있는 UTF-8로 저장합니다.

IO.File.WriteAllText("C:\export\items.csv", T, New UTF8Encoding(True))

Imports System.Text 가 있어야 UTF8Encoding 을 그대로 쓸 수 있습니다. 저장 폴더가 없으면 DirectoryNotFoundException 이 나니, 처음 실행하는 PC를 생각해서 IO.Directory.CreateDirectory 로 폴더를 먼저 만들어 두면 안전합니다.

엑셀에서 열었을 때 숫자가 바뀌는 문제

이 예제처럼 바코드가 들어 있는 CSV는 엑셀에서 열면 값이 망가지기 쉽습니다. 13자리 바코드 8801234567890 은 8.80123E+12 같은 지수 표기로 보이고, 0012 같은 코드는 앞의 0이 사라져 12 가 됩니다. CSV에는 형식 정보가 없어서 엑셀이 숫자처럼 생긴 값을 숫자로 바꿔 버리기 때문입니다. 파일 자체는 멀쩡하니 다른 프로그램에서 읽을 거라면 그대로 두면 되고, 엑셀로 볼 사람이 있다면 엑셀의 "텍스트/CSV 가져오기"로 열면서 해당 열을 텍스트로 지정하도록 안내하는 게 가장 깔끔합니다.

날짜 열도 신경 써야 합니다. DateTime 값을 ToString 하면 시스템 지역 설정에 따라 2026-10-08 오후 3:20:00 같은 형태가 나와서, 다른 프로그램에서 날짜로 읽지 못할 수 있습니다. 날짜가 들어가는 열은 CDate(v).ToString("yyyy-MM-dd HH:mm:ss") 처럼 형식을 직접 지정해서 넣는 편이 안전합니다.