굉장히 오랜만에 글을 작성하는 것 같다.
최근에 취뽀하고 회사를 갔더니 자바는 하지도 못하고 C#만 할 것 같다.
그래서 급하게 언어도 새로 배워야 하고 .NET 개발도 처음이라 새로 배워야 하고 MSSQL도 처음해보고 전부 처음해보는 것 투성이라 배울게 굉장히 많다.
서론은 이만하고 오늘 이해한 것을 정리 해보아야겠다.
SQL 서버를 사용하려면 클라이언트 프로그램이 먼저 서버와 접속되어야 한다.
서버에 접속하려면 접속 시 사용하는 Connection String이 필요한데, 이 것은 서버명과 인증 방법, 초기 데이터베이스명 등을 지정 해주어야 한다.
//아래와 같은 식으로 서버 연결을 위한 Connection String을 사용할 수 있을 것이다.
string conn1 = "Data Source=[서버주소];Initial Catalog=[DB명]; Intergrated Security=SSPI;";
string conn2 = "Data Source=[서버주소];Initial Catalog=[DB명];User ID=[아이디];Password=[비밀번호];";
이렇게 내 데이터베이스 연결에 필요한 요소들을 갖춰줬으면 이제 SqlConnection으로 서버와 접속을 할 수 있게 된다.
SqlConnection conn = new SqlConnection(conn1);
conn.Open();
...
conn.Close();
위와 같이 SqlConnection을 이용해 클라이언트 프로그램과 서버를 연결을 시켜주고 Open()
,Close()
로 열어서 사용하고 닫을 수 있게 된다.
그렇다면 자주 사용하는 SQL 서버에 명령을 내리기 위한 클래스에는 어떤 것들이 있을까?
회사에서 SqlConnection 명령어 중 가장 많이 봤던 클래스는 SqlCommand와 SqlDataAdapter, SqlDataReader
이 세가지를 가장 자주 봤던 클래스인 것 같다.
첫 번째로 SqlCommand
, 이 클래스는 SQL서버에 어떠한 명령을 내리기 위해 사용하는 클래스이다. 이 클래스를 사용해 SQL을 읽어와서 실행한다고 이해하고 있는 중이다.
예를 들면 `SELECT * FROM Table;' 이라는 쿼리를 사용한다고 했을 경우
string sql = "SELECT * FROM Table"
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
이렇게 사용하게 되는데 SqlCommand(sql, conn)이 잘 이해가 안갈 수도 있다고 생각한다.
첫 번째 매개변수인 sql
부분은 우리가 위에서 사용하기 위해 만들었던 string sql
, 즉 쿼리가 들어가게 된다.
그래서 앞자리의 역할은 string
타입의 SQL 쿼리(문장)을 문자열로 전달하는 역할을 한다.
두 번째 매개변수인 뒷자리 conn
은 Sqlconnection
타입의 데이터베이스와의 연결 정보를 제공하는 객체를 넣어주어야한다.
그래서 뒷자리의 역할은 SqlCommand
는 "이 conn
이라는 SqlConnection
객체를 사용하여 데이터베이스에 SQL 쿼리를 실행할 수 있다~" 라고 이해하면 될 것이다.
SqlCommand에는 위에서 사용된 SqlCommand 생성자 오버로드 이외에도
빈 SqlCommand를 생성하는 SqlCommand()
,
지정된 SQL 쿼리로 SqlCommand 개체를 생성하는 SqlCommand(string query)
,
트랜잭션을 포함하여 생성하는 SqlCommand(string query, SqlConnection connection, SqlTransaction transaction)
이 있다.
두 번째로는 SqlDataAdapter
이다. 이 클래스는 SQL Server에서 데이터를 클라이언트로 가져온 후 데이터베이스와 연결을 끊은 상태(Disconnected mode) 에서 사용할 수 있도록 도와주는 클래스이다.
쉽게 설명을 한다면 SqlDataAdapter
는 데이터베이스와 클라이언트(프로그램) 사이에서 중간 역할을 한다. 그래서 데이터를 가져오고나면 데이터베이스와 연결을 끊어도 데이터 사용이 가능하다는 것이다.
그렇다면 그 데이터를 가져와서 채워놓아야하는데 그것을 위해서 주로 DataSet
또는 DataTable
과 함께 사용 된다.
그렇다면 어떻게 동작할까?에 대해서 알아보도록 하자
우선 데이터를 저장할 DataTable
객체를 생성해 주도록 하고, SqlDataAdapter
를 생성해 SQL을 실행해 데이터를 가져와 주도록 하겠다.
DataTable dt = new DataTable();
SqlDataAdapter da = new SqlDataAdapter(sql, conn);
이제 데이터를 가져왔으니 Fill()
메서드를 사용해서 데이터를 DataTable
에 저장해주어야 한다.
da.Fill(dt)
이로써 DataTable
객체에 SqlDataAdapter
로 실행하고 가져온 데이터를 저장하게 된고 Fill()
메서드를 사용하게 되면 해당 위치에서 연결이 잠깐 사용된다.
이 과정을 거치게 되면 메모리(DataTable)에 데이터를 저장하게 되어 이후 연결이 끊어진 상태가 되더라도 DataTable을 사용하여 DB 연결 없이 데이터를 활용하는 것이 가능하게 되는 것이다.
SqlConnection의 연결 중,
conn.Close()
을 호출하지 않고 안전하게 사용하고 싶다면using
을 사용하여 해당 블록 안에서만 연결이 유지되고Fill
이 끝나면 자동으로 닫히게도 만들 수 있다.
!using
을 사용하면 이벤트가 끝날 때 자동으로 연결이 닫히므로 불필요한 연결 유지 문제를 방지할 수 있다.
이후에는 DataTable인 dt를 가지고 저장된 테이블의 데이터를 활용해서 사용할 수 있다.
==========================================================================================================
if(dt.Rows.Count > 0)
{
this.tableNumb = dt.Rows[0]["컬럼명"].ToString(); //"컬럼명" 컬럼의 0번째 인덱스의 값을 문자열로 가져온다.
}
==========================================================================================================
foreach(DataRow row in dt.Rows)
{
this.comboBox1.Items.Add(row["컬럼명"].ToString();
}
==========================================================================================================
결론적으로 SqlDataAdapter
는 SQL 쿼리 결과를 DataTable에 저장하는 역할을 하며, Fill(DataTable)을 사용하여 데이터를 가져오고, 연결이 자동으로 닫힌다. 그래서 연결을 유지하지 않더라도 DataTable
을 활용하여 언제든 사용할 수 있다.
그래서 DataTable이란? SQL에서 가져온 데이터를 메모리 내에서 저장하고 활용할 수 있는 구조이고 Rows, Column 속성을 사용하여 데이터를 조작할 수 있다. 연결이 끊긴 상태에서도 데이터 조작이 가능하다(Disconnection Mode)
dt.Rows
란? DataTable
에 저장된 모든 행(Row)을 포함하는 컬렉션이며,
개별 행을 가져올 때는 dt.Rows[인덱스]
형태로 사용하고 특정 컬럼 값을 가져오려면 dt.Rows[인덱스][["컬럼명"]
을 사용한다.
foreach
를 사용하면 모든 행 데이터를 가져오기가 가능하며 foreach (DataRow row in dt.Rows)
를 사용하면 모든 데이터를 반복해서 가져올 수 있다.
이는 여러 개의 데이터를 순회하면서 사용할 때 유용하다.
foreach
에 대해서 자세한 점들은 나중에 기회가 된다면 따로 정리를 해보도록 하겠다.
마지막으로는 SqlDataReader
이다. SqlDataReader
는 SQL Server에서 데이터를 가져올 때, 빠르고 효율적으로 읽을 수 있도록 설계된 객체이다.
SqlDataReader
의 첫 번째 특징으로는 순방향으로만 데이터를 읽는다. 다시 말해 Forward-only
, 앞으로만 읽으므로 한 번 읽은 데이터는 다시 접근이 불가하다.
두 번째 특징으로는 연결된 상태에서 데이터를 읽으며, SqlConnection
을 닫을 때까지 데이터를 유지한다.
세 번째 특징으로는 while(reader.Read())
로 행을 한 줄씩 처리한다.
마지막으로는 빠른 속도와 적은 메모리 사용이 장점이며, DataTable
보다 성능이 좋다! 특히 대량의 데이터를 처리할 때 더욱 강점을 보인다.
이제 SqlDataReader
의 사용법을 알아보도록 하자.
우선 이전의 예제처럼 SqlConnetion
은 이미 연결되어 열려있다는 상태를 가정하고 진행하도록 하겠다. 그리고 쿼리를 SqlCommand
로 불러왔었다.
SqlDataReader dr = cmd.ExcuteReader(); //SQL 실행 후 Reader 반환
while(reader.Read())
{
int id = reader.GetInt32(0);
string name = reader.GetString(0);
}
reader.Close();
위의 과정을 설명하자면 SqlDataReader
클래스를 이용한 객체를 만들고 SqlCommand
로 불러온 값 들을 dr(DataReader)
에 반환해준다.
그리고 앞서 설명한 세 번째 특징처럼 while
문 반복을 이용하며 reader.Read()
로 한 행씩 읽게되고, 각 행의 id, name을 각각 GetInt32()
, GetString()
으로 타입에 맞게 컬럼 값을 가져오는 것이고, reader.Close()
를 이용해 데이터 리더를 닫게 된다.
위의 예제처럼 컬럼 인덱스로 값을 가져오는 경우도 있지만 다른 방법도 있으니 소개해보겠다. 하지만 인덱스로 값을 가져오는 것이 가장 빠르다고 하니 기억해두자.
인덱스로 값 가져오는 방법 이외에는 컬럼명을 사용하여 값을 가져오기, Null값 처리(DBNull 체크)도 가능하다.
=============================================================================================================================
//컬럼명을 사용하여 값 가져오기
int id = Convert.ToInt32(reader["ID"]); //ID라는 컬럼명을 사용
string name = reader["Name"].ToString(); //ID라는 컬럼명을 사용
==============================================================================================================================
//Null값 처리 (DBNull 체크)
int? age = reader.IsDBNull(2) ? (Int?)null : reader.GetInt32(2); //IsDBNull(컬럼 인덱스)를 사용해 Null 값을 체크하는 것이 가능하다.
==============================================================================================================================
자바만 배우다 C#을 처음하는 사람은 Null값 처리하는 부분에서 ?에 대해서 잘 모르고 궁금할 것이다.
그래서 잠깐 빠르게 설명하고 지나가도록 하겠다.
자바에서는 기본적으로
int?
와 같은 Nullable 타입이 없다. 또한int
,double
,boolean
,char
와 같이 기본형 타입(Primitive Type)은 null을 저장할 수 없고클래스(Class)
,배열(Array)
,인터페이스(Interface)
,열거형(Enum)
,Wrapper 클래스(Integer, Double, Boolean, Character)
등과 같은 참조형 타입(Reference Type)의 경우만 null을 가질 수 있다.
하지만 C#에서는 Java에서 기본형 타입과 비슷한 값 타입에서 Nullable 타입을 선언하여 null을 저장할 수 있다.
위의 설명과 같이 C#에서는 값 타입에서 Nullable
타입을 선언하여 null을 저장하고 사용할 수 있게 되어있으므로, int?
는 null
값을 가질 수 있는 int형 변수라는 뜻이 된다.
뒤에 있는 물음표는 자바에서도 있는 삼항 연산자이며 조건 ? 참일 때 값 : 거짓일 때 값
이며 조건이 참이면 앞의 값, 거짓이면 뒤의 값이 실행된다.
다시 예제로 돌아와서 int? age = reader.IsDBNull(2) ? (int?)null : reader.GetInt32(2);
라는 것은null
값을 저장할 수 있는 Nullable<int>
타입의 age
변수가 있으며,
해당 변수에는 reader.IsDBNull(2)
로 컬럼 인덱스 2번의 컬럼이 NULL
이면 null
을 저장하고, NULL
이 아닐 경우 reader.GetInt32(2)
를 통해 int
값을 가져와 저장한다.
지금까지 C#에서 SQL Server와 연동하여 데이터를 다루는 방법에 대해 정리해보았다.
특히, SqlConnection
, SqlCommand
, SqlDataAdapter
, SqlDataReader
등의 핵심 클래스를 통해 데이터베이스와 어떻게 상호작용하는지에 대해 학습했다.
자바와 비교했을 때 C#의 Nullable<T>
타입과 SQL의 NULL 값을 처리하는 방식이 다소 생소할 수 있고, 아직은 많이 부족하겠지만 이를 이해하면 보다 안전하고 유연한 데이터 처리 로직을 구현할 수 있을 것 같다.
이제 막 C#과 .NET 개발을 시작했지만, 한 걸음씩 나아가면서 꾸준히 학습해야겠다.
이 글이 이후에 다시 학습할 때 좋은 참고 자료가 될 수 있기를 바라며, 앞으로도 지속적인 정리를 통해 성장해 나가야겠다.