david's daily developer note

오라클 데이타베이스 기반 .NET 애플리케이션의 구축 본문

Develop (kids)

오라클 데이타베이스 기반 .NET 애플리케이션의 구축

mouse-david 2010. 8. 6. 11:10
728x90

오라클 데이타베이스를 사용한 .NET 애플리케이션의 구축에 수반되는 기본적인 프로세스를 배워 보십시오.

Microsoft .NET Framework의 인기가 높아지면서, 많은 개발자들이 .NET 애플리케이션과 오라클의 기본적인 연결, Visual Studio.NET(VS.NET)을 이용한 개발작업, 효과적인 통합 방법에 대해 궁금해하고 있습니다.

이 문서에서는 오라클 데이타베이스 기반 환경에서 .NET 애플리케이션을 구축하는데 관련한, 기본적이면서도 매우 중요한 프로세스를 설명합니다:

  • .NET 프로젝트에서 Oracle 클래스 라이브러리를 지원하기 위한 프로젝트 레퍼런스의 추가 방법
  • Oracle Database connection string의 생성 방법
  • Connection, Command, DataReader 오브젝트의 활용 방법

또 서로 다른 난이도를 갖는 세 가지 실습 예제를 통해, 배운 내용을 적용해 보실 수 있을 것입니다.

애플리케이션 보안에 관련한 정보가 필요하시다면 필자의 다른 아티클 "오라클 데이터베이스 기반의 .NET 애플리케이션 보안" 을 참고하시기 바랍니다. "오라클 기반의 .NET 애플리케이션 개발 마스터하기" 시리즈에서도 전체 애플리케이션 라이프사이클에 걸친 다양한 기술적 이슈에 대한 설명을 확인하실 수 있습니다.)

OTN에서 무료로 다운로드 가능한 Oracle Developer Tools for .NET에 포함된 Visual Studio .NET 애드-인을 이용하면 훨씬 쉽고 직관적인 방법으로 오라클 기반 .NET 애플리케이션의 개발 작업을 수행할 수 있습니다. 보다 자세한 정보는 Oracle Developer Tools for .NET 제품 센터에서 확인하시기 바랍니다.

.NET Data Provider

.NET 애플리케이션을 사용하려면 기본적인 오라클 클라이언트 연결(client connectivity) 소프트웨어 이외에도 소위 “managed data provider”라 알려진 컴포넌트가 필요합니다 (여기서 “managed”란 코드가 .NET 프레임워크에 의해 관리되고 있음을 의미합니다). 데이타 프로바이더(data provider)는 .NET 애플리케이션 코드와 오라클 클라이언트 연결 소프트웨어의 중간 계층을 이룹니다. 대부분의 경우, “generic”한 .NET OLE DB 데이타 프로바이더를 사용하는 것보다 특정 데이타베이스 플랫폼에 최적화된 데이타 프로바이더를 사용했을 때 최상의 성능을 얻을 수 있습니다.

오라클, Microsoft와 써드 파티 벤더는 모두 오라클에 최적화된 데이타 프로바이더를 제공하고 있습니다. 오라클과 Microsoft는 Oracle 데이타 프로바이더를 무료로 제공하고 있습니다. (Microsoft의 .NET 프레임워크 프로바이더 버전 1.1은 프레임워크에 기본적으로 포함되어 있으므로 별도 다운로드/설치가 불필요합니다.) 일부 써드 파티 데이타 프로바이더의 경우, 오라클 구 버전을 지원하거나, 오라클 클라이언트 소프트웨어를 설치할 필요가 없다는 부가적인 장점을 제공합니다. 이 문서에서는 Oracle Data Provider for .NET(ODP.NET)을 별도로 다운로드하여 설치한 경우를 가정합니다.

ODP.NET과 오라클 클라이언트 연결 소프트웨어가 설치되었다면, Visual Studio.NET을 이용한 애플리케이션 개발 작업을 바로 시작할 수 있습니다. 개발 작업을 시작하기 전에 먼저 클라이언트 연결을 점검하는 것이 좋습니다. VS.NET이 설치된 장비에서 SQL*Plus를 통해 오라클에 연결할 수 있다면, 오라클 클라이언트 소프트웨어가 올바르게 설치/구성된 것으로 볼 수 있습니다.

오라클을 처음 접하는 경우라면 Oracle Data Provider for .NET Developer's Guide 10g Release 1 (10.1) 의 "Connecting to the Oracle Database" 섹션을 참고하셔서 ODP.NET에 관련한 기본적인 정보를 얻으시거나, Oracle Database Administrator's Guide 10g Release 1 (10.1) 를 참고하여 오라클 데이타베이스 관리에 관련한 정보를 얻으시기 바랍니다. 이 밖에도 ODP.NET의 연결 방법에 대한 How-To 문서 "Connect to an Oracle Database Using ODP.NET" 가 별도로 제공됩니다.

Visual Studio.NET을 이용한 프로젝트의 생성

VS.NET을 시작한 다음 제일 먼저 프로젝트를 생성합니다. New Project 버튼을 클릭하거나 File | New Project... 메뉴를 선택하면 됩니다.

figure 1
그림 1: Visual Studio.NET에서 새로운 프로젝트 만들기

New Project 대화상자가 표시됩니다. 대화상자 왼쪽의 Project Types에서 사용할 프로그래밍 언어를 선택합니다. 예제에서는 VB.NET이 선택되었습니다. 대화상자 오른쪽의 Templates에서는 프로젝트 템플릿을 선택합니다. 여기에서는 Windows Application을 선택하기로 합니다.

figure 2
그림 2: New Project 대화상자

프로젝트 명(OtnWinApp)과 솔루션 명(OtnSamples)은 의미 있는 이름을 사용하는 것이 좋습니다. 특정 솔루션(solution)에는 하나 또는 그 이상의 프로젝트가 포함됩니다. 솔루션에 하나의 프로젝트만이 포함된 경우에는 프로젝트 명과 솔루션 명을 동일하게 설정하는 경우가 많습니다.

레퍼런스의 추가

프로젝트를 오라클 데이타베이스에 연결하려면, 선택한 데이타 프로바이더를 포함하는 dll에 레퍼런스를 추가해야 합니다. Solution Explorer에서 Refrence 노드를 선택하고, 마우스 오른쪽 버튼을 클릭한 후 Add Reference를 선택합니다. 
(또는 메뉴 바에서 
Project와 Add Reference를 순서대로 선택합니다.)

figure 3
그림 3: 레퍼런스의 추가

Add Reference 대화상자가 표시됩니다.

figure 4
그림 4: ODP.NET Managed Data Provider의 선택

리스트에서 Oracle.DataAccess.dll을 선택한 후 Select 버튼을 클릭합니다. 그리고 OK 버튼을 클릭하면 ODP.NET 데이타 프로바이더를 프로젝트에서 사용할 수 있게 됩니다.

figure 5
그림 5: Oracle Managed Provider 선택 후 Solution Explorer 확인

VB.NET/C# 구문

레퍼런스를 추가한 후에는 VB.NET Imports 구문, C# using 구문, 또는 J# import 구문을 삽입하는 것이 일반적인 관행입니다. 이 구문은 반드시 요구되는 것은 아니지만, 길고 복잡한 “qualified name” 대신 간단한 이름으로 데이타베이스 오브젝트를 참조할 수 있다는 장점이 있습니다.

이 구문은 코드 파일의 네임스페이스/클래스 선언부 이전의 최상단 부분에 위치하는 것이 관례입니다.
Imports System.Data              ' VB.NET
Imports Oracle.DataAccess.Client ' ODP.NET Oracle managed provider

using System.Data;              // C#
using Oracle.DataAccess.Client; // ODP.NET Oracle managed provider

import System.Data.*;            // J#
import Oracle.DataAccess.Client; // ODP.NET Oracle managed provider
Connection String과 오브젝트

오라클 “name resolution”을 위해서는 Oracle connection string이 반드시 필요합니다. tnsnames.ora 파일에 “OraDb”를 데이타베이스 앨리어스(alias)로 지정한 경우를 가정해 봅시다:

OraDb=
  (DESCRIPTION=
    (ADDRESS_LIST=
      (ADDRESS=(PROTOCOL=TCP)(HOST=OTNSRVR)(PORT=1521))
    )
    (CONNECT_DATA=
      (SERVER=DEDICATED)
      (SERVICE_NAME=ORCL)
    )
  )
tnsnames.ora에 정의된 OraDb 앨리어스를 사용하려면 다음과 같은 구문을 사용해야 합니다:
Dim oradb As String = "Data Source=OraDb;User Id=scott;Password=tiger;" ' VB.NET

string oradb = "Data Source=OraDb;User Id=scott;Password=tiger;"; // C#
tnsnames.ora 파일을 이용하지 않도록 connection string을 수정할 수도 있습니다. tnsnames.ora 파일에 앨리어스를 정의하지 않고 직접 앨리어스를 선언하는 방법이 아래와 같습니다.
' VB.NET 
Dim oradb As String = "Data Source=(DESCRIPTION=" _
           + "(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=OTNSRVR)(PORT=1521)))" _
           + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));" _
           + "User Id=scott;Password=tiger;"

string oradb = "Data Source=(DESCRIPTION="              // C#
             + "(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=OTNSRVR)(PORT=1521)))"
             + "(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ORCL)));"
             + "User Id=scott;Password=tiger;";
위의 예제에서는, 사용자명과 패스워드가 connection string에 일반 텍스트 포맷으로 저장되어 있습니다. 이것은 connection string을 생성하는 가장 간단한 방법이긴 하지만, 보안 측면에서 볼 때 문제가 있습니다. 또 .NET 애플리케이션 코드를 컴파일하더라도 보안 수준의 향상에는 그리 도움이 되지 못합니다. .NET dll/exe 파일을 디컴파일(decompile)해서 텍스트 컨텐트를 확인하는 것은 매우 쉬운 일입니다. (가장 권장할만한 방법은 데이타를 암호화하는 것입니다. 하지만 여기서는 예제를 단순화하기 위해 암호화를 적용하지 않기로 합니다.)

다음은 connection 클래스로부터 connection 오브젝트를 인스턴스화(instantiate)할 차례입니다. connection string은 connection 오브젝트와 연결(associate)되어 있어야 합니다.
Dim conn As New OracleConnection(oradb) ' VB.NET

OracleConnection conn = new OracleConnection(oradb); // C#
위에서 connection string이 오브젝트의 (overload된) constructor를 통한 “pass through” 방식으로 connection 오브젝트와 연결되어 있음을 참고하시기 바랍니다. Constructor의 overload를 이용하면 위의 구문 대신 다음과 같은 구문을 적용하는 것도 가능합니다:
Dim conn As New OracleConnection() ' VB.NET
conn.ConnectionString = oradb

OracleConnection conn = new OracleConnection(); // C#
conn.ConnectionString = oradb;
Connection string과 connection 오브젝트를 연결한 다음에는 Open 메소드를 이용하여 실제 커넥션을 인스턴스화(instantiate)합니다.
conn.Open() ' VB.NET

conn.Open(); // C#
에러 핸들링에 관해서는 뒷 부분에서 다루기로 하겠습니다.

Command 오브젝트

Command 오브젝트는 실행될 SQL 커맨드 텍스트(SQL 문자열 또는 저장 프로시저)를 명시하는 용도로 사용됩니다. Connection 오브젝트와 마찬가지로, Command 오브젝트 역시 클래스로부터 생성되며 overloaded constructor를 갖습니다.

Dim sql As String = "select dname from dept where deptno = 10" ' VB.NET
Dim cmd As New OracleCommand(sql, conn)
cmd.CommandType = CommandType.Text

string sql = "select dname from dept where deptno = 10"; // C#
OracleCommand cmd = new OracleCommand(sql, conn);
cmd.CommandType = CommandType.Text;
다른 overload를 사용하는 경우, 다른 형태의 구문을 사용할 수 있게 됩니다. Command 오브젝트는 커맨드 텍스트의 실행을 위한 메소드를 포함하고 있습니다. 또 SQL 커맨드 유형 별로 다른 메소드를 사용하게 됩니다.

스칼라 값의 조회

데이타베이스로부터 데이타를 조회하려면, 먼저 DataReader 오브젝트를 인스턴스화(instantiate)하고, ExecuteReader 메소드를 사용하여 OracleDataReader 오브젝트를 반환하도록 해야 합니다. VB.NET 개발자는 Item 속성에 컬럼 명 또는 “zero-based” 컬럼 오디널(column ordinal)을 명시하는 방법으로 반환된 데이타에 접근할 수 있습니다. 또는 accessor 타입 메소드를 이용하여 컬럼 데이타를 반환하는 방법도 있습니다.

Dim dr As OracleDataReader = cmd.ExecuteReader() ' VB.NET
dr.Read()
Label1.Text = dr.Item("dname") ' retrieve by column name
Label1.Text = dr.Item(0) ' retrieve the first column in the select list
Label1.Text = dr.GetString(0) ' retrieve the first column in the select list
C# 개발자의 경우에는 반드시 데이타 조회를 위해 accessor 타입 메소드를 이용해야 합니다. .NET 네이티브 데이타 타입의 반환, 그리고 네이티브 오라클 데이타 타입의 반환을 위한 타입별 accessor가 각각 별도로 존재합니다. 이때 반환할 컬럼을 명시하기 위해 zero-based ordinal을 accessor에 전달하는 방법이 사용됩니다.

OracleDataReader dr = cmd.ExecuteReader(); // C#
dr.Read();
label1.Text = dr.GetString(0); // C# retrieve the first column in the select list
위의 예제에서 반환되는 dname은 문자열 데이타 타입으로, 레이블 컨트롤(label control)의 텍스트 속성 값(이 값 역시 문자열입니다)을 설정하는데 사용됩니다. 하지만 dname 대신 문자열이 아닌 deptno가 반환되었다면 데이타 타입 미스매치(data type mismatch)가 발생하게 될 것입니다. .NET 런타임은 소스 데이타 타입과 타겟 데이타 타입이 일치하지 않는 경우 내부적으로 데이타 타입을 변환하려고 시도합니다. 이때 데이타 타입의 호환이 불가능한 경우에는 변환 작업이 실패하고 exception이 발생하게 됩니다. 또 변환이 성공한다 하더라도, 이러한 암시적(implicit) 데이타 타입 변환 대신 명시적(explicit) 데이타 변환을 사용하는 것이 권장됩니다.

integer 데이타 타입에 대해서 명시적 변환을 실행한 예가 아래와 같습니다:
Label1.Text = CStr(dr.Item("deptno")) ' VB.NET integer to string cast
C#는 VB.NET과 같은 암시적 변환(implicit conversion) 기능을 제공하지 않으므로, 직접 명시적 변환(explicit conversion)을 정의해야 합니다:
string deptno = dr.GetInt16("deptno").ToString(); // C#
이때 스칼라 값 뿐 아니라 어레이 역시 명시적 변환이 가능합니다.

Close와 Dispose

데이타베이스에 대한 연결을 종료하기 위해서는 connection 오브젝트의 Close 또는 Dispose 메소드를 호출해야 합니다. 이때 Dispose 메소드는 Close 메소드를 호출하는 방식으로 동작합니다.

conn.Close() ' VB.NET
conn.Dispose() ' VB.NET

conn.Close(); // C#
conn.Dispose(); // C#
또 C#에서는 연결이 “out of scope” 처리되는 경우 자동으로 연결을 해제하는 구문을 사용할 수 있습니다. 
이 기능의 구현을 위해 using 키워드가 사용됩니다.
using (OracleConnection conn = new OracleConnection(oradb))
{
    conn.Open();

    OracleCommand cmd = new OracleCommand();
    cmd.Connection = conn;
    cmd.CommandText = "select dname from dept where deptno = 10";
    cmd.CommandType = CommandType.Text;
	
	OracleDataReader dr = cmd.ExecuteReader();
    dr.Read();

    label1.Text = dr.GetString(0);
}
Lab 1 (데이타베이스로부터 데이타 조회하기)과 Lab 2 (인터액티브 환경의 추가)에서 위에서 설명한 개념을 응용한 예를 확인하실 수 있습니다).

에러 핸들링

.NET 언어에는 “Try-Catch-Finally” 구조의 에러 핸들링이 포함되어 있습니다. Try-Catch-Finally 구문을 적용한 간단한 예가 아래와 같습니다:

Dim conn As New OracleConnection(oradb) ' VB.NET
Try
    conn.Open()

    Dim cmd As New OracleCommand
    cmd.Connection = conn
    cmd.CommandText = "select dname from dept where deptno = " + TextBox1.Text
    cmd.CommandType = CommandType.Text

    If dr.Read() Then
        Label1.Text = dr.Item("dname") ' or use dr.Item(0)
    End If
Catch ex As Exception ' catches any error
    MessageBox.Show(ex.Message.ToString())
Finally
    conn.Dispose()
End Try

OracleConnection conn = new OracleConnection(oradb); // C#
try
{
    conn.Open();

    OracleCommand cmd = new OracleCommand();
    cmd.Connection = conn;
    cmd.CommandText = "select dname from dept where deptno = " + textBox1.Text;
    cmd.CommandType = CommandType.Text;

    if (dr.Read()) // C#
    {
        label1.Text = dr.GetString(0);
    }
}
catch (Exception ex) // catches any error
{
    MessageBox.Show(ex.Message.ToString());
}
finally
{
    conn.Dispose();
}
위와 같은 방법으로 데이타베이스의 데이타 인출 과정에서 발생하는 에러를 효과적으로 캡처할 수 있지만, 사용자 친화적인 환경이 제공되지 않는다는 단점이 있습니다. 한 예로, 데이타베이스 연결이 불가능한 경우 표시되는 메시지가 아래와 같습니다:

figure 6
그림 6: 사용자에게 표시되는 ORA-12545 에러

“ORA-12545”는 오라클 DBA나 개발자에게는 의미가 있지만, 엔드 유저에게는 그렇지 못합니다. 이런 방법보다는 Catch 구문을 추가하여 가장 일반적인 데이타베이스 에러에 대해 사용자 친화적인 메시지를 표시하도록 하는 것이 바람직합니다.

Catch ex As OracleException ' catches only Oracle errors
    Select Case ex.Number
        Case 1
            MessageBox.Show("Error attempting to insert duplicate data.")
        Case 12545
            MessageBox.Show("The database is unavailable.")
        Case Else
            MessageBox.Show("Database error: " + ex.Message.ToString())
    End Select
Catch ex As Exception ' catches any error
    MessageBox.Show(ex.Message.ToString())

catch (OracleException ex) // catches only Oracle errors
{
    switch (ex.Number)
    {
        case 1:
            MessageBox.Show("Error attempting to insert duplicate data.");
            break;
        case 12545:
            MessageBox.Show("The database is unavailable.");
            break;
        default:
            MessageBox.Show("Database error: " + ex.Message.ToString());
            break;
    }
}
catch (Exception ex) // catches any error
{
    MessageBox.Show(ex.Message.ToString());
}
위 코드에서 두 개의 Catch 구문을 참고하시기 바랍니다. 캐치(catch)할 오라클 에러가 없는 경우, 첫 번째 Catch 구문은 실행되지 않으며, 따라서 그 밖의 에러 유형은 두 번째 Catch 구문을 통해 처리되게 됩니다. 여러 개의 Catch 구문을 사용할 때에는, 세부적인 조건에서 가장 일반적인 조건의 순서대로 배치되어야 합니다. 위의 코드를 적용했다면, ORA-12545 에러가 발생한 경우 아래와 같은 메시지가 표시됩니다:

figure 7
그림 7: ORA-12545 에러 발생 시 표시되는 사용자 친화적 에러 메시지

Finally 코드 블록은 에러의 발생 여부에 관계없이 언제나 실행됩니다. Finally 코드 블록에 connection 오브젝트의 Close 또는 Dispose 메소드를 삽입하면, Try-Catch-Finally 코드 블록이 완료될 때마다 데이타베이스 연결이 종료됨을 보장할 수 있습니다. “open”되지 않은 데이타베이스 연결을 “close”하는 경우에는 에러가 발생하지 않습니다. 예를 들어 데이타베이스에 접근이 불가능한 경우, 데이타베이스 연결을 open되지 않습니다. 따라서 Finally 코드 블록은 존재하지 않는 연결을 종료하려 시도하지만, 이것이 문제가 되지는 않습니다. 중요한 것은 Finally 코드 블록에 Close 또는 Dispose 메소드를 위치시킴으로써 어떤 경우에든 연결이 종료됨을 보장할 수 있다는 점입니다.

DataReader를 이용한 여러 개의 값 조회

지금까지는 하나의 값만을 조회하는 방법에 대해 설명했습니다. DataReader를 이용하여 다수의 컬럼, 다수의 로우를 동시에 조회하는 것도 가능합니다. 먼저 여러 개의 컬럼과 하나의 로우를 쿼리하는 경우를 생각해 봅시다:

select deptno, dname, loc from dept where deptno = 10
컬럼의 값을 가져오기 위해서 zero-based ordinal 또는 컬럼 명이 사용됩니다. 오디널(ordinal)은 쿼리의 순서에 따라 결과가 달라집니다. 따라서 VB.NET에서 dr.Item(2) 또는 dr.Item("loc")을 이용하면 loc 컬럼의 값을 가져올 수 있습니다.

반환된 dname 컬럼과 loc 컬럼의 값을 연결(concatenate)하는 코드가 아래와 같습니다:

Label1.Text = "The " + dr.Item(1) + " department is in " + dr.Item("loc") ' VB.NET

Label1.Text = "The " + dr.GetString(1) + " department is in " + dr.GetString(2); // C#
이제 여러 개의 로우를 반환하는 쿼리를 생각해 봅시다:
select deptno, dname, loc from dept
DataReader가 반환하는 복수 개의 로우를 처리하려면, 루프(loop) 메커니즘이 필요합니다. 또 여러 개의 로우를 디스플레이하기 위한 컨트롤을 사용하는 것이 권장됩니다. DataReader는 “forward-only, read-only” 커서이므로 Windows Forms DataGrid 컨트롤과 같은 “updateable, fully scrollable" 컨트롤을 적용할 수는 없습니다. DataReader는 ListBox 컨트롤과 호환하며, 그 적용 예가 아래와 같습니다:
While dr.Read() ' VB.NET
   ListBox1.Items.Add("The " + dr.Item(1) + " department is in " + dr.Item("loc")) End While

while (dr.Read()) // C#
{
  listBox1.Items.Add("The " + dr.GetString(1) + " department is in " + dr.GetString(2);
}
Lab 3 (DataReader를 통해 복수의 컬럼/로우 조회하기)에서 위에서 설명한 개념을 실습해 보실 수 있습니다.

결론

이 문서를 통해 VS.NET 프로그래밍 언어를 이용하여 오라클 데이타베이스에 접근하는 과정을 설명하였습니다. 지금까지 설명된 내용을 응용해서 데이타베이스에 연결하고 복수의 컬럼과 로우를 조회할 수 있을 것입니다.


출처 : http://www.oracle.com/technology/global/kr/pub/articles/cook_dotnet.html 

728x90