[C#] 46. Nuget 사용법(외부 라이브러리)과 데이터 베이스(MariaDB(Mysql)) 사용법 그리고 트랜젝션(Transaction)


Study/C#  2021. 10. 8. 17:57

안녕하세요. 명월입니다.


이 글은 C#에서 Nuget 사용법(외부 라이브러리)과 데이터 베이스(MariaDB(Mysql)) 사용법 그리고 트랜젝션(Transaction)에 대한 글입니다.


C#이 언어적으로 장점이라고 생각되는 부분은 .Net framework 안에 기본적인 라이브러리가 정말 많이 포함되어 있다는 것입니다. 그 외에도 Visual studio의 IDE 툴도 굉장히 편하고, 같은 제품군(MS 제품군)에 대해서 호완성도 뛰어나다는 장점도 있습니다.

그런데 많은 라이브러리를 포함하고 있다고 해도, 실무에서 사용되는 모든 라이브러리를 가지고 있지 않습니다.


예를 들면, 기본적으로 C#에서는 MsSQL 데이터 베이스에 대한 접속 라이브러리를 가지고 있습니다. 따로 외부 라이브러리가 필요가 없습니다. 그렇기 때문에 다른 종류의 데이터 베이스의 라이브러리는 .Net framework에 없습니다.

한마디로 C#를 사용하면 MsSql를 사용하기를 권장하는 것입니다. 그런데 실무에서는 사양에 따른, 혹은 여러가지 이유로 개발 언어는 C#으로 개발해도 데이터 베이스는 Oracle(오라클)이나 Mysql(MariaDB)를 사용해야 할 경우도 있습니다.


그렇다면 외부 라이브러리를 사용해야 하는데 MariaDB 홈페이지에 들어가면 C# 라이브러리 파일이 있습니다.

링크 - mariadb net-connectors

이렇게 다운로드해서 사용해도 괜찮은데, 이렇게 하면 버젼 관리라던가 배포(Deploy)나 여러 사람과 프로젝트 공유를 할 때, 라이브러리 관리등의 문제가 발생하게 됩니다.


그래서 프로젝트 라이브러리 관리 툴이 있는데 Java에서는 maven이 있는 것처럼 C#에서는 Nuget이 있습니다.

Nuget은 Visual studio가 설치되어 있으면 별도로 설치할 필요는 없습니다.

프로젝트에서 References의 항목에서 마우스 오른쪽 클릭을 해서 Context 메뉴를 보면 Manage Nuget 항목이 있습니다.

Browse 탭에서 검색을 하면 설치할 수 있는 라이브러리가 표시됩니다.

우리는 MariaDB를 사용할 것이기 때문에 MySql.Data 라이브러리를 클릭하고 Version을 확인하고 Install를 누릅니다.

그러면 약관이 표시가 되고 Accept를 눌러서 설치를 시작합니다.

그리고 References의 항목을 열면 라이브러리가 연결되어 있는 것을 확인할 수 있습니다.

혹시 라이브러리가 의존성되어 있는 라이브러리가 있다면 (즉, 라이브러리에서 다른 라이브러리를 참조하고 있다면) 필요한 라이브러리도 자동으로 다 연결이 됩니다.


여기서 우리는 MariaDB를 사용할 것인데 왜 Mysql를 다운을 받는가 하면 사실 과거 Mysql를 예전에 무료 데이터 베이스였는데 Oracle에 인수되면서 유료화로 바뀌었습니다.

그래서 예전 Mysql를 만들던 팀이 다시 독립을 해서 무료 시절의 Mysql의 소스를 포크하여 무료 데이터 베이스인 MariaDB를 만들었습니다.

그러다 보니 MySql과 MariaDB는 비슷하지만 다른 데이터 베이스라고 생각하면 됩니다.


MariaDB를 설치하는 방법은 다른 글에서 소개하고 있으니 참고하세요.

링크 - [Window] MariaDB를 설치하는 방법


데이터 베이스가 설치 되었으면 간단한 테이블을 만들고 데이터를 넣고 C# 프로그램에서 검색해 보겠습니다.

-- drop table Test;
-- 테이블 생성
create table Test(
	idx bigint auto_increment,
	data varchar(1024),
	
	primary key(idx)
)
-- 데이터 입력
insert into Test (data) values('Hello world - 1');
insert into Test (data) values('Hello world - 2');
insert into Test (data) values('Hello world - 3');
insert into Test (data) values('Hello world - 4');
insert into Test (data) values('Hello world - 5');
insert into Test (data) values('Hello world - 6');
insert into Test (data) values('Hello world - 7');
insert into Test (data) values('Hello world - 8');
insert into Test (data) values('Hello world - 9');
insert into Test (data) values('Hello world - 10');
insert into Test (data) values('Hello world - 11');
insert into Test (data) values('Hello world - 12');
-- 검색
select * from Test;

위처럼 데이터 베이스에 테이블을 만들고 간단한 데이터를 입력했습니다.


이 데이터를 가지고 일단 C# 프로그램에서 데이터를 취득해 보겠습니다.

using System;
using System.Collections.Generic;
using MySql.Data.MySqlClient;
using System.Data;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection("Data Source=localhost;Database=BlogExample;SSL Mode=None;User Id=root;Password=");
      // 검색할 쿼리를 넣는다.
      cmd.CommandText = "select * from Test";
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // 검색해서 가져오는 데이터 변수
      List<List<object>> dataList = null;
      // 검색한 테이블의 Column 변수
      string[] fieldList = null;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 위 쿼리를 접속해서 SqlDataReader를 취득한다.
        var dr = cmd.ExecuteReader();
        // 데이터 변수 인스턴스 생성
        dataList = new List<List<object>>();
        // Column 변수 인스턴스 생성
        fieldList = new string[dr.FieldCount];
        // 테이블의 필드 크기만큼
        for (var i = 0; i < fieldList.Length; i++)
        {
          // 필드 명을 배열에 넣는다.
          fieldList[i] = dr.GetName(i);
        }
        // 레코드의 개수만큼 루프
        while (dr.Read())
        {
          // 레코드 데이터를 넣을 변수 리스트 생성
          var entity = new List<object>();
          // 데이터 변수 리스트에 넣는다.
          dataList.Add(entity);
          // Column의 크기만큼
          for (var i = 0; i < fieldList.Length; i++)
          {
            // 컬럼의 타입을 취득
            var type = dr.GetFieldType(i);
            // int 타입이라면
            if (type == typeof(int))
            {
              // int 타입으로 취득
              entity.Add(dr.GetInt32(i));
            }
            // string 타입이라면
            else if (type == typeof(string))
            {
              // string 타입으로 취득
              entity.Add(dr.GetString(i));
            }
            else
            {
              // object 타입으로 취득
              entity.Add(dr.GetValue(i));
            }
          }
        }
      }
      // 출력을 위해 콘솔 상단에 Column 이름을 출력한다.
      foreach (var field in fieldList)
      {
        // 콘솔 출력
        Console.Write(field);
        // tab 두번 콘솔 출력
        Console.Write("\t\t");
      }
      // 개행
      Console.WriteLine();
      // 테이블 Column 명과 데이터를 구분하기 위해 콘솔 출력
      for (int i = 0; i < fieldList.Length; i++)
      {
        // 콘솔 출력
        Console.Write("-----------------");
      }
      // 개행
      Console.WriteLine();
      // 데이터의 크기만큼
      foreach (var entity in dataList)
      {
        // Column의 크기만큼
        foreach (var column in entity)
        {
          // 콘솔 출력
          Console.Write(column);
          // tab 두번 콘솔 출력
          Console.Write("\t\t");
        }
        // 개행
        Console.WriteLine();
      }

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

사용 방법은 MsSQL에서 접속한 방법이랑 같습니다.

ExecuteReader로 결과 값을 받고 ExecuteNonQuery로 insert나 update, delete를 실행하는 방법입니다.

링크 - [C#] 45. 데이터 베이스(MSSQL)에 접속하는 방법


여기서 그럼 조금 궁금한 점이 생깁니다. 쿼리를 실행중에 에러가 발생되면 어떻게 될까요?

using System;
using MySql.Data.MySqlClient;
using System.Data;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection("Data Source=localhost;Database=BlogExample;User Id=root;SSL Mode=None;Password=");
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 테이블 삭제 쿼리
        cmd.CommandText = "delete from Test";
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
        // 에러를 발생!
        throw new Exception();
        // 테이블에 데이터 추가
        cmd.CommandText = "insert into Test values(@data)";
        // @data 파라미터에 데이터를 입력한다.
        cmd.Parameters.Add(new MySqlParameter("@data", "Hello world - Addition"));
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
      }

      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

일부러 에러를 발생 시켰습니다. 그런데 그 에러가 delete를 실행하고 에러를 발생합니다.

프로그램이 에러가 발생했기 때문에 프로그램에서 실행되는 모든 쿼리는 실행 취소가 되어야 하는데 테이블에서 데이터가 모두 삭제가 되어버렸습니다.

실제 업무에서도 처리중에 에러가 발생되면 프로그램 상에서 실행된 쿼리가 취소가 되어야 하는데 그렇지 않으면 중간에 쿼리 처리가 멈춘 불완전한 데이터가 되어버립니다.


그래서 트랜젝션 기능이 있는데 그 구역안에 완벽히 실행이 완료가 되어야 모든 쿼리가 실행되는 처리입니다.

using System;
using MySql.Data.MySqlClient;
using System.Data;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection("Data Source=localhost;Database=BlogExample;User Id=root;SSL Mode=None;Password=");
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 트랜젝션 생성
        using (var transaction = cmd.Connection.BeginTransaction())
        {
          try
          {
            // 테이블에 데이터 추가
            cmd.CommandText = "insert into Test values(@data)";
            // @data 파라미터에 데이터를 입력한다.
            cmd.Parameters.Add(new MySqlParameter("@data", "Hello world - Addition"));
            // 실행(반환 값이 필요없다.)
            cmd.ExecuteNonQuery();
            // 에러 발생
            throw new Exception();
            // 트랜젝션 커밋(트랜젝션 안에서 실행된 쿼리를 데이터 베이스에 실행)
            transaction.Commit();
          }
          catch
          {
            // 트랜젝션 롤백(트랜젝션 안에서 실행된 쿼리를 취소)
            transaction.Rollback();
          }
        }
      }
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

insert를 실행했지만 트랜젝션에 의해 중간에 에러가 발생하면 insert 실행되지 않은 것을 확인할 수 있습니다.

즉, 트랜젝션은 중간에 많은 쿼리를 실행했다 해도 에러가 발생하면 데이터 베이스에 적용되지 않도록 하는 기능입니다.


여기까지 Transaction 기능인데 위처럼 작성되면 무언가 프로그램답지 않은 소스입니다.

여기서 옵서버 패턴으로 프로그램을 작성해 보겠습니다.

using System;
using MySql.Data.MySqlClient;
using System.Data;
using System.Text;

namespace Example
{
  class Program
  {
    // Transaction 옵서버 패턴
    static T Transaction<T>(MySqlCommand cmd, Func<T> func)
    {
      // 트랜젝션 생성
      using (var transaction = cmd.Connection.BeginTransaction())
      {
        try
        {
          // 함수 실행
          T ret = func();
          // 트랜젝션 커밋(트랜젝션 안에서 실행된 쿼리를 데이터 베이스에 실행)
          transaction.Commit();
          // 함수의 결과를 리턴
          return ret;
        }
        catch
        {
          // 트랜젝션 롤백(트랜젝션 안에서 실행된 쿼리를 취소)
          transaction.Rollback();
          // class라면 null을 struct라면 기본 인스턴스를 리턴
          return default(T);
        }
      }
    }
    // Transaction 옵서버 패턴
    static void Transaction(MySqlCommand cmd, Action action)
    {
      // 트랜젝션 생성
      using (var transaction = cmd.Connection.BeginTransaction())
      {
        try
        {
          // 함수 실행
          action();
          // 트랜젝션 커밋(트랜젝션 안에서 실행된 쿼리를 데이터 베이스에 실행)
          transaction.Commit();
        }
        catch
        {
          // 트랜젝션 롤백(트랜젝션 안에서 실행된 쿼리를 취소)
          transaction.Rollback();
        }
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection("Data Source=localhost;Database=BlogExample;User Id=root;SSL Mode=None;Password=");
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // Transaction 실행
        Transaction(cmd, () =>
        {
          // 테이블에 데이터 추가
          cmd.CommandText = "insert into Test (data) values(@data)";
          // @data 파라미터에 데이터를 입력한다.
          cmd.Parameters.Add(new MySqlParameter("@data", "Hello world - Addition"));
          // 실행(반환 값이 필요없다.)
          cmd.ExecuteNonQuery();
        });
        // Transaction 실행
        var str = Transaction(cmd, () =>
        {
          // 데이터 검색
          cmd.CommandText = "select * from Test";
          // 버퍼
          var sb = new StringBuilder();
          // 실행
          using (var dr = cmd.ExecuteReader())
          {
            // 검색 레코드 수만큼
            while (dr.Read())
            {
              // 버퍼에 결과 입력
              sb.AppendLine($"{dr.GetValue(0)}\t{dr.GetValue(1)}");
            }
          }
          // 버퍼 리턴
          return sb.ToString();
        });
        // 결과 콘솔 출력
        Console.WriteLine(str);
      }
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제는 패턴을 써서 좀 더 프로그램다운 프로그램을 만들었습니다.

사실 C#에는 ORM 프레임워크라고 Entity 프레임워크가 있습니다. 직접적으로 데이터 베이스에 접속하기 위해 일일히 Transaction 설정하고 Open, Close 등을 만드는 경우도 있지만 대부분은 위처럼 데이터 베이스를 효율적으로 접속하기 위해 패턴으로 짜여져 있는 프레임워크가 있는데 그것이 ORM(Object reference mapping) 프레임워크인 Entity 프레임워크가 있습니다.

Entity 프레임워크에 대해서는 다른 글에서 상세하게 설명하겠습니다.


여기까지 C#에서 Nuget 사용법과 데이터 베이스(MariaDB(Mysql)) 사용법 그리고 트랜젝션(Transaction)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.