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


Study/C#  2021. 10. 7. 14:48

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


이 글은 C#에서 데이터 베이스(MSSQL)에 접속하는 방법에 대한 글입니다.


우리가 프로그램을 작성하다보면 데이터를 저장하는 경우가 많은데, IO를 이용한 파일로 저장하거나 Socket 서버를 이용해서 다른 PC나 다른 프로그램을 이용해서 저장하는 방법 등등이 있습니다.

그러나 데이터를 저장하기 가장 쉽고, 검색이나 필터를 하기 쉬운 방법으로 데이터 베이스를 이용하는 방법이 있습니다.


데이터 베이스의 종류는 엄청나게 많지만 그 중에서 가장 대표적인 것이 Oracle(오라클), MsSQL(Sql-server), MySql 혹은 MariaDB가 가장 많이 사용하는 데이터 베이스입니다.

그 중에서 MsSQL은 MS(Microsoft)사에서 제공하는 데이터 베이스이고, C#도 MS(Microsoft)사에서 제공하는 프로그램 언어이기 때문에 C#에서는 상대적으로 MsSQL이 다루기가 가장 쉽습니다. 다루기가 쉽다기 보다는 별로의 라이브러리를 연결하지 않아도 사용할 수 있는 데이터 베이스입니다.


먼저 데이터 베이스를 접속하기 위해서는 데이터 베이스를 설치해야 하는데 그건 다른 글에서 소개하고 있으니 참고하세요.

링크 - [MSSQL] MSSQL Express 설치하기


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

-- drop table Test;
-- 테이블 작성
create table Test(
	idx int identity(1,1),
	data nvarchar(max)
)
-- 데이터 입력
insert into Test values('Hello world - 1');
insert into Test values('Hello world - 2');
insert into Test values('Hello world - 3');
insert into Test values('Hello world - 4');
insert into Test values('Hello world - 5');
insert into Test values('Hello world - 6');
insert into Test values('Hello world - 7');
insert into Test values('Hello world - 8');
insert into Test values('Hello world - 9');
insert into Test values('Hello world - 10');
insert into Test values('Hello world - 11');
insert into Test values('Hello world - 12');
-- 검색
select * from Test;

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

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

using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // SqlCommand 클래스의 인스턴스 생성
      var cmd = new SqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new SqlConnection("Data Source=localhost;Database=BlogExample;User Id=sa;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();
    }
  }
}

위 예제를 보면 데이터 베이스에 있는 Test의 테이블의 데이터를 읽어와서 콘솔에 표시했습니다.

SqlCommand 클래스의 인스턴스를 생성해서 커넥션을 만들고 ExecuteReader의 함수를 실행해서 데이터를 읽어옵니다.

그리고 ExecuteReader의 함수의 리턴값은 SqlDataReader의 인스턴스이고 SqlDataReader의 Read함수를 이용해서 데이터를 읽어 옵니다.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // SqlCommand 클래스의 인스턴스 생성
      var cmd = new SqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new SqlConnection("Data Source=localhost;Database=BlogExample;User Id=sa;Password=");
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // 검색해서 가져오는 데이터 변수
      List<List<object>> dataList = null;
      // 검색한 테이블의 Column 변수
      string[] fieldList = null;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 테이블 삭제 쿼리
        cmd.CommandText = "delete Test";
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
        
        // 테이블에 데이터 추가
        cmd.CommandText = "insert into Test (data) values(@data)";
        // @data 파라미터에 데이터를 입력한다.
        cmd.Parameters.Add(new SqlParameter("@data", "Hello world - Addition"));
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
        // 테이블에서 데이터 검색
        cmd.CommandText = "select * from Test";
        // 위 쿼리를 접속해서 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();
    }
  }
}

위 예제에서는 delete와 insert를 해서 데이터를 삭제하고 추가했습니다.

그리고 함수는 ExecuteReader의 함수가 아니고 ExecuteNonQuery의 함수를 사용해서 실행했습니다.

왜냐하면 delete와 insert는 검색의 결과를 가져오는 것이 아니고 실행만 하는 것이 중요하기 때문입니다.


위 방법은 연결 지향형 데이터 베이스 접속이라고 합니다. 즉, 데이터 베이스의 데이터를 한번에 다 가져오는게 아니고 Read() 함수를 통해서 하나하나 취득해오는 것이기 때문입니다. 연결 지향형에서는 시스템에 대한 부담은 상대적으로 적을 수 있으나 데이터가 많으면 데이터 베이스의 lock이나 트랙젝션 문제가 있을 수 있습니다.

그래서 레코드 하나하나식 읽어오면 연결 지향형이 아니고 데이터를 한번에 취득해 오는 비연결 지향형 접속 방식이 있습니다.

using System;
using System.Data;
using System.Data.SqlClient;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // SqlDataAdapter 인스턴스 생성
      var sda = new SqlDataAdapter();
      // SqlCommand 클래스의 인스턴스 생성
      sda.SelectCommand = new SqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      sda.SelectCommand.Connection = new SqlConnection("Data Source=localhost;Database=BlogExample;User Id=sa;Password=");
      // 검색할 쿼리를 넣는다.
      sda.SelectCommand.CommandText = "select * from Test";
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      sda.SelectCommand.CommandType = CommandType.Text;
      // DataSet 인스턴스 생성
      var ds = new DataSet();
      // SqlDataAdapter 인스턴스에 DataSet 데이터를 넣음
      sda.Fill(ds);
      foreach (DataTable table in ds.Tables)
      {
        // 출력을 위해 콘솔 상단에 Column 이름을 출력한다.
        foreach (DataColumn column in table.Columns)
        {
          // 콘솔 출력
          Console.Write(column.ColumnName);
          // tab 두번 콘솔 출력
          Console.Write("\t\t");
        }
        // 개행
        Console.WriteLine();
        // 테이블 Column 명과 데이터를 구분하기 위해 콘솔 출력
        for (int i = 0; i < table.Columns.Count; i++)
        {
          // 콘솔 출력
          Console.Write("-----------------");
        }
        // 개행
        Console.WriteLine();
        // 테이터의 개수만큼
        foreach (DataRow row in table.Rows)
        {
          // 데이터를 column의 수만큼
          foreach (DataColumn column in table.Columns)
          {
            // 콘솔 출력
            Console.Write(row[column]);
            // tab 두번 콘솔 출력
            Console.Write("\t\t");
          }
          // 개행
          Console.WriteLine();
        }
      }

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

결과는 연결 지향형으로 데이터를 가져오는 것과 같습니다. 데이터 베이스에 데이터를 가져오는 것은 같습니다만 데이터를 취득하는 방식에 대한 차이입니다.

이 경우에는 데이터를 한번에 가져와서 DataSet에 가져오기 때문에 데이터가 커지게 되면 프로그램 쪽에 많은 인스턴스 생성으로 인해 오히려 느려지고 메모리 부담이 생기게 되겠네요. 대신에 데이터 베이스에서는 한번에 Select 하고 난 후여서 조금 더 부담이 적겠네요.

즉, 상황에 맞게 연결 지향형으로 할지 비연결 지향형으로 설정하여 사용하면 되겠네요.


여기까지 C#에서 데이터 베이스(MSSQL)에 접속하는 방법에 대한 글이었습니다.


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