lecture Home > ASP.NET > .NET Board

.NET 게시판 강좌 - 김연진님 제공

   강좌 최초 작성일 : 2001년 09월 10일
   강좌 최종 수정일 : 2001년 09월 17일

   작성자 : Cassatt(김 연진)
   편집자 : Taeyo(김 태영)

   강좌 제목 : Table 만들기 & .NET에서의 DB연결

3.1 테이블 만들기

게시판을 만들기 위해 먼저 할일은 DB에 테이블을 만드는 것입니다. 이번 강좌에선, 테이블을 만든후 테스트하고, 닷넷에서 쿼리문 실행하는 방법을 간단히 알아보려 합니다.

DB를 만든 후, Query Analyser 에서 다음과 같은 쿼리문을 실행해서 테이블을 만듭니다.

create table cstBoard (
  seq int not null identity(1,1),
  writer varchar(20) not null,
  pwd varchar(20) not null,
  email varchar(100) null,
  title varchar(200) not null,

  ip int not null,
  writeDate smalldatetime not null,
  readed int not null default(0),
  mode tinyint not null,
  content text,

  constraint pk_cstBoard_seq primary key clustered ( seq desc )
)

cstBoard에서 cst 는 cassatt의 약자입니다. 앞으로 테이블명이나, 클래스 이름등의 앞에는 cst를 붙여서 다른 것과 혼동되지 않도록 구분하려합니다.

테이블 필드를 설명하면 다음과 같습니다

seq자동 증가형 값으로, 각 글을 구별하는 key로 쓰입니다.
또한 글 목록 보기에서는 게시물을 쓴 역순으로 정렬하기 위해 사용됩니다.
writer작성자
pwd비밀번호
email메일주소
title제목
ip작성자 아이피.
int 형으로 되어 있는 것은 아이피rk 4byte int 형으로 표현이 가능하기 때문입니다.
ip -> 정수, 정수 -> ip로 변환하는 함수도 만들 것입니다.
writeDate글쓴 날짜. 아주 정확할 필요는 없기 때문에 분단위 정확도인 smalldatetime을
사용하였습니다.. 그냥 datetime으로 하셔도 됩니다. insert 시, ms-sql 함수인
getdate() 로 글을 쓴 시간을 넣습니다.
readed조회수
mode글이 html 태그를 허용(1)하는지, 아닌지(0) 나타냅니다.
bit가 아닌, tinyint로 하는 것은 나중에 다른 옵션이 추가될 수 있기 때문입니다.
content글의 내용

그리고 constraint 절은 seq 필드를 primary key로 잡는 문장입니다. 글을 구별하는 것이 seq 필드이고, 또한 글 정렬이 seq 역순으로 되기 때문에 seq를 pk로 잡습니다. desc는 역순 인덱스임을 나타냅니다.
* 주의 : ms-sql 7.0에서는 desc 를 빼세요. ms-sql 2000 이상만 되나 봅니다.

자세한 설명은 생략합니다. 이것은 query analyser용인데, 불편하신 분은 Enterprise Manager를 사용해도 상관없습니다. 다만 seq 필드에 primary key를 주시는 것은 잊지 마시기 바랍니다. seq 필드가 pk냐, 아니냐에 따라 퍼포먼스가 엄청나게 차이납니다. 글의 수가 증가함에 따라 말 그대로 "기하 급수적"으로 차이가 납니다. ( 실제로, 수학적으로 따져도 기하 급수적입니다 ) 기본적인 얘기겠지만, 의외로 index를 과소평가하는 분들이 ( 제가 느끼기에 ) 많은 것 같아서 강조합니다.
그리고 역순으로 주는데요, 역순이냐 아니냐는 큰 차이가 나지는 않습니다.

 

3.2 닷넷 에서의 DB연결

우선 닷넷에서의 DB연결을 테스트해보고, 쿼리 실행해 대해 간단히 알아보도록 하겠습니다.

<%@ Import NameSpace="System.Data.SqlClient" %>

<script language="C#" runat="server">
  int recordCount= 0; 
  void Page_Load( Object o, EventArgs e) {
     SqlConnection dbConnection;
     dbConnection=new SqlConnection("server=(local);uid=cassatt;pwd=1111;database=taeyo;" );
     dbConnection.Open();
     SqlCommand cmd;
     cmd = new SqlCommand( "select count(*) as cnt from cstBoard", dbConnection );
     SqlDataReader dr;
     dr=cmd.ExecuteReader();
     if(dr.Read()) {
       recordCount=(int)dr["cnt"];
     }
     else {
       recordCount=0;
     }
     dr.Close();
     dbConnection.Close();
     DataBind();
  }
</script>

게시판에는 <%# recordCount %> 개의 레코드가 있습니다.

이것은 db에서 cstBoard라는 테이블에서 레코드 갯수를 알아내서 출력하는 예제입니다. 앞의 쿼리문 대로 테이블을 만드셨다면, 아직은 레코드가 하나도 없을 것이므로, "게시판에는 0 개의 레코드가 있습니다"라는 메시지가 나오면 성공입니다.

이 예제를 이용해서, asp.net에서 DB 사용이 어떻게 될지, 개략적으로만 설명해 드리겠습니다.

우선, 여기에 쓰일 개체인 SqlConnection이나, SqlDataReader등을 사용하기 위해서는 다음처럼, System.Data.SqlClient 라는 NameSpace를 Import 해와야 합니다.

<%@ Import NameSpace="System.Data.SqlClient" %>

c로 치면 include 문 쯤 됩니다. 각 개체별로, 속한 NameSpace(이름 공간)가 있습니다. 어떤 개체를 사용하려면, 먼저 그 개체가 속한 NameSpace를 사용한다고, 알려줘야 합니다. ASP.net 내에는 워낙에 많은 개체가 있기에, 이런식으로 안하면 관리가 안됩니다.
namespace에 대해서, 사실 저도 정확한 의미는 모릅니다. -_-; 다만, 작업하실때, 어느 개체의 형식 또는 NameSpace를 찾을 수 없다거나 하는 에러가 날때가 있을겁니다. 그럴땐 도움말에서 그 개체를 찾으면 아래 부분에 NameSpace가 나오는데, 그 NameSpace를 Import 해주시면 됩니다.

예전의 ADO와 마찬가지로, 우선 해당 DB와 Connection을 열어야 합니다.

 
SqlConnection dbConnection;
dbConnection=new SqlConnection("server=(local);uid=cassatt;pwd=1111;database=taeyo;" );
dbConnection.Open();

위에서는 local 서버에, cassatt라는 아이디, 비밀번호 1111로 taeyo라는 database에 연결하고 있습니다.

전과 비슷해 보입니다만, 연결 문자열에 전에 썼던 dsn=xxx 식의 odbc 이름은 쓸 수 없습니다. server=서버이름; 식으로 MS-SQL 서버이름을 적어주어야 하죠. 도움말을 보니 다른 OLE-DB는 OleDbConnection을 사용해야 한다고 되어 있습니다.
"... SqlConnection 은 MS SQL 서버 데이터베이스에 연결할때, 퍼포먼스를 높이기 위해 SqlDataAdapter나 SqlCommand와 결합하여 사용한다. 다른 서드-파티 SQL 서버 제품 ( OLE DB 가 지원되는 다른 Data source를 포함) 은 OleDbConnection을 사용하라 ... "
말하자면, SqlConnection은 ODBC용도 아닐 뿐더러, MS-SQL에만 쓰이는 겁니다. Provider=xxx 같은 ole-db 연결도 안됩니다. ole-db 는, OleDbConnection, OleDbCommand, OleDbDataReader등 ole-db용 개체를 써야 합니다.
ODBC는 SDK에 아예 포함되어 있지 않고요, http://msdn.microsoft.com/downloads/default.asp?URL=/code/sample.asp?url=/MSDN-FILES/027/001/668/msdncompositedoc.xml 현재 여기서 ODBC .NET Data Provider Beta 1 을 다운받을 수 있습니다.
위에 준 uid/pwd 같은 것 외에도 여러 옵션을 줄 수 있습니다. 자세한 내용은 도움말에서 SqlConnection의 프로퍼티인 ConnectionString 을 찾아보시면 됩니다.

아직 저도 자세히 모르기 때문에 알려드리기 어렵습니다. 세부 옵션이나, Access나, Oracle, MySql 등의 다른 DB에 대해서는 다른 분의 강좌에 의해 채워지리라 생각합니다. MySql은 당분간은 ODBC addon을 다운받아 쓰셔야 할것 같고요, Oracle은 OleDb로, Access는 Jet 엔진을 Ole db 연결하거나, MSDE 를 이용하시면 될것 같습니다.

Connection을 열었으면, SqlCommand 개체를 생성하고, SqlDataReader로 DB의 내용을 읽습니다.

cmd = new SqlCommand( "select count(*) as cnt from cstBoard", dbConnection );
SqlDataReader dr;
dr=cmd.ExecuteReader();
if(dr.Read()) {
  recordCount = (int)dr["cnt"]; 
}else {
  recordCount = 0;
}

여기서는 최대 1줄이기 때문에 if(dr.Read()) 식으로 if 문을 썼습니다만, 보통 while 문으로 루프를 돌게 됩니다.

마지막으로 SqlDataReader와 컨넥션을 닫습니다.

dr.Close();
dbConnection.Close();

개략적인 형태는 이와 같습니다. 이 예제는 SqlDataReader로 읽어들이는 예제 인데요, 여러 다른 방식들이 있습니다.

 

3.2.1 SqlCommand.ExecuteReader() 메서드

우선, 앞에 쓰인 SqlCommand.ExecuteReader 메서드는 레코드를 읽어들일때 쓰이는데, 대략 다음과 같은 모습을 하게 됩니다.

SqlCommand cmd;
cmd = new SqlCommand("select * from xxx", dbConnection);
SqlDataReader dr;
dr=cmd.ExecuteReader();
while( dr.Read() ) {
  ... 여기서 dr["필드이름"] 을 해당 형으로 cast 해서 (ex. (string)dr["title"]) 사용합니다.
  ... dr.Read() 시에, 한 레코드씩 읽게 됩니다.
}
dr.Close();

SqlDataReader.Read()가 호출될 때마다 레코드가 한줄 한줄 읽히게 됩니다. 레코드를 읽었으면 true/읽지 못했으면 false가 되기 때문에, 위처럼 while 문으로 루프를 돌게 됩니다. Read() 자체가 하나를 읽고, 다음레코드로 가기때문에, 전처럼 RecordSet.MoveNext() 같은 메서드는 필요없습니다
레코드를 다 읽었으면, SqlDataReader.Close()를 호출해서, SqlDataReader를 닫아주는 것도 중요합니다. ( 닫지 않고 다른 DB 작업을 하려하면 Runtime 에러 납니다 )
이번 게시판에선 글 목록 보여줄때 사용하게 됩니다.

 

3.2.2 SqlCommand.ExecuteNonQuery() 메서드

한편, SqlCommand.ExecuteNonQuery 메서드는 결과 레코드가 필요 없을때 쓰입니다. update 문이나 insert 문이 해당되겠죠.

SqlCommand cmd;
cmd = new SqlCommand("update * from xxx where xx=yy", dbConnection );
cmd.ExecuteNonQuery();

예제에서는 간단한 쿼리문으로 사용되었지만, 정식으로(?) 하자면 넘길 값(parameter)를 정확히 지정하게 됩니다. 글 쓰기(write.aspx)에서 좀더 자세히 다룹니다.

 

3.2.3 SqlCommand.ExecuteScalar() 메서드

이외에, 이번 beta2에 포함된 것중, ExecuteScalar가 있는데요, 이 메서드는 레코드 하나만 읽습니다.

SqlCommand cmd;
cmd = new SqlCommand("select count(*) from xxx", dbConnection );
co = (int)cmd.ExecuteScalar();

count(*), sum, max 값 같은 경우에 유용하게 쓰이리라 생각됩니다.

 

3.2.4 SqlDataAdapter + DataSet

그리고 SqlCommand 대신 SqlDataAdapter/DataSet을 이용하는 방법도 있습니다.

...
SqlDataAdapter myCommand = new SqlDataAdapter("select * from theTable",dbConnection);
DataSet ds = new DataSet(); 
myCommand.Fill(ds,  "theTable"); 

...
이하 DataTable개체인 ds.Tables["theTable"] 로 작업한다 

아직은 이 개체를 잘 알지 못해서 많은 설명을 못드리겠네요. 도움말에는 "SqlDataAdapter는 DataSet과 Sql 서버 사이에서, 자료를 얻거나 저장하기 위한 다리bridge 역할을 한다"고 되어 있습니다. SqlDataAdapter에는 중요한 메서드로 Fill 과 Update 를 제공하는데요, 위에도 쓰인 Fill은 자료를 얻어오기(retrive)위해서, Update는 자료를 저장하기 위해서 쓰입니다.

위 소스에선 select 쿼리문으로 SqlDataAdapter를 생성하고, 그 내용을 DataSet에 채웁니다(Fill). 일단 채워지면, 그것은 배열 쓰듯이 사용할 수 있고요, DataGrid라는 개체와 연동하면 페이징을 할 수 있습니다.

많이 사용해 보지 못했기 때문에, 이 방식의 장/단점을 말씀 드리기 어렵습니다. 제가 잘 몰라 짧게 설명합니다만, 사용 방법도 복잡하고, 내용이 많습니다.

SqlDataAdapter는 기능이 많다는 대신, 속도가 느려질 수 있다는 단점이 있습니다. /QuickStart의 퍼포먼스 팁 란에는, ( 만약 퍼포먼스가 문제된다면 ) DataSet 보다 SqlDataReader 사용을 해보라고 권하더군요.

이것은, 전에 asp 레코드 셋에서 지원하는 페이징 쓸때와 상황이 비슷합니다. 위 소스에서 myCommand.Fill(ds, "xxx") 식으로 Fill 메서드를 사용하였는데요, 이러면 해당 쿼리문의 모든 레코드를 가져와 해당 DataSet에서 저장합니다. 그렇다면, 레코드 수가 많거나, DB서버와 웹서버가 분리되어 있는 경우엔 느려질 수 밖에 없겠죠.

myCommand.Fill(ds, 1,20, "xxx") 처럼, 어디서 부터 어디까지(위에선 1번째~20번째) 가져오는 것이 가능하긴 합니다만, 이러면 (제가 아는 한 ) 총 레코드 수를 구할 수 없어서, 게시판 글 목록 보기에 사용하긴 어렵습니다. SqlDataAdapter + DataSet + DataGrid 의 페이징을 사용하면 좋겠는데, 그러기 위해선 총 레코드수와 총 페이지수를 구하기위해 모든 레코드를 가져와야 하고, 그러면 레코드 수가 많아질 수록 느려집니다.

이번 게시판에서는 SqlDataAdapter 대신 SqlDataReader 만을 사용합니다. 제가 전에 쓰던 방법인, 서브 쿼리를 사용해서 중간만 끊어오도록 했는데요, 이 방식에선 SqlDataReader면 충분합니다.

SqlDataAdapter 쓰는 페이징 경우 테스트를 많이는 못해봤는데, 전의 adOpenStatic 같은 커서로 열어서 페이징 하는 것보다는 빨라 보이더군요. 앞으로 많은 얘기 있었으면 좋겠습니다.

 

Back