Value of Life

ODBC와 DAO(MFC) 본문

IT/데이터 베이스 프로그래밍

ODBC와 DAO(MFC)

앵글메이커 2008. 7. 17. 13:53
반응형

12 ODBC와 DAO

본장은 데이터베이스 프로그램을 작성할때 필수적으로 필요한 ODBC와 DAO에 대해 설명을 합니다. ODBC는 윈도우에 설치된 여러 데이터베이스 엔진을 쉽게 접근하며 SQL를 이용한 프로그래밍을 할때 자주사용되는 프로그래밍 입니다. DAO은 Microsoft 사에서 제공하는 PC용 데이터 베이스 엔진입니다. 이 두개의 사용법및 프로그래밍 방법은 매우 유사한 면이 많습니다. 본장에서는 ODBC를 기반으로 프로그래밍 하는 방법을 설명하고 이개념을 이용하여 DAO에 접근할것입니다.


1.ODBC

ODBC의 개념

ODBC는 Open DataBase Connectivity의 약자입니다. 데이터베이스 엔진을 이용하여 유저 인터페이스 프로그램을 작성할때 사용하는 어플리케이션 인터페이스 입니다. 조금 말이 어렵군요. 쉽게 이야기 한다면 여러 DBMS를 Visual C++프로그램에서 컨트롤 하거나 또는 Visual Basic등의 프로그래밍 언어나 툴에서 편리하게 컨트롤 하기 위한 인터페이스라는 것입니다.

DBMS란 데이터베이스 관리 시스템이라는 의미입니다. 오라클같은 프로그램을 DBMS라고 하는데 이것은 거대한 데이터베이스를 관리하는 데이터베이스 시스템입니다. 이런 엔진들과 연결하여 데이터베이스 프로그램을 작성할때 사용하는 인터페이스가 ODBC입니다.

데이터베이스 엔진을 만드는 회사에서는 윈도우용 데이터베이스 엔진 드라이버를 제공합니다. 이것을 ODBC드라이버라고 하는데 어플리케이션 프로그램에서는 이 ODBC드라이버에 연결하고 몇가지 명령을 드라이버에서 명령에 따라서 데이터베이스 파일들을 컨트롤 합니다.

ODBC를 모르시는 분들이 있을것 같아서 쉽게 예를 들어 개념을 설명하겠습니다.

자동차를 운전하고자 할때 운전면허증을 땁니다. 예를 들어 2종 운전 면허증을 교부받으면  어떤 승용차든 운전할수 있습니다. 그것이 기모사에서 나온 사피아든 현모사에서 나온 알랑트라든 대모사에서 나온 라간자든 간에 어떤 자동차든지 운전을 할수 있습니다. 사실 운전하는것은 어느차를 운전하든 방법은 같습니다. 다만 조작대의 위치나 레버 버튼등이 차따라 다릅니다. 운전면허를 교부받을때 “본운전 면허는 현모사에서 나온 알랑트라만 운전할수 있음” 이라고 적혀있지는 않습니다. 운전을 잘하고 못하고는 상관없이 비싼차는 그만큼 좋은것이고 싼차는 그만큼 운행하는데 불편을 격습니다. 다만 운전하는것은 똑같다는 의미입니다. ODBC도 이런개념에서 생각하시면 됩니다. 수억또는 수천만원이 가는 비싼 데이터베이스 엔진을 구입하든 또는 싼 데이터베이스 엔진을 구입하든 파일에 데이터를 저장하고 검색하고 수정하고 삭제하는 기능은 같습니다.

자동차를 움직일때 핸들과 기아만 작동하지만 이 작동에 의해서 자동차가 움직일때는 내부에 아주복잡한 기계들이 복잡하게 조직화 되어서 움직이듯이 데이터베이스 엔진도 내부적으로는 아주 복잡하게 움직이지만 외부에서 사용자가 이용하는 기능은 단순하게 데이터 입력,검색,수정,삭제 뿐이라는 것입니다. 그렇기 때문에 이 기능을 드라이버로 제공하고 어플리케이션 프로그래머는 이드라이버와 연결하여 입력,검색,수정,삭제의 간단한 명령을 드라이버에 제공하는 것입니다.

여기에서 생각하실 점은 바로 드라이버가 있어야만 그 데이터베이스 파일을 구동할수 있는것입니다.

ODBC는 단순하게 면허증에 불과합니다. 컴퓨터안에 오라클 ODBC드라이버가 없다면 오라클로 작성한 데이터베이스에는 접근할수 없는것입니다. 또한 MDB드라이버가 없으면 MDB(마이크로 소프트사에서 만든 데이터베이스 파일)를 ODBC로 접근할수 없는것입니다. 운전면허증만 있다고 자동차도 없는데 운전할수 없는것과 마찬가지입니다. 즉 ODBC자체로는 드라이버가 설치되지 않는 상태에서는 어떤 데이터베이스 엔진도 접근할수가 없습니다. 현재 자신의 컴퓨터에 어떤 데이터베이스 드라이버가 있는가를 확인할려고 한다면 제어판에서 ODBC항목을 클릭하시면 됩니다. 그림 1은 ODBC항목을 클릭했을때 출력되는 화면입니다.


       (그림 1)제어판에서 ODBC항목 선택시 출력화면


그림1에서는 Microsoft 사의 Access, DBase,Excel,FoxPro,Oracle73 SQL 서버등이 설정되어 있습니다.  그림1과 같이 설치된 컴퓨터에서는 Access데이터베이스 파일이나 또는 DBase,Excel,FoxPro,오라클등은 ODBC를 사용하여 데이터베이스에 접근을할수 있습니다. 그림1에 등록되어 있지 않는 데이터베이스는 ODBC로는 접근을 할수 없다는 의미입니다.

지금까지의 내용으로 본다면 결국 ODBC란 드라이버와 연결되는 명령통로라고 생각하시면 됩니다. ODBC를 배우시기 전에 자신의 컴퓨터에는 어떤 드라이버들이 있는지 확인을 해주시기 바랍니다.


ODBC 등록하기

오라클과 같은 ODBC는 파일을 등록할 필요가 없으나 일반적으로 많이 사용하는 Access의 파일 (*.mdb)는 컴퓨터에 등록되어 있어야만 합니다. 제어판에서 ODBC항목을 선택하면 그림 2와 같은 화면이 출력될것입니다.


       (그림 2)제어판에서 ODBC 항목을 클릭했을때 출력화면


그림 2는 Window NT에서 ODBC를 선택했을때의 화면입니다. 각컴퓨터 마다 그림 2와 같이 똑같은 형태로 출력되지 않습니다. 해당 데이터베이스 엔진이 설치된 유무에 따라서 다르게 나타납니다. 중요한것인 사용자 DSN에서 추가 항목이 있을것이고 이버튼을 클릭하여 원하는 데이터베이스 파일을 선택한다는 것입니다.

추가 버튼을 클릭하면 그림 3과 같은 화면이 나옵니다.



       (그림 3) 세 데이터 원본 작성

추가버튼을 클릭한다는 것은 ODBC에 등록할 새로운 데이터베이스 파일을 설정한다는 것입니다. 일반적으로 ODBC에 등록할 파일들이나 테이블들은 데이터베이스 엔진에서 제공하여 주는 프로그램으로 먼저 만들어 놓아야 합니다. 예를 들어서 Micorsoft Access Driver(*.mdb) 파일을 ODBC에 등록하고자 한다면 Access프로그램을 이용하여 등록할 파일을 만드셔야 합니다. Access프로그램을 사용하는 방법과 각 엔진에 의해서 파일을 만드는 방법은 본책에 내용과는 거리가 있기 때문에 기록하지 않습니다.

다시 말씀드린다면 ODBC는 데이터베이스관리 프로그램에서 제작한 파일을 연결하여 이파일에 데이터를 쓰고,읽고,수정하는 기능을 주로 합니다. 본장에서는 일반적으로 사용하는 Access 드라이버를 이용하여 ODBC를 등록하는 방법에 대해서 설명하겠습니다.  그림 3과 같은 하면이 나온상태에서 완료 버튼을 클릭하면 그림 4와 같은 화면이 나타납니다.

       (그림 4)Access를 이용하여 ODBC를 설정하는 화면


그림 4와 같은 화면이 나올때에 “선택” 이라는 버튼을 클릭하면 파일 대화상자가 출력됩니다. 이파일 대화상자에서 자신이 만든 mdb파일을 선택하면 됩니다. 이렇게 선택하면 “데이터베이스” 라는 란에 현재 설정된 파일의 전체 경로가 나타납니다. 그림 4에서 보면 “D:\public\winpro\namecard.mdb"를 설정한것이 보일것입니다. 즉 현재 ODBC에 D:\public\winpro\namecard.mdb가 설정되었다는 것입니다. 이렇게 파일을 선택하고 이파일의 어떤 파일인지 이름을 설정합니다. 이름을 설정하는 항목이 “데이터 원본 이름(N)" 입니다. 이곳에 ”namecard"라고 설정하였습니다. “namecard"라고 등록하면 ODBC를 이용할때 데이터베이스의 파일명인 “D:\public\winpro\namecard.mdb" 을 이용하지 않고 "namecard" 만이용합니다.

예를 들어

       LoadDatabase("namecard");

필드 명

데이터 형식

  내용

ID

일련 번호

데이터가 기록될때의 번호

name

문자열

이름 (색인됨)

tel

문자열

전화번호

address

문자열

주소

bigo

문자열

기타정보

같은 방식이라는 의미입니다.(MFC에서 실제 위와 같이 쓰지는 않습니다.) 즉 데이터베이스를 로드할경우 "namecard"라는 이름으로 로드하면 드라이버는 이데이터베이스 파일이 어느곳에 위치해 있다는 것을 알아내고 그파일을 핸들링하는 것입니다. 설명은 본파일에 대한 설명합니다. 본장에서 만든 namecard 는 간단하게 개인정보를 기억하는 파일입니다. 본파일의 Table1 대한 정보는 표1과 같습니다.

               (표1) namecard파일의 Table1 필드정보

MDB 에서는 한개의 파일에 여러개의 테이블을 넣을수 있습니다. namecard.mdb파일에 첫번째 테이블에 설정되어 있는 필드정보는 표1과 같다는 의미입니다.

Microsoft Access를 이용하여 namecard를 직접 제작하든지 또는 제공하는 CD-ROM에 있는 namecard.mdb를 특정 디렉토리에 복사하든지 하여 이것을 ODBC에 등록하여야 합니다. 또한 필드 정보는 표1과 같아야 합니다. 이렇게 하나를 만들어 놓아야 이것을 이용하여 예제 프로그램을 작성할수 있기 때문입니다.

그림 4와 같은 화면에서 “확인” 버튼을 클릭한면 ODBC에 namecard가 존재하게 됩니다.


ODBC 에제 MExOdbc 프로젝트 만들기

이제 namecard를 이용하는 예제를 작성해 보겠습니다. 프로젝트명을 MExOdbc로 선택하고 SDI형으로 선택합니다. AppWizard Step2에서 그림 5와 같은 화면이 나올때 Database view without file support를 선택합니다.

               (그림 5)AppWizard Step2


ODBC를 구종하는 방법에 대한 내용을 설정한것이 AppWizard의 Step2입니다. 이 스텝에서는 ODBC를 사용할것인가 아닌가를 물어봅니다. None를 선택하시면 ODBC를 이용하지 않는다는 의미입니다.(지금까지는 None를 사용하였습니다) Header files only는 헤더만 사용하겠다는 의미이며 Database view whithout file support는 데이터베이스를 이용하여 “파일 열기”나 “파일저장”같은 일반적인 파일 입출력은 사용하지 않겠다는 의미이며 Database view with file support는 “파일 입출력” 기능을 제공하겠다는 것입니다. 우리가 만드는 예제는 단순하게 데이터베이스만 이용하기 때문에 3번째 항목 Database view whthout file supprot항목을 선택한것입니다. 이렇게 선택하면 어떤 데이터베이스를 이용할것인가를 설정해 주어야 합니다. 그림 5에서 Data Source를 클릭하여 이용할 데이터베이스를 설정해 줍니다. Data Source버튼을 클릭하면 그림 6과 같은 화면이 출력됩니다.

             (그림 6) Database Otptions 화면

Datasource항목에서 ODBC를 선택하고 콤보박스를 클릭하면 전에 우리가 설정하였던 namecard 가 나타납니다. namecard라고 선택을 하면 본 프로젝트에서는 namecard라는 데이터베이스를 ODBC로 이용하겠다는 의미입니다. DAO를 선택하면 ODBC가 아닌 DAO를 이용하여 데이터베이스를 사용하겠다는 의미인데 DAO에 대해서는 뒤에 설명을 하겠습니다. Recordset type는 레코드에 데이터가 갱신 되었을때 바로 데이터베이스에 기록할것인가? 아니면 데이터베이스 이용을 끝낸다음 갱신할것인가를 설정해주는 것입니다. Snapshot란 현재 데이터베이스의 이용이 끝난다음 갱신된 내용을 저장하는 것이고 Dynaset는 갱신이 되는 순간 바로 수정을 하는 것입니다. 보통 ODBC는 Snapshot를 이용하며 DAO는 Dynaset를 이용합니다. 그이유는 ODBC는 한곳에서만 데이터베이스를 이용하는 것이 아니라 여러 곳에서 한개의데이터베이스를 읽고 수정하기 때문에 자신이 수정한 내용이 완벽할경우에만 기록을 해야지 데이터를 잘목기록해놓고 “아이고 이게 아닌가 부다!” 라고 해서 다시 수정을 할때 이순간 잘목기록해 놓은 내용을 다른 곳에서 검색을 하고 오류를 범할수 있기 때문입니다. 반면에 DAO는 개인 PC에서 단일적으로 사용하기 때문에 이런 오류가 없고 그렇기 때문에 바로바로 수정을 하는 방식을 택하게 됩니다.

위와 같이 한후에 OK버튼을 클릭하게 되면 그림 7과 같은 화면이 출력됩니다.


               (그림 7) Select Database Tables

그림 7의 의미는 현재 선택된 데이터베이스안에 있는 테이블중 특정 테이블을 선택하라는 의미입니다.

그림 7과같이 namecard에 선택되어 잇는 Tabel을 선택합니다. 이후에 Table2를 사용하는 방법은 뒤에 계속 설명을 하겠습니다.

위와 같이 한후 OK버튼을 클릭하고 Next키를 눌러서 Step6에 가면 그림 8과 같은 화면이 나타납니다.


       (그림 8) MFC AppWizard Step6

그림 8에서 보듯이 View은 Base class  가 CRecordView입니다. 지금까지 사용한 View는 FormView나 또는 CView또는 ScrollView이었으나 이번장에서는 새롭게 CRecordView를 보시게 될것입니다. 또한 CMExOdbSet라는 새로운 클래스가 하나 더 생성되었습니다. 계속 예제를 만들어 가면서 설명을 하겠지만 여기에서 ODBC를 이용할때 두개의 새로운 클래스가 도입된다는 것을 기억해 주시기 바랍니다. CMExObjecSet 는 CRecordset을 상속받은 클래스이며 View는 CRecordView를 상속받았다는 내용입니다.

Finish버튼을 클릭하면 프로젝트가 만들어 집니다. 이제 각 클래스에 대해서 조사를 해보겠습니다.


CRecordView

CMExOdbcView는 CRecordView를 상속받았습니다. 그렇다면 이제 우리는 CRecordView가 도대체 무엇인가 그정체를 알아야 합니다. 도움말에서 CRecordView를 찾아 보면 그림 9와 같은 계층도표가 나올것입니다.

               (그림 9)CRecordView의 계층구조

CRecordView는 CFormView를 상속받았습니다. 이렇게 상속받았다는것은 대화상자 자원을 이용한다는 것입니다. 대화상자 자원을 이용하는 것은 CFormView와 같은데 다음으로 궁금한것은 어떤 특징이 추가되었는가입니다. 이에 대한 답은 CRecordView를 상속받은 CMExOdbcView의 헤더에 나와있습니다. CMExOdbcView의 헤더인 MExOdbcView.h에 보시면 다음과 같은 내용이 있을것입니다.(직접 확인을 해보시기 바랍니다.)


       CMExOdbcSet* m_pSet;


CRecordView를 상속받은 CMExOdbcView에서는 CMExOdbcSet형인 m_pSet가 있습니다. 프로젝트를 만들때 우리는 CMExOdbcSet클래스는 CRecordset을 상속받은 클래스라는것을 어렴풋이 알고 넘어갔습니다.

중요한것은 CMExOdbcView에서는 CRecordset를 상속받은 CMExOdbcSet을 클래스 맴버 변수로 가지고 있다는 것입니다. 다음에 CRecordSet에 대한 기능을 자세히 서명하기로 하고 우선 CRecordView의 정체에 대해서 알려 드리겠습니다.

CRecordView는 FormView형태이면서 맴버 변수로 CRecordSet를 가지고 있는 View입니다. CRecordSet는 우리가 설정한 ODBC를 핸들링하는 클래스이며 이맴버를 가지고 있는 View는 ODBC에설정된 필드의 정보들을 화면에 출력할수 있겠금 CRecordSet를 컨트롤 하는 맴버 함수를 내장한 View라는 것입니다.

즉 다시 말하면 RecordView는 FormView처럼 대화상자자원을 가지고 있고 내부 맴버로 데이터베이스 컨트롤하는 클래스 CRecordSet를 맴버 변수로 가지고 있다는 것입니다. 그래서 이변수를 이용하여 데이터를 얻고 얻은 데이터를 화면에 보여주는 기능을 하는 것입니다.

namecard는 Table1 “이름”,“전화번호”,“주소”,“비고” 필드를 가지고 있는 데이터베이스 파일입니다. 프로그램을 작성한다면 “이름”,“전화번호”,“주소”,“비고” 가 출력되는 에디터박스가 있고 “다음”,“이전” 등의 버튼을 이동하면 해당 데이터가 바뀌게 하면 좋지 않겠습니까? 그리고 해당 데이터를 수정하고 “수정”버튼을 클릭하면 해당 데이터가 바뀌게 되면 더욱 좋고요. 즉 정보가 출력되게 하기 위해서 가장 손쉽게 하는 방법은 FormView를 이용하여 각 필드에 해당하는 에디터 박스를 넣어두고 또한 “수정” 이나 “삭제” 같은 버튼을 두어서 해당 데이터를 수정하면 더욱좋습니다. 버튼이나 에디터 박스등을 도시하는것은 FormView가 적당하고 여기에 현재 설정된 데이터베이스를 핸들링하기 위해서 맴버로 데이터베이스 핸들링 클래스 CRecordSet를 설정한것입니다.


CRecordset

CRecordset는 데이터베이스를 로드하고 로드된 데이터베이스의 레코드를 관리하는 클래스입니다. CRecordset를 상속받은 CMExOdbcSet클래스를 보면 CRecordset속성을 알수 있습니다. CMexOdbcSet의 헤더에 보면다음과 같은 맴버들이 있을것입니다.


// Field/Param Data

       //{{AFX_FIELD(CMExOdbcSet, CRecordset)

       long    m_ID;

       CString m_name;

       CString m_tel;

       CString m_address;

       CString m_bigo;

       //}}AFX_FIELD


프로젝트를 만들때 설정한 namecard 데이터베이스의 Table1에는 이름,전화번호,주소,비고 등이 있었습니다. 이 각 필드에 있는 정보들을 헤더에 설정한 각 변수에 로드하는 것입니다. 자원에 있는 항목들을 클래스이 맴버 변수로 연결시킬때 DoDataExchange를 사용하였습니다. CRecordset에서는 위에 설정한 m_name,m_tel 등등과 연결될 실제 데이터베이스 필드를 다음과 같은 함수를 사용하였습니다.


void CMExOdbcSet::DoFieldExchange(CFieldExchange* pFX)

{

       //{{AFX_FIELD_MAP(CMExOdbcSet)

       pFX->SetFieldType(CFieldExchange::outputColumn);

       RFX_Long(pFX, _T("[ID]"), m_ID);

       RFX_Text(pFX, _T("[name]"), m_name);

       RFX_Text(pFX, _T("[tel]"), m_tel);

       RFX_Text(pFX, _T("[address]"), m_address);

       RFX_Text(pFX, _T("[bigo]"), m_bigo);

       //}}AFX_FIELD_MAP

}


DoFieldExchange 함수는 필드와 맴버의 변수와 연결시키주는 기능을 합니다. RFX_Long는 필드의 long를 현재 맴버변수 m_ID와 연결을 하면 RFX_Text는 필드의 텍스트를 현재 설정되어있는 CSTring와 연결을 합니다. 대화상자와 연결하는 DoDataExchange와 비슷한 개념으로 보시면 될것입니다.


프로젝트를 만들때 우리는 namecard라는 데이터베이스를 선택하였고 이데이터베이스의 첫번째 테이블 "Table1"를 테이블로 선택하였습니다. AppWizard는 프로그래머가 편리하도록 소스를 대신 코딩하는 작업을 한다고 하였습니다. 그렇다면 namecard라고 설정한 데이터베이스 명과 "Table1"를 설정한 항목이 분명 어느곳에 기록되어 있다는 추측을 할수 있습니다. 이추측과 걸맞게 바로 CMExOdbcSet의 소스에 다음과 같은 내용이 있을것입니다.


CString CMExOdbcSet::GetDefaultConnect()

{

       return _T("ODBC;DSN=namecard");

}


CString CMExOdbcSet::GetDefaultSQL()

{

       return _T("[Table1]");

}


GetDefaultConnect함수는 현재 설정되어 있는 데이터베이스명을 리턴하며 GetDefaultSQL은 현재 설정되어 있는 테이블을 리턴합니다.(Access에서만 그렇다는 이야기 입니다.) 이두함수에 데이터베이스명과 테이블 명만 바꾸면 다른 데이터베이스와 다른 테이블을 로드할수 있는것입니다.

현재는 ODBC의 namecard와 Table1으로 설정되어 있습니다. 한가지 더있다면 Snapshot, Dynaset 은 어느곳에 설정되어 있는가입니다. CMExOdbcSet의 생성자를 보시면 다음과 같은 코드가 있을것입니다.


        m_nDefaultType = snapshot;


m_nDefaultType는 CRecordSet의 맴버 함수로써 현재 레코드 컨트롤 클래스가 어떤 형태인가 스타일을 설정해주는 플래그입니다. 이플래그에 snapshor값을 기록하여 snapshot로 설정한것입니다.

여기까지 보면 AppWizard에서 잘못설정했다 하더라도 우리가 수정을 할수 있다는 것을 느끼실것입니다. “아차 namecard가 아니라 name라는 데이터베이스인데..”

“아차 Table1이 아니라 Tabel2인데 ” 라고 생각하셨을때 바로 CRecordset를 상속받은 CMExOdbcSet를 수정하시면 됩니다.

CRecordset에는 표2와 같은 함수들이 있습니다.

              (표2) CRecordset의 함수리스트

함수명

  내용

MoveFirst

제일 처음 레코드로 이동한다.

MoveLast

마지막 레코드로 이동한다

MoveNext

다음 레코드로 이동한다

MovePrev

이전 레코드로 이동한다.

SetBookmark

현제 레코드를 북마크한다.

AddNew

세로운 필드를 만든다

Delete

핸재 레코드를 지운다

Edit

데이터베이스를 수정가능한 상태로 한다.

Update

CRecordset 에 있는 데이터 맴버들의 내용을

데이터베이스에 업데이트한다.

Requery

SQL언어에 의해서 또는 함수에 의해서 설정된것을 새롭게 query 한다.

IsEOF

현재가 파일 마지막이면 1를 리턴한다.

IsBOF

현재가 파일처음이면 1를 리턴한다.

GetRecordCount

현재 테이블의 레코드 수를 얻는다.

m_pDatabase

CDatabase 클래스형의 맴버로 실제 데이터베이스를 컨트롤 하는 클래스

m_strFilter

SQL언어를 가동시키기 위한 문자열을 저장하는 변수 이곳에 문자열을 저장하고 Requery를 실행하면 SQL언어를 실행할수 있다.

표2의 함수를 보면 데이터베이스를 입력하고 수정하고 검색하는 모든 기능은 CRecosrset에 있다는 것을 느낄수 있을겁니다.

여기에서 다시 정리한다면 CRecordView(MExOdbc에서는 CMExOdbcView)에서는 단순하게 CRecordset(MExOdbc에서는 CMExOdbcSet)를 맴버 변수로 가지고 있고 실제 레코드를 핸들링하는 것은 CRecordset라는 것을 알수 있습니다. CRecordView는 CRecordset에서 설정된 레코드 데이터를 FormView형태로 화면에 출력하는 것이 전부입니다.


CDatabase

초보적인 ODBC를 이용한다고 하였을경우 CRecordset의 기능만 이해해도 가능합니다. 그러나 CRecordset까지 조사해본 이마당에 또하나를 더알아보겠습니다. CRecordset에는 m_pDatabase라는 CDatabase라는 클래스의 맴버 형이 있습니다. 이클래스는 우리가 지금까지 보지못한 새로운 클래스입니다. 이클래스의 기능이 무엇인가를 알기위해서 도움말에서 CDatabase를 찾아보시기 바랍니다. calss member 를 선택하면 그림 10과 같은 화면이 나오게 될것입니다.



       (그림 10)도움말에서 찾아본 CDatabase Class Members

본장이 본책의 마지막 부분이고 본책을 끝난후에는 여러분들이 프로그램을 할때 최우선적으로 참고서를 삼아야 할것이 도움말이 될것입니다. 따라서 이제는 도움말을 보고 어떤 클래스인가를 확인할수 있어야 합니다. CDatabase는 실제로 데이터베이스를 컨트롤하는 클래스입니다. 우리가 선택한 데이터베이스 즉 namecard를 직접 컨트롤하는것은 CRecordView도 아니고 CRecordset도 아닌 CDatabase입니다. 그러나 이 CDatabase는  내부에 가려져 있기 때문에 우리가 알수가 없었던것입니다. 프로그래밍을 할때 CDatabase만가지고도 ODBC를 핸들링할수 있습니다. MFC에서는 CDatabase가지고 핸들링할때 다소 복잡한것을 막기 위하여 데이터베이스 프로그래밍을 그립 11과 같은 구조로 설정하도록 권고를 한것입니다.

       (그림11) 데이터베이스 핸들링 클래스 형태


그림 11에서 보는것처럼 원하는 데이터베이스를 오픈하는 실제 클래스는 CDatabase이며 이클래스를 이용하여 데이터베이스를 오픈하고 해당 레코드 정보를

로드하는 클래스가 CRecordset이며 로드된 레코드들을 화면에 출력하는 것이 CReocrdView입니다. 이런 개념을 이해하고 예제를 작성해 나가면 데이터베이스의 개념을 이해할수 있을것입니다.


CRecordView의 자원과 CRecordSet 연결

이제 프로젝트 MExOdbc에 새로운 데이터베이스를 핸들링하는 예제를 만들어 보겠습니다.

MExOdbc에서 CMExOdbcView와 연결된 대화상자 자원 ID는 IDD_MEXODBC_FORM 입니다. 그림 12은 IDD_MEXODBC_FOMR을 수정한 화면입니다.


       (그림 12)IDD_MEXODBC_FORM

IDD_MEXODBC_FORM 에는 이름,전화번호,주소,비고등을 기입할수 있고 또는 출력할수 있는 에디터 박스를 설정하였습니다. 비고는 여러 줄을 입력할수 있도록 multiline형태로 설정하였습니다. 위와 같이 설정된 각 레코드를 CRecordset형인 CMExOdbcSet의 변수로 연결시켜 보겠습니다.

그림 13은 이름을 CMExOdbcSet의 m_name로 연결하는 형태입니다.

       (그림 13) 이름의 자원을 m_pSet->m_name로 연결시킨형태

이름을 입력하는 에디터박스에 마우스를 위치하고Ctrl 키를 누른상태에서 좌측 마우스 버튼을 클릭하게 되면 그림 13과 같은 화면이 나타납니다. CRecoreView로 설정하면 Member variable name에 콤보박스형태로 나타나게 되고 m_pSet(CMExOdbcSet의 형)의 필드 데이터를 저장하는 변수들의 리스트가 보이게 됩니다. 이때 이름의 필드와 연결하기 위해서 m_pSet->m_name를 선택한것입니다. 이와 같은 방법으로 전화번호,주소 비고등을 연결합니다. 각 필드와 연결되는 m_pSet의 맴버의 리스트는 표3과 같습니다.

  ID

  내용

m_pSet과 연결

IDC_NAME

이름 필드

m_name

IDC_TEL

전화번호

m_tel

IDC_ADDRESS

주소

m_address

IDC_BIGO

비고

m_bigo

               (표3) m_pSet과 연결되는 대화상자 ID


위와 같이 하면 DoDataExchange 함수는 다음과 같이 설정됩니다.


void CMExOdbcView::DoDataExchange(CDataExchange* pDX)

{

       CRecordView::DoDataExchange(pDX);

       //{{AFX_DATA_MAP(CMExOdbcView)

       DDX_FieldText(pDX, IDC_NAME, m_pSet->m_name, m_pSet);

       DDX_FieldText(pDX, IDC_TEL, m_pSet->m_tel, m_pSet);

       DDX_FieldText(pDX, IDC_ADDRESS, m_pSet->m_address, m_pSet);

       DDX_FieldText(pDX, IDC_BIGO, m_pSet->m_bigo, m_pSet);

       //}}AFX_DATA_MAP

}


DDX_Text가 아닌 DDX_FieldText형태로 두개의 데이터가 서로 연결될것입니다. DDX_FieldText 함수는 한개의 인자를 더필요합니다. 이인자는 CRecordset형의 현재 RecordView에 설정된 변수입니다.

이와 같이 한후 컴파일하고 실행하면 그림 14와 같은 화면이 출력될것입니다.


       (그림 14) MExOdbc 실행결과


데이터 입력


현재까지는 namecard에 설정되어 있는 데이터를 화면에 출력하고 도구바에 있는 이동버튼을 이용하여 레코드를 이용할뿐입니다. 이제 이 프로그램에서 입력을 설정해 보겠습니다. 메뉴의 레코드항목에 “입력” 이라는 항목을 추가합니다. 그림 15은 “입력”이라는 항목이 추가된 메뉴자원의 형태입니다.


       (그림 15) 메뉴에 입력항목을 추가한 형태

그림 15처럼 ID가 IDD_ADDDATA인 입력이라는 항목을 추가하고 이항목을 선택하였을때 실행되는 함수를 CMExOdbcView에 설정합니다. 설정된 함수는 다음과 같습니다.


void CMExOdbcView::OnAdddata()

{

       // TODO: Add your command handler code here

       

}

이제 이함수에서 기록된 데이터를 새로 이력하도록 합니다. 새로 입력할때는 데이터베이스에 빈 레코드를 하나 만들어야 합니다. 빈레코드를 만들어야 이레코드에 데이터를 입력할수 있는것입니다. 그럼 빈레코드를 어느곳에 만들어야 할까요? 당연히 레코드 맨마지막으로 이동해서 만들어야 합니다. 이렇게 빈 레코드를 만들고난후 에디터에 입력한 값을 이 빈레코드에 넣으면 됩니다. 이렇게 한후 Requery를 실행시킵니다. 이것을 실행하지 않으면 데이터베이스가 갱신되지 않습니다. 이렇게 하여 데이터베이스를 갱신한후 데이터베이스 특정 레코드에 위치를 해야 합니다. Requery를 하게 되면 보통 맨처음 레코드로 이동하게됩니다. 이렇게 되면 현래 레코드 데이터를 그대로 화면에 보여주게 해야 합니다. 이런 루틴으로 OnAdddata함수를 수정한 것이 다음과 같습니다.


void CMExOdbcView::OnAdddata()

{

       // TODO: Add your command handler code here

       m_pSet->MoveLast();//마지막 레코드로 이동한후

       m_pSet->AddNew();//빈 레코드를 하나 만든다

       UpdateData(TRUE);//자원에 있는 데이터를 m_pSet으로 전송

       m_pSet->Update();//빈레코드에 데이터를 넣는다.

       m_pSet->Requery();//데이터베이스를 갱신한다.

       m_pSet->MoveFirst();//처음으로 이동한다.

       UpdateData(FALSE);//m_pSet에 있는 데이터를 자원으로 전송

}


CRecordset의 MoveLast함수는 마지막 레코드로 이동하는 함수입니다. 이렇게 마지막으로 이동한후 AddNew()함수를 이용하여 빈레코드를 만듭니다. 이렇게 빈레코드를 만들면 m_pSet의 m_name,m_tel등의 맴버들은 모두 아무데이터도 들어있지 않습니다. 이멤버들에 현재 화면에서 입력한 데이터를 넣어야 합니다. 이럴때 사용하는 게 UpdateData(TRUE)입니다. 이렇게 하면 m_pSet의 m_name,m_tel,m_address,m_bigo 등에 입력한 데이터가 저장되게 됩니다. m_pSet의 맴버 변수의 데이터를 DB에 저장하고자 할때 사용하는 함수가 Update입니다. Update를 하면 m_name등에 저장된 데이터가 바로 데이터베이스로 전송되는 것입니다. RecordSet이 snapshot 형태이므로 데이터베이스를 갱신해야 설정된 데이터가 바로 파일에 기록됩니다. 이렇게 갱신할때 사용하는 함수가 Requery()입니다.

Requery함수에 의해서 데이터베이스가 갱신되고 MoveFirst로 이동하게 되면 m_pSet의 맴버 변수들에는 첫번째 데이터가 저장될것입니다. 이 데이터를 화면에 출력하기위해서 UpdateData(FALSE);함수를 호출한것입니다.

이와같이 하고 컴파일한후 새로운 데이터를 입력하고 메뉴에서 “입력”항목을 클릭하여 보세요. 그림16는 입력루틴을 설정한 MExOdbc의 출력결과입니다.

(그림 16)MExOdbc에 입력루틴 추가한 모습


데이터 수정

수정하기는 입력하기보다 매우 간단합니다. 메뉴판에 입력하기와 비슷하게 수정하기항목을 IDD_MODEDATA라는 아이디로 만든다음 이항목을 선택하면 실행하는 함수를 만들고 이함수를 다음과 같이 수정합니다.


void CMExOdbcView::OnModedata()

{

       // TODO: Add your command handler code here

       m_pSet->Edit();

       UpdateData(TRUE);

       m_pSet->Update();

       m_pSet->Requery();

       m_pSet->MoveFirst();

       UpdateData(FALSE);

}


수정을 하고자 할경우에는 CRecordSet의 Edit()함수를 호출하면 됩니다. 이함수를 호출하면 현재 레코드를 수정가능한 상태로 만듭니다. 이렇게 한후 화면에서 입력한 내용을 받고 Update 함수를 이용하여 현재 레코드에 업데이트합니다. 이렇게 한후 Requery를 이용하여 데이터베이스를 갱신하고 MoveFirst를 이용하여 데이터베이스 첫번째 레코드로 간다음 첫번째 레코드의 데이터를 화면에 보여주는 것입니다.

컴파일하고 실행한후 현재 입력된 데이터를 수정해 보세요. 수정되는 것을 보실수 있을것입니다.


데이터 검색

데이터 검색방법은 CRecordset의 m_strFilter를 이용합니다. 예를 들어 name가 “이상엽” 인데이터를 찾아라라고 할경우 m_strFilter에 다음과 같이 설정한후 Requery함수를 호출합니다.


m_pSet->m_strFilter="name= '이상엽' ";

m_pSet->Requery();


m_strFilter에 설정하는 내용은 SQL문입니다. SQL문이란 데이터베이스를 컨트롤하는 데이터베이스 언어라고 생각하시면 됩니다. SQL문에 대해서는 본책에서 언급하지 않습니다. 데이터베이스에 관련된 책자에는 SQL문에 대해서 자세히 나와있을것입니다. 실제로 ODBC는 SQL문을 드라이버에 전송하고 이 SQL문에 의해서 구동하는것입니다. 지금까지 사용한 MoveFirst나 MoveLast등 모두 내부적으로 보면 SQL문을 호출하고 있습니다. CDatabase를 직접 핸들링하여 데이터베이스를 구동한다고 할경우에는 CDatabase의 맴버함수인 ExcuteSql이라는 함수에 SQL문을 넣어서 수행하면 데이터베이스를 직접 핸들링할수 있습니다.

이제 MExOdbc에 검색항목을 설정하겠습니다. 메뉴에 “검색” 이라는 항목을 설정하고 ID를 IDD_SEEKNAME로 설정합니다. 이렇게 설정하고 본 메뉴항목을 선택하였을때 실행하는 함수를 CMExOdbcView에 만듭니다.

다음은 CMExOdbcView에 설정한 함수입니다.


void CMExOdbcView::OnSeekname()

{

       // TODO: Add your command handler code here

       UpdateData(TRUE);

       m_pSet->m_strFilter.Format("name= '%s' ",m_pSet->m_name);

       m_pSet->Requery();

       UpdateData(FALSE);   

}


먼저 화면에 입력한 데이터를 받아두고 m_strFilter에 현재 이름란에 입력한 데이터를 받아서 검색하게 합니다. 예를 들어 이름란에 “홍길동” 이라고 입력하였다면 m_strFilter에는 다음과 같은 내용이 설정될것입니다.

"name= '홍길동' “

이와 같이한후 Requery함수를 호출하면 “홍길동” 이란 이름이 있는 레코드를 모두 로드합니다.

여기에서 주의할점은 “홍길동”이라는 이름이 있는 레코드만 유효화고 나머지 레코드는 무시한다는 것입니다. “홍길동” 이라는 레코드가 단순하게 한개일경우에는 현재 설정된 레코드는 단 한개입니다. 프로그램을 컴파일 하고 실행시켜서 “홍길동”이라는 레코드를 설정하고 찾아보시면 “홍길동”은 찾게 될것입니다. 이렇게 찾게된후 도구바에서 레코드 이동 도구바를 클릭하면 이동이 되지 않습니다. 왜냐하면 현재 로드한 레코드는 오로지 “홍길동” 이라는 이름이 있는 레코드이기 때문입니다.

이부분에 대해서 웹서버에 질문이 많이 들어옵니다. “레코드를 찾으면 레코드는 찾는데 다른데이터가 사라집니다!” 라는 질문입니다.

m_strFilter에 검색 SQL 문을 사용하게 되면 검색된 데이터만 유효하게 로드되기 때문에 전체 데이터를 로드하는 것이 아닙니다. 비로 데이터베이스 “홍길동,이상엽,이상돌,만득이” 라는 데이터가 있다 하더라도 “홍길동” 에 해당하는 데이터는 1개이기 때문에 한개만 로드하는 것입니다.

검색이 되었다면 검색을 해제 하여야 합니다. 검색을 해제 하는것은 매우 간단합니다. m_strFilter에 ""값을 입력한후 Requery함수를 실행시키면 됩니다.

메뉴에 “검색해제” 라는 항목을 두고 이항목의 ID를 IDD_FREESEEK라고 설정한후 이항목을 선택하였을때의 함수를 CMExOdbcView에 설정합니다. 이렇게 만든 함수는 다음과 같습니다.


void CMExOdbcView::OnFreeseek()

{

       m_pSet->m_strFilter="";

       m_pSet->Requery();

       UpdateData(FALSE);           

}


OnFreeseek()함수는 먼저 m_strFilter에 ""값을 설정하고 Requery함수를 실행 한것입니다. 이렇게 검색해제를 하면 모든 데이터가 다 로드됩니다.

위와 같이 한후 컴파일하고 실행하면 그림17과 같은 화면이 나타납니다. 직접 지금까지의 내용을 프로그래밍하시기 바랍니다.


레코드 삭제

레코드 삭제는 단한개의 함수로 이루어집니다. CRecordset의 Delete라는 함수를 호출하면 됩니다. 메뉴항목에 “삭제”라는 항목을 만들고 ID를 IDD_DELDATA 로 설정한다음 이항목을 선택하엿을때의 함수를 CMExOdbcView에 만듭니다.

이함수는 다음과 같습니다.




       (그림 17) MExOdbc실행결과


void CMExOdbcView::OnDeldata()

{

       // TODO: Add your command handler code here

       m_pSet->Delete();

       m_pSet->Requery();

       UpdateData(FALSE);

}

"삭제“ 항목을 클릭하면 m_pSet의 Delete함수가 호출되고 현재 데이터가 삭제됩니다. 그리고 난후에 Requery함수를 이용하여 데이터베이스를 갱신한다음 현재 선택된 데이터를 화면에 출력하기 위해서 UpdateData함수를 호출한것입니다.

OnDeldata함수를 만들고 난후에 컴파일하고 실행을 해보시기 바랍니다.


레코드 이동

지금까지 데이터를 수정,검색,삭제,입력 등의 중요 4가지 항목을 설명하였습니다. 이제 남은 내용이 레코드이동입니다. 레코드이동에서 처음으로 이동하는 함수 MoveFirst와 마지막으로 이동하는 함수 MoveLast는 이미 설명하였습니다.

현재 레코드에서 다음 레코드로 이동하는 함수와 현재 레코드에서 바로 이전으로 이동하는 함수는 다음과 같습니다.

m_pSet->MoveNext(); //다음으로 이동

m_pSet->MovePrev(); //이전으로 이동

MExOdbc에서는 도구바의 레코드 이동 버튼을 이용하여 이동하기 때문에 위의 두함수를 사용하지 않았습니다. 그러나 실제로 이함수들을 기억해야할때가 있을겁니다. 도구바에서 버튼을 클릭하면 제일먼저 실행되는 함수가 OnMove함수입니다. 이함수는 CRecordView에 내장되어 있는 함수입니다. 이함수의 인자로 nIDMoveCommand 가 들어오는데 처음 버튼을 클릭하면 ID_RECORD_FIRST가 마지막 버튼을 클릭하면 ID_RECORD_LAST가 다음 버튼을 클릭하면 ID_RECORD_NEXT가 이전 버튼을 클릭하면 ID_RECORD_PREV가 전해집니다. 이렇게 전해진 값에 의해서 MoveFirst,MoveLast,MoveNext,MovePrev가 실행되는 것입니다.

클래스 위저드를 실행시키면 CMExOdbcView에서 OnMove 가 보일것입니다.

버튼의 동작에 따라 실행을 바꾸고자 한다면 이함수를 만들고 수정을 하면 될것입니다.


CRecordView와 도큐먼트와의 관계


지금까지 제작한 프로그램은 모두 CRecordView를 상속받은 CMExOdbcView에서만 하였습니다. 원래규칙대로라면 데이터베이스자료는 CDocument 를 상속받은 CMExOdbcDoc에 설정되어 있어야 할것인데 왜 View에 설정되어 있을까 하는 의문을 갖지는 않으셨는지요? 그런 의문을 가졌다면 여기에서 해답이 나옵니다. 실제 데이터베이스 레코드를 핸들링하는 CMExOdbcSet은 도큐먼트에 설정되어 있습니다. CMExOdbcDoc헤더에 보시면 다음과 같은 변수가 있을것입니다.


        CMExOdbcSet m_mExOdbcSet;


그렇다면 CMExOdbcView에서 사용하였던 m_pSet은 무엇일까요? 이것은 View의

OnInitialUpdate() 함수를 보시면 답이 나옵니다.


void CMExOdbcView::OnInitialUpdate()

{

       m_pSet = &GetDocument()->m_mExOdbcSet;

       CRecordView::OnInitialUpdate();

}


View에 있는 m_pSet은 도큐먼트에 있는 m_mExOdbcSet의 포인터일뿐입니다.

즉 실제 레코드컨트롤 클래스는 도큐먼트에 있다는 의미입니다.



CMExOdbcView 프로그램 소스

CMExOdbcView의 프로그램 소스는 다음과 같습니다.

(프로그램 소스)

// MExOdbcView.h : interface of the CMExOdbcView class

//

/////////////////////////////////////////////////////////////////////////////


#if !defined(AFX_MEXODBCVIEW_H__C19DF77F_CEFA_11D1_AD20_006097AD9DDC__INCLUDED_)

#define AFX_MEXODBCVIEW_H__C19DF77F_CEFA_11D1_AD20_006097AD9DDC__INCLUDED_


#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000


class CMExOdbcSet;


class CMExOdbcView : public CRecordView

{

protected: // create from serialization only

        CMExOdbcView();

        DECLARE_DYNCREATE(CMExOdbcView)


public:

        //{{AFX_DATA(CMExOdbcView)

        enum { IDD = IDD_MEXODBC_FORM };

        CMExOdbcSet* m_pSet;

        //}}AFX_DATA


// Attributes

public:

        CMExOdbcDoc* GetDocument();


// Operations

public:


// Overrides

        // ClassWizard generated virtual function overrides

        //{{AFX_VIRTUAL(CMExOdbcView)

        public:

        virtual CRecordset* OnGetRecordset();

        virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

        protected:

        virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

        virtual void OnInitialUpdate(); // called first time after construct

        virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

        virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

        virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

        //}}AFX_VIRTUAL


// Implementation

public:

        virtual ~CMExOdbcView();

#ifdef _DEBUG

        virtual void AssertValid() const;

        virtual void Dump(CDumpContext& dc) const;

#endif


protected:


// Generated message map functions

protected:

        //{{AFX_MSG(CMExOdbcView)

        afx_msg void OnAdddata();

        afx_msg void OnModedata();

        afx_msg void OnSeekname();

        afx_msg void OnFreeseek();

        afx_msg void OnDeldata();

        //}}AFX_MSG

        DECLARE_MESSAGE_MAP()

};


#ifndef _DEBUG  // debug version in MExOdbcView.cpp

inline CMExOdbcDoc* CMExOdbcView::GetDocument()

   { return (CMExOdbcDoc*)m_pDocument; }

#endif


/////////////////////////////////////////////////////////////////////////////


//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.


#endif // !defined(AFX_MEXODBCVIEW_H__C19DF77F_CEFA_11D1_AD20_006097AD9DDC__INCLUDED_)


// MExOdbcView.cpp : implementation of the CMExOdbcView class

//


#include "stdafx.h"

#include "MExOdbc.h"


#include "MExOdbcSet.h"

#include "MExOdbcDoc.h"

#include "MExOdbcView.h"


#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif


/////////////////////////////////////////////////////////////////////////////

// CMExOdbcView


IMPLEMENT_DYNCREATE(CMExOdbcView, CRecordView)


BEGIN_MESSAGE_MAP(CMExOdbcView, CRecordView)

        //{{AFX_MSG_MAP(CMExOdbcView)

        ON_COMMAND(IDD_ADDDATA, OnAdddata)

        ON_COMMAND(IDD_MODEDATA, OnModedata)

        ON_COMMAND(IDD_SEEKNAME, OnSeekname)

        ON_COMMAND(IDD_FREESEEK, OnFreeseek)

        ON_COMMAND(IDD_DELDATA, OnDeldata)

        //}}AFX_MSG_MAP

        // Standard printing commands

        ON_COMMAND(ID_FILE_PRINT, CRecordView::OnFilePrint)

        ON_COMMAND(ID_FILE_PRINT_DIRECT, CRecordView::OnFilePrint)

        ON_COMMAND(ID_FILE_PRINT_PREVIEW, CRecordView::OnFilePrintPreview)

END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////

// CMExOdbcView construction/destruction


CMExOdbcView::CMExOdbcView()

        : CRecordView(CMExOdbcView::IDD)

{

        //{{AFX_DATA_INIT(CMExOdbcView)

        m_pSet = NULL;

        //}}AFX_DATA_INIT

        // TODO: add construction code here


}


CMExOdbcView::~CMExOdbcView()

{

}


void CMExOdbcView::DoDataExchange(CDataExchange* pDX)

{

        CRecordView::DoDataExchange(pDX);

        //{{AFX_DATA_MAP(CMExOdbcView)

        DDX_FieldText(pDX, IDC_NAME, m_pSet->m_name, m_pSet);

        DDX_FieldText(pDX, IDC_TEL, m_pSet->m_tel, m_pSet);

        DDX_FieldText(pDX, IDC_ADDRESS, m_pSet->m_address, m_pSet);

        DDX_FieldText(pDX, IDC_BIGO, m_pSet->m_bigo, m_pSet);

        //}}AFX_DATA_MAP

}


BOOL CMExOdbcView::PreCreateWindow(CREATESTRUCT& cs)

{

        // TODO: Modify the Window class or styles here by modifying

        //  the CREATESTRUCT cs


        return CRecordView::PreCreateWindow(cs);

}


void CMExOdbcView::OnInitialUpdate()

{

        m_pSet = &GetDocument()->m_mExOdbcSet;

        CRecordView::OnInitialUpdate();

}


/////////////////////////////////////////////////////////////////////////////

// CMExOdbcView printing


BOOL CMExOdbcView::OnPreparePrinting(CPrintInfo* pInfo)

{

        // default preparation

        return DoPreparePrinting(pInfo);

}


void CMExOdbcView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

        // TODO: add extra initialization before printing

}


void CMExOdbcView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

        // TODO: add cleanup after printing

}


/////////////////////////////////////////////////////////////////////////////

// CMExOdbcView diagnostics


#ifdef _DEBUG

void CMExOdbcView::AssertValid() const

{

        CRecordView::AssertValid();

}


void CMExOdbcView::Dump(CDumpContext& dc) const

{

        CRecordView::Dump(dc);

}


CMExOdbcDoc* CMExOdbcView::GetDocument() // non-debug version is inline

{

        ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMExOdbcDoc)));

        return (CMExOdbcDoc*)m_pDocument;

}

#endif //_DEBUG


/////////////////////////////////////////////////////////////////////////////

// CMExOdbcView database support

CRecordset* CMExOdbcView::OnGetRecordset()

{

        return m_pSet;

}



/////////////////////////////////////////////////////////////////////////////

// CMExOdbcView message handlers

//입력시

void CMExOdbcView::OnAdddata()

{

        // TODO: Add your command handler code here

        m_pSet->MoveLast();

        m_pSet->AddNew();

        UpdateData(TRUE);

        m_pSet->Update();

        m_pSet->Requery();

        m_pSet->MoveFirst();

        UpdateData(FALSE);

}

//수정시

void CMExOdbcView::OnModedata()

{

        // TODO: Add your command handler code here

        m_pSet->Edit();

        UpdateData(TRUE);

        m_pSet->Update();

        m_pSet->Requery();

        m_pSet->MoveFirst();

        UpdateData(FALSE);

}

//이름찾기

void CMExOdbcView::OnSeekname()

{

        // TODO: Add your command handler code here

        UpdateData(TRUE);

        m_pSet->m_strFilter.Format("name= '%s' ",m_pSet->m_name);

        m_pSet->Requery();

        UpdateData(FALSE);   

}

//착기 해제

void CMExOdbcView::OnFreeseek()

{

        m_pSet->m_strFilter="";

        m_pSet->Requery();

        UpdateData(FALSE);           

}

//데이터 삭제

void CMExOdbcView::OnDeldata()

{

        // TODO: Add your command handler code here

        m_pSet->Delete();

        m_pSet->Requery();

        UpdateData(FALSE);

}


(프로그램 소스끝)


2.DAO

DAO란 Microsoft 사에서 제공하는 단일컴퓨터용 데이터베이스 라이브러리 입니다. DAO클래스는 기본적으로 MDB파일을 구동합니다. 단일 컴퓨터용으로 제작되어 있기 때문에 속도 또한 ODBC보다 빠릅니다. ODBC는 ODBC 디바이스 드라이버를 이용하는 반면에 DAO는 자체 라이브러리를 이용하기 때문입니다. 개인용 컴퓨터에서는 DAO를 많이 사용합니다. 또한 파일을 만들수가 있으며 파일에 새로운 테이블및 필드들을 기록할수도 있습니다.

DAO는 ODBC와 방법이 같습니다. 다만 사용되는 클래스들이 다릅니다. ODBC의 CRecordset클래스는 CDaoRecordset와 대응되고 CRecordView는 CDaoRecordView로 대응됩니다. 실제 프로그래밍을 작성해 가면서 DAO의 기능을 알아보겟습니다.



프로젝트 MExDao만들기

프로젝트명을 MExDao로 선택합니다. 이렇게 설정한후 SDI형태로 설정합니다. ODBC 프로젝트를 만들때와 같은 방법으로 AppWizard의 Step2에서 Database view without file support를 선택합니다. 이와 같이 선택한후에 Data Source를 클릭합니다. 그림 18은 Data Source를 선택하였을 때의 화면입니다.



(그림 18)Database Options 설정화면


그림 18과 같이 Database Options화면이 출력되면 DAO를 클릭합니다. 이때 DAO항목의 에디터박스가 활성화 되는데 옆의 버튼을 클릭하면 파일 대화상자가 출력되고 이곳에서 mdb파일을 설정할수 있습니다. 또한 DAO입력란에 전체 경로명을 설정하여 주셔도 됩니다. Record type는 Dynaset로 합니다. 이와 같이 하고 OK버튼을 클릭합니다. OK버튼을 클릭하면 그림 19와 같이 Table를 선택하는 화면이 나타납니다.



       (그림 19)Select Database Tables 선택화면

이화면에서 Table1을 선택합니다. 이와 같이 선택한후 Next버튼을 클릭하여 Step6까지 가면 그림 20과 같은 화면이 출력됩니다.

                       (그림 20)AppWizard Step6 화면


그림 20에서 보듯이 CMExDaoView는 CDaoRecordView로 설정되어 있으며 CMExDaoSet는 CDaoRecordset로 설정되어 있습니다. 즉 DAO를 이용하여 데이터베이스를 구동할경우에는 CDaoRecordView와 CDaoRecordset을 이용한다는 것입니다. 이와 같이하고 Finish를 클릭하여 프로젝트를 만듭니다.


CRecordView

CMExDaoView는 CDaoRecordView를 상속받았습니다. 이것은 CFormView를 상속받았으며 DAO데이터베이스를 핸들링하도록 설정된 것입니다. 그림 21은 CDaoRecordView의 계층도입니다.

               (그림 21)CDaoRecordView의 계층구조


CDaoRecordView는 전항목에 설명한 CRecordView와 기능이 거히 같습니다. 다만 차이는 CDaoRecordView는 DAO를 핸들링하는 CDaoRecordset를 m_pSet으로 맴버를 가지고 있다는 것입니다.


MExOdbc와 같은 방법으로 프로그래밍 하기

이데 MExOdbc와 같은 방법으로 CMExDaoView와 연결된 자원  ID IDD_MEXDAO_FORM 에 그림 22와 같이 설정합니다.

       (그림 22)IDD_MEXDAO_FORM 수정 화면

위와 같이 설정한후 각 에디터와 m_pSet 의 맴버를 연결시킵니다. 즉 DoDataExchange에 DDX_FieldText 로 연결시키라는 의미입니다. 연결방법은 MExOdbc를 만들때의 방법과 같습니다.

이렇게 연결된 CMExDaoView 의 DoDataExchange 다음과 같습니다.


void CMExDaoView::DoDataExchange(CDataExchange* pDX)

{

       CDaoRecordView::DoDataExchange(pDX);

       //{{AFX_DATA_MAP(CMExDaoView)

       DDX_FieldText(pDX, IDC_NAME, m_pSet->m_name, m_pSet);

       DDX_FieldText(pDX, IDC_TEL, m_pSet->m_tel, m_pSet);

       DDX_FieldText(pDX, IDC_ADDRESS, m_pSet->m_address, m_pSet);

       DDX_FieldText(pDX, IDC_BIGO, m_pSet->m_bigo, m_pSet);

       //}}AFX_DATA_MAP

}


위와같이 한후에 메뉴를 수정합니다. 메뉴를 수정한 화면은 그림 23과 같습니다.

       (그림 23) 수정된 메뉴 형태

즉 MExOdbc 와 같은 기능을 그대로 DAO 로 만들어 보는것입니다.

이와 같이 메뉴를 만든다음 새로 설정한 “입력”,“수정”,“검색”,“검색해제”,“삭제”의 항목을 클릭했을때 실행되는 함수를 CMExDaoView에 설정하고 설정된 함수를 수정합니다. 다음은 설정된 함수를 수정한 내용입니다.


// 입력항목

void CMExDaoView::OnAdddata()

{

       // TODO: Add your command handler code here

       m_pSet->MoveLast();

       m_pSet->AddNew();

       UpdateData(TRUE);

       m_pSet->Update();

       m_pSet->Requery();

       UpdateData(FALSE);   

}

//수정항목

void CMExDaoView::OnModedata()

{

       // TODO: Add your command handler code here

       m_pSet->Edit();

       UpdateData(TRUE);

       m_pSet->Update();

       m_pSet->Requery();

       UpdateData(FALSE);

       

}

//검색

void CMExDaoView::OnSeekname()

{

       // TODO: Add your command handler code here

       UpdateData(TRUE);

       m_pSet->m_strFilter.Format("name= '%s' ",m_pSet->m_name);

       m_pSet->Requery();

       UpdateData(FALSE);   

       

}

//검색 해제

void CMExDaoView::OnFreeseek()

{

       // TODO: Add your command handler code here

       m_pSet->m_strFilter="";

       m_pSet->Requery();

       UpdateData(FALSE);           

       

}

//삭제

void CMExDaoView::OnDeldata()

{

       // TODO: Add your command handler code here

       m_pSet->Delete();

       m_pSet->Requery();

       UpdateData(FALSE);

       

}


이와 같이 한후 컴파일 하여 실행하면 그림24와 같은 화면이 출력됩니다. 이기능은 ODBC 와 같습니다.

               (그림 24)MExDao 출력결과


진보된 ODBC와 DAO프로그래밍에 대하여

본장에서는 CRecordView와 CDaoRecordView그리고 CRecordset,CDaoRecordset을 이용하여 데이터베이스를 핸들링하는 방법을 설명하였습니다.

또한 한개의 테이블만 핸들링하는 데이터베이스를 설정하였습니다. 실제로 복잡한 ODBC나 DAO를 핸들링할때는 CRecordView와 CDaoRecordView와 결합하지 않은 CDatabase와 CDaoDatabase클래스를 핸들링하는 것이 일반적입니다. 이부분에 대한 내용의 참조는  필자가 쓴 “Visual C++ Programming Bible"를 참조하시기 바랍니다.

 

출처:e-book은 이상엽 박사의 집필본을 옮겨놓은 것입니다. 여러분의 프로그래밍 학습에 많은 도움이 되기를 바랍니다.

반응형

'IT > 데이터 베이스 프로그래밍' 카테고리의 다른 글

데이터베이스 프로그래밍  (0) 2008.07.25