Microsoft MVP성태의 닷넷 이야기
.NET Framework: 885. C# - 닷넷 응용 프로그램에서 SQLite 사용 [링크 복사], [링크+제목 복사],
조회: 23038
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)

C# - 닷넷 응용 프로그램에서 SQLite 사용

요즘엔, 웬만한 건 모두 NuGet에 다 들어 있으니... 검색을 해보면 가장 상단에 sqlite가 나옵니다.

sqlite 3.13.0
; https://www.nuget.org/packages/sqlite/

아쉽게도 저건 C/C++ 사용자를 위한 native DLL을 담고 있을 뿐 닷넷 개발자에게는 Microsoft.Data.Sqlite가 의미 있습니다.

[닷넷 프레임워크]
Install-Package Microsoft.Data.SQLite -Version 3.1.1
; https://www.nuget.org/packages/Microsoft.Data.Sqlite/

[닷넷 코어]
Install-Package Microsoft.Data.Sqlite.Core -Version 3.1.1
; https://www.nuget.org/packages/Microsoft.Data.Sqlite.Core/

현재(2020-02-09) 3.1.1 기준으로 .NET Standard 2.0을 요구하기 때문에 .NET Framework의 경우 4.6.1 이상의 프로젝트에서 참조 추가할 수 있습니다. 사용법은, System.Data의 명세를 따른 만큼 일전에 소개한 다른 DB들과,

무료 데이터베이스 서버 성능 비교(SQL Server Express, IBM DB2 Express, MySQL, Sybase, PostgreSQL, Oracle XE)
; https://www.sysnet.pe.kr/2/0/1411

코드 상으로 거의 유사합니다.

using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string currentPath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
            string sqlFilePath = Path.Combine(currentPath, "test.sqlite");

            string connectionString = $"Data Source={sqlFilePath};";

            using (SqliteConnection connection = new SqliteConnection(connectionString))
            {
                connection.Open();

                CreateTableIfNotExists(connection);

                // Create
                SqliteCommand insertCommand = new SqliteCommand();
                insertCommand.Connection = connection;
                insertCommand.CommandText = "INSERT INTO mytable(id, NAME, age, DESCRIPTION) VALUES (@id, @NAME, @age, @DESCRIPTION)";

                insertCommand.Parameters.Add("@id", SqliteType.Integer);
                insertCommand.Parameters.Add("@NAME", SqliteType.Text, 50);
                insertCommand.Parameters.Add("@age", SqliteType.Integer);
                insertCommand.Parameters.Add("@DESCRIPTION", SqliteType.Text, 150);

                string nameValue = "Name" + Guid.NewGuid().ToString();
                insertCommand.Parameters[0].Value = (int)DateTime.Now.Ticks;
                insertCommand.Parameters[1].Value = nameValue;
                insertCommand.Parameters[2].Value = 10;
                insertCommand.Parameters[3].Value = nameValue + "_Description";

                int affected = insertCommand.ExecuteNonQuery();
                Console.WriteLine("# of affected row: " + affected);

                // Update
                SqliteCommand updateCommand = new SqliteCommand();
                updateCommand.Connection = connection;
                updateCommand.CommandText = "UPDATE mytable SET DESCRIPTION=@DESCRIPTION WHERE NAME=@NAME";

                updateCommand.Parameters.Add("@NAME", SqliteType.Text, 50);
                updateCommand.Parameters.Add("@DESCRIPTION", SqliteType.Text, 150);

                updateCommand.Parameters[0].Value = nameValue;
                updateCommand.Parameters[1].Value = nameValue + "_Description2";

                affected = updateCommand.ExecuteNonQuery();
                Console.WriteLine("# of affected row: " + affected);

                // Select - ExecuteScalar
                SqliteCommand selectCommand = new SqliteCommand();
                selectCommand.Connection = connection;
                selectCommand.CommandText = "SELECT count(*) FROM mytable";

                object result = selectCommand.ExecuteScalar();
                Console.WriteLine("# of records: " + result);

                // Select - DataTable
                DataSet ds = new DataSet();

                /* DataAdapter 미구현

                SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM mytable", sqlConnection);
                da.Fill(ds, "mytable");

                DataTable dt = ds.Tables["mytable"];
                foreach (DataRow dr in dt.Rows)
                {
                    Console.WriteLine(string.Format("Name = {0}, Desc = {1}", dr["NAME"], dr["DESCRIPTION"]));
                }
                */

                // Delete
                SqliteCommand deleteCommand = new SqliteCommand();
                deleteCommand.Connection = connection;
                deleteCommand.CommandText = "DELETE FROM mytable WHERE NAME=@NAME";

                deleteCommand.Parameters.Add("@NAME", SqliteType.Text, 50);
                deleteCommand.Parameters[0].Value = nameValue;

                affected = deleteCommand.ExecuteNonQuery();
                Console.WriteLine("# of affected row: " + affected);
            }
        }

        private static void CreateTableIfNotExists(SqliteConnection conn)
        {
            string sql = "create table if not exists mytable(id int, NAME varchar(50), age int, DESCRIPTION varchar(150))";

            using (SqliteCommand command = new SqliteCommand(sql, conn))
            {
                command.ExecuteNonQuery();
            }

            sql = "create index if not exists idx_NAME on mytable(NAME)";
            using (SqliteCommand command = new SqliteCommand(sql, conn))
            {
                command.ExecuteNonQuery();
            }
        }
    }
}

단지 주요 차이점이라면 다음과 같은 정도가 됩니다.

  • 파일 기반
  • DataAdapter의 미구현
  • 타입명이 각각 SqliteConnection, SqliteCommand라는 것
  • SqliteType { Integer = 1, Real, Text, Blob } 4가지 필드 타입만 제공

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




그 외에 유의할 사항으로는, 대량 데이터 INSERT 시 속도가 (대단히) 느리다는 점입니다. 이에 대해서는 다음의 글에도 나오지만,

SQLite is creating a transaction for each insert
; https://stackoverflow.com/questions/3852068/sqlite-insert-very-slow

다중 실행 시 "being", "end" 명령어로 명시적인 트랜잭션을 취하면 됩니다. 따라서, ADO.NET의 전통적인 방식에 따라 SqliteTransaction 타입을 이용해 처리하거나,

using (SqliteTransaction mytransaction = connection.BeginTransaction())
{
    insertCommand.Transaction = mytransaction;
    insertCommand.Connection = connection;

    for (int i = 0; i < 100; i++)
    {
        string nameValue = "Name" + Guid.NewGuid().ToString();
        insertCommand.Parameters[0].Value = (int)DateTime.Now.Ticks;
        insertCommand.Parameters[1].Value = nameValue;
        insertCommand.Parameters[2].Value = 10;
        insertCommand.Parameters[3].Value = nameValue + "_Description";

        int affected = insertCommand.ExecuteNonQuery();
        Console.WriteLine("# of affected row: " + affected);
    }

    mytransaction.Commit();
}

SqliteTransaction을 사용하지 않고 직접 "begin", "end" 명령어를 사용해도 무방합니다. 아래는 해당 명령어를 이용한 래퍼 클래스의 사용법을 보여줍니다.

using (SqliteBulk bulk = new SqliteBulk(connection))
{
    for (int i = 0; i < 100; i++)
    {
        string nameValue = "Name" + Guid.NewGuid().ToString();
        insertCommand.Parameters[0].Value = (int)DateTime.Now.Ticks;
        insertCommand.Parameters[1].Value = nameValue;
        insertCommand.Parameters[2].Value = 10;
        insertCommand.Parameters[3].Value = nameValue + "_Description";

        int affected = insertCommand.ExecuteNonQuery();
        Console.WriteLine("# of affected row: " + affected);
    }
}

public sealed class SqliteBulk : IDisposable
{
    SqliteConnection _connection;

    public SqliteBulk(SqliteConnection connection)
    {
        _connection = connection;
        new SqliteCommand("begin", connection).ExecuteNonQuery();
    }

    public void Dispose()
    {
        new SqliteCommand("end", _connection).ExecuteNonQuery();
    }
}




참고로, sqlite 파일의 내부를 보려면 별도의 도구를 다운로드해야 합니다.

DB Browser for SQLite
; https://sqlitebrowser.org/dl/

또한, Microsoft.Data.SQLite는 내부적으로 SQLitePCL을 라이선스해 사용하는 것으로 보이는데, 이에 대한 소스 코드도 다음의 사이트에 공개되어 있습니다.

ericsink/SQLitePCL.raw
    - A Portable Class Library (PCL) for low-level (raw) access to SQLite
; https://github.com/ericsink/SQLitePCL.raw

그 외에 다른 글을 찾아보면,

C#으로 SQLite 다루기
; http://www.gisdeveloper.co.kr/?p=2290

SQLite 공식 사이트에서도 배포하는 패키지가 있다고 하니, Microsoft.Data.SQLite 대신 그것을 사용해도 무방할 것입니다. (그나저나... 공식 사이트에서 배포한 버전이 좋을까요? Microsoft가 배포한 버전이 좋을까요? ^^)




예외 상황 하나 정리해 보면, SqliteTransaction 트랜잭션 사용 시 SqliteCommand의 Transaction 속성에 명시적으로 트랜잭션 인스턴스를 설정하지 않으면,

// insertCommand.Transaction = mytransaction;

다음과 같은 식의 예외가 발생합니다.

Unhandled Exception: System.InvalidOperationException: Execute requires the command to have a transaction object when the connection assigned to the command is in a pending local transaction.  The Transaction property of the command has not been initialized.
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteNonQuery()
   at ConsoleApp1.Program.Main(String[] args) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 57




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/19/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2020-05-16 12시06분
[gwise] Microsoft.Data.Sqlite vs System.Data.SQLite 2개중에 어느게 좋은가요? 궁금합니다.
[guest]
2020-05-16 12시25분
그러게요... ^^ 어느게 좋을지 저도 모르겠군요. (gwise 님의 블로그에 테스트 해보시고 정리한 글을 올리시면 어떨까요? ^^)
정성태
2020-05-16 02시18분
[gwise] Prepare()이 System.Data.SQLite는 안되어 있고 Microsoft.Data.Sqlite는 구현되어 있는거 같습니다.
그리고 저장/조회 속도면에서 Microsoft.Data.Sqlite가 조금 더 빠르긴 하나 이건 사용자가 느낄 정도는 아니라서..
저는 .net 4.5를 사용해야 해서 System.Data.SQLite를 사용하는데 이 조건이 아니라면 Microsoft.Data.Sqlite이 좋아 보입니다.
[guest]
2022-12-09 11시48분
[gwise] 2년을 지나 검색해서 이 글에 다시 왔습니다.
로컬 프로그램 개발이 필요해서 Postgresql과 Sqlite를 고민 했으나 Postgresql는 사용자가 별도 설치를 해야 해서 Sqlite로 결정했는데 암호화에서 문제가 됐습니다.
 System.Data.SQLite는 기능(DataAdapter 개꿀..넘좋음)이 많으나 암호화 하려면 SEE라이선스 $2,000을 구입해야 해서 결국 Microsoft.Data.Sqlite 를 사용하게 됐습니다.

Microsoft.Data.Sqlite에서 DataAdapter 가 없어서 아래 처럼 비슷하게 사용했습니다.
SqliteDataReader reader = selectCommand.ExecuteReader();
dt.Load(reader);

암호화는 아래 처럼 하면 된다고 되어 있는데 역시 한번에 안되고
https://learn.microsoft.com/ko-kr/dotnet/standard/data/sqlite/encryption?tabs=netcore-cli

C:\Users\gwise\.nuget\packages\sqlitepclraw.lib.e_sqlcipher\2.1.3 해당 폴더의 runtimes 폴더 전체를 Debug에 복사 해 두고 하면 됩니다.
물론 배포 할 때도 runtimes 폴더 같이 해야 합니다.

이렇게 해서 테스트 해보니 암호화는 잘 됐습니다. 생성된 db파일을 DB Browser (SQLite)에서 열어 볼려고 하니 안 열리고 DB Browser (SQLCipher)에서 비밀번호 입력해야 열립니다.

성능은 잘 모르겠고 개발 편의성은 System.Data.SQLite이 개꿀이긴 하나 암호화를 해야 한다면 Microsoft.Data.Sqlite을 선택하는걸 추천합니다.
물론 돈이 많아서 200만원 정도 주도 SEE 살 정도 되면 System.Data.SQLite으로 선택

성태님 감사해요~!. 저의 닷넷 개발의 대부분은 성태님의 지식에서 기반 합니다. ^^
[guest]
2022-12-09 12시25분
좋은 정보 공유 감사드립니다. 끈기 있으시군요. ^^ gwise 님은 제가 아니었더라도 docs/learn 사이트를 참고하며 잘 공부하셨을 것입니다.
정성태
2023-04-18 10시02분
[guest] Sqlite는 암호화의 문제가 있군요nn
[guest]

... 91  92  93  [94]  95  96  97  98  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11584정성태7/5/201818248Math: 35. GeoGebra 기하 (12) - 삼각형의 내심과 내접하는 원파일 다운로드1
11583정성태7/5/201818049.NET Framework: 785. public으로 노출되지 않은 다른 어셈블리의 delegate 인스턴스를 Reflection으로 생성하는 방법파일 다운로드1
11582정성태7/5/201824652.NET Framework: 784. C# - 제네릭 인자를 가진 타입을 생성하는 방법 [1]파일 다운로드1
11581정성태7/4/201821382Math: 34. GeoGebra 기하 (11) - 3대 작도 불능 문제의 하나인 임의 각의 3등분파일 다운로드1
11580정성태7/4/201818161Math: 33. GeoGebra 기하 (10) - 직각의 3등분파일 다운로드1
11579정성태7/4/201817242Math: 32. GeoGebra 기하 (9) - 임의의 선분을 한 변으로 갖는 정삼각형파일 다운로드1
11578정성태7/3/201817393Math: 31. GeoGebra 기하 (8) - 호(Arc)의 이등분파일 다운로드1
11577정성태7/3/201817332Math: 30. GeoGebra 기하 (7) - 각의 이등분파일 다운로드1
11576정성태7/3/201819520Math: 29. GeoGebra 기하 (6) - 대수의 4칙 연산파일 다운로드1
11575정성태7/2/201819959Math: 28. GeoGebra 기하 (5) - 선분을 n 등분하는 방법파일 다운로드1
11574정성태7/2/201818454Math: 27. GeoGebra 기하 (4) - 선분을 n 배 늘이는 방법파일 다운로드1
11573정성태7/2/201817787Math: 26. GeoGebra 기하 (3) - 평행선
11572정성태7/1/201817128.NET Framework: 783. C# 컴파일러가 허용하지 않는 (유효한) 코드를 컴파일해 테스트하는 방법
11571정성태7/1/201818576.NET Framework: 782. C# - JIRA에 등록된 Project의 Version 항목 추가하는 방법파일 다운로드1
11570정성태7/1/201818744Math: 25. GeoGebra 기하 (2) - 임의의 선분과 특정 점을 지나는 수직선파일 다운로드1
11569정성태7/1/201817952Math: 24. GeoGebra 기하 (1) - 수직 이등분선파일 다운로드1
11568정성태7/1/201830180Math: 23. GeoGebra 기하 - 컴퍼스와 자를 이용한 작도 프로그램 [1]
11567정성태6/28/201819470.NET Framework: 781. C# - OpenCvSharp 사용 시 포인터를 이용한 속도 향상파일 다운로드1
11566정성태6/28/201825156.NET Framework: 780. C# - JIRA REST API 사용 정리 (1) Basic 인증 [4]파일 다운로드1
11565정성태6/28/201822018.NET Framework: 779. C# 7.3에서 enum을 boxing 없이 int로 변환하기 - 세 번째 이야기파일 다운로드1
11564정성태6/27/201820460.NET Framework: 778. (Unity가 사용하는) 모노 런타임의 __makeref 오류
11563정성태6/27/201819284개발 환경 구성: 386. .NET Framework Native compiler 프리뷰 버전 사용법 [2]
11562정성태6/26/201818766개발 환경 구성: 385. 레지스트리에 등록된 원격지 스크립트 COM 객체 실행 방법
11561정성태6/26/201830103.NET Framework: 777. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! [8]파일 다운로드1
11560정성태6/25/201821397.NET Framework: 776. C# 7.3 - 초기화 식에서 변수 사용 가능(expression variables in initializers)파일 다운로드1
11559정성태6/25/201828576개발 환경 구성: 384. 영문 설정의 Windows 10 명령행 창(cmd.exe)의 한글 지원 [6]
... 91  92  93  [94]  95  96  97  98  99  100  101  102  103  104  105  ...