Microsoft MVP성태의 닷넷 이야기
.NET Framework: 362. C# - 닷넷 응용 프로그램에서 Sybase DB 사용 [링크 복사], [링크+제목 복사],
조회: 24242
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)

C# - 닷넷 응용 프로그램에서 Sybase DB 사용

역시 유료 DBMS인 Sybase도 무료 버전인 "SQL Anywhere Developer Edition"을 배포하고 있습니다. 다음의 경로를 통해서 다운로드 받고,

SQL Anywhere 12.0.1 Developer Edition
; http://www.sybase.com/detail?id=1055872

설치하면 관리도구까지 모두 함께 설치됩니다. 특이하게도, 관리자 암호를 묻는 창도 안 뜹니다. 이것 역시 DB2와 유사하게 DB 인스턴스에 접속해서 관리를 하는 것이 아니라 데이터베이스를 생성하면서 계정 정보를 입력하고 관리하는 식입니다.

함께 설치된 관리도구 - Sybase Central을 실행하고 "Tools" / "Create Database..." 메뉴를 선택해 새로운 데이터베이스를 생성합니다. 나머지는 그냥... ^^ 감각적으로 DB 테이블 생성하고 해주시면 됩니다.

재미있는 것은, DB를 생성한 것과 서비스를 시작하는 것은 전혀 다르다는 점입니다. DB 생성은 단지 파일을 생성한 것 뿐입니다. 이 DB 파일을 호스팅하는 것은 완전히 별개로 나뉩니다.

서비스 시작은 별도로 "시작" 메뉴에 등록된 "Network Server"라는 프로그램을 실행시켜야 합니다. 그럼 다음과 같이 DB 파일을 선택하는 창이 뜨고,

sybase_connect_1.png

파일 경로만 입력한 후 나머지 입력 상자는 무시하고 그냥 "OK" 버튼을 누릅니다. 그럼 다음과 같은 창이 뜨면서 로그를 출력하고 서비스가 시작됩니다.

sybase_connect_2.png

무료인 "Developer Edition"이라 그런지 제약이 많습니다. Connection Limit도 3이고, 서비스도 이렇게 Interactive 프로세스로 뜨는 것만 기본 제공되고 있습니다.

명령행에서 곧바로 실행시키려면 다음과 같이 실행하면 됩니다.

C:\Program Files\SQL Anywhere 12\Bin64>dbsrv12 d:\sadb\mytestdb.db

스케쥴러에 컴퓨터 시작 시 실행되는 프로그램으로 등록해 두면 그나마 NT 서비스 처럼 동작시킬 수 있습니다.

참고로, 테스트를 위한 DB 테이블은 지난번 MySQL 과 동일하게 해주었습니다.




구성된 DB 서버로 .NET 클라이언트에서 접속을 해봐야 하는데요.

Sybase 용 ADO.NET Data Provider 역시 다음의 다운로드를 통해 쉽게 설치할 수 있습니다. 설치 도중 "SQL Anywhere Client" 구성 요소가 64-bit, 32-bit 중에 하나만 선택되어 있기 때문에 필요하다면 2개 모두 명시적으로 설치 표시를 해야 합니다.

SQL Anywhere Database Client Download   
; http://www.sybase.com/detail?id=1087327

설치 후 C:\Program Files\SQL Anywhere 12\Assembly 폴더를 보면 .NET 2.0, 3.5, 4.0 각각의 버전에 맞게 하위 폴더가 나뉘고 다음의 3가지 어셈블리 파일을 구할 수 있습니다.

  • iAnywhere.Data.SQLAnywhere.dll
  • iAnywhere.Data.SQLAnywhere.v3.5.dll
  • iAnywhere.Data.SQLAnywhere.v4.0.dll

iAnywhere.Data.SQLAnywhere도 MySQL처럼 완벽한 하나의 Managed Provider 역할을 합니다. 따라서 지난번 DB2처럼 별도의 Native 모듈을 배포할 필요는 없습니다.

프로그램은 대충 다음의 글을 참고해서 만들 수 있습니다.

Connecting to a SQL Anywhere Database Using ADO.NET and the SQL Anywhere .NET Data Provider 
; https://wiki.scn.sap.com/wiki/display/SQLANY/Connecting+to+a+SQL+Anywhere+Database+Using+ADO.NET+and+the+SQL+Anywhere+.NET+Data+Provider

위의 글에는 ODBC에 등록된 DSN을 이용한 연결문자열을 사용했지만, ODBC 경유 없이 곧바로 접속하는 것도 가능합니다. 다음은 제 테스트 환경의 연결 문자열입니다.

ENG=mytestdb;LINKS=tcpip(Host=192.168.100.21;ServerPort=2638);UID=DBA;PWD=sql

그리고 테스트 예제는 지난번과 다름없이 만들었습니다. Parameter 대체 문자로 '?'를 쓴 것이 다를 뿐 거의 비슷합니다.

string connectionString = "ENG=mytestdb;LINKS=tcpip(Host=192.168.0.11;ServerPort=2638);UID=DBA;PWD=dba@2008";

using (SAConnection myConnection = new SAConnection(connectionString))
{
                
    myConnection.Open();

    // Create
    SACommand insertCommand = new SACommand();
    insertCommand.Connection = myConnection;
    insertCommand.CommandText = "INSERT INTO mytable(id, NAME, age, DESCRIPTION) VALUES (?, ?, ?, ?)";

    insertCommand.Parameters.Add("@id", SADbType.Integer);
    insertCommand.Parameters.Add("@NAME", SADbType.VarChar, 50);
    insertCommand.Parameters.Add("@age", SADbType.Integer);
    insertCommand.Parameters.Add("@DESCRIPTION", SADbType.VarChar, 150);

    string nameValue = "Name" + Guid.NewGuid().ToString();
    insertCommand.Parameters[0].Value = (int)DateTime.Now.Ticks;
    insertCommand.Parameters[1].Value = nameValue;
    insertCommand.Parameters[2].Value = 10;
    insertCommand.Parameters[3].Value = nameValue + "_Description";

    int affected = insertCommand.ExecuteNonQuery();
    Console.WriteLine("# of affected row: " + affected);

    // Update
    SACommand updateCommand = new SACommand();
    updateCommand.Connection = myConnection;
    updateCommand.CommandText = "UPDATE mytable SET DESCRIPTION=? WHERE NAME=?";

    updateCommand.Parameters.Add("@NAME", SADbType.VarChar, 50);
    updateCommand.Parameters.Add("@DESCRIPTION", SADbType.VarChar, 150);

    updateCommand.Parameters[0].Value = nameValue;
    updateCommand.Parameters[1].Value = nameValue + "_Description2";

    affected = updateCommand.ExecuteNonQuery();
    Console.WriteLine("# of affected row: " + affected);

    // Select - ExecuteScalar
    SACommand selectCommand = new SACommand();
    selectCommand.Connection = myConnection;
    selectCommand.CommandText = "SELECT count(*) FROM mytable";

    object result = selectCommand.ExecuteScalar();
    Console.WriteLine("# of records: " + result);

    // Select - DataTable
    DataSet ds = new DataSet();
    SADataAdapter da = new SADataAdapter("SELECT * FROM mytable", myConnection);
    da.Fill(ds, "mytable");

    DataTable dt = ds.Tables["mytable"];
    foreach (DataRow dr in dt.Rows)
    {
        Console.WriteLine(string.Format("Name = {0}, Desc = {1}", dr["NAME"], dr["DESCRIPTION"]));
    }

    // Delete
    SACommand deleteCommand = new SACommand();
    deleteCommand.Connection = myConnection;
    deleteCommand.CommandText = "DELETE FROM mytable WHERE NAME=?";

    deleteCommand.Parameters.Add("@NAME", SADbType.VarChar, 50);
    deleteCommand.Parameters[0].Value = nameValue;

    affected = deleteCommand.ExecuteNonQuery();
    Console.WriteLine("# of affected row: " + affected);
}

배포는 어떻게 해야 할까요? 위에서 언급한 것처럼 "iAnywhere.Data.SQLAnywhere.dll" 파일이 관리 제공자로써 동작하기 때문에 위의 프로그램을 다른 컴퓨터에 배포할 때 "SQL Anywhere Client"를 굳이 설치할 필요는 없습니다.

하지만, 다소 복잡한 문제가 발생했습니다. 알고 보니 iAnywhere.Data.SQLAnywhere.dll 파일은 내부적으로 dbdata12.dll과 dbdata12.dll.x64라는 네이티브 DLL을 리소스로 들고 있습니다. SAConnection은 최초 실행 시에 임시 폴더에 현재 프로세스와 관련된 dbdata12 DLL 파일을 임시 폴더에 풀어놓습니다. 예를 들어 다음과 같은 경로입니다.

C:\Windows\Temp\{16AA8FB8-4A98-4757-B7A5-0FF22C0A6E33}_1201.x64_1\dbdata12.dll

하지만, dbdata12.dll은 내부적으로 오류 메시지를 담은 또 다른 네이티브 DLL을 로드합니다. 그래서, iAnywhere.Data.SQLAnywhere.dll만 참조해서 그냥 실행하면 SAConnection 생성 시점에 다음과 같은 식의 예외가 발생합니다.

System.TypeInitializationException occurred
  HResult=-2146233036
  Message=The type initializer for 'iAnywhere.Data.SQLAnywhere.SAConnection' threw an exception.
  Source=mscorlib
  TypeName=iAnywhere.Data.SQLAnywhere.SAConnection
  StackTrace:
       at System.Runtime.Remoting.RemotingServices.AllocateUninitializedObject(RuntimeType objectType)
       at System.Runtime.Remoting.Activation.ActivationServices.CreateInstance(RuntimeType serverType)
  InnerException: iAnywhere.Data.SQLAnywhere.SAException
       HResult=-2147467259
       Message=언어 리소스 파일(dblgko12.dll, dblgen12.dll)을 찾을 수 없습니다.
Cannot find the language resource file (dblgko12.dll, dblgen12.dll).
       Source=SQL Anywhere .NET Data Provider
       ErrorCode=-2147467259
       NativeError=0
       StackTrace:
            at iAnywhere.Data.SQLAnywhere.SAUnmanagedDll.get_Instance()
            at iAnywhere.Data.SQLAnywhere.SAConnection..cctor()
       InnerException: 

이를 해결하기 위해서는 exe 파일과 같은 위치에 dblgen12.dll 파일만 함께 복사해 주면 됩니다. 문제는 이 파일이 네이티브 DLL이기 때문에 x86/x64로 나뉜다는 점입니다. 각각의 언어 모듈은 "SQL Anywhere Client"를 설치한 경우 다음의 폴더에 위치하고 있습니다.

C:\Program Files\SQL Anywhere 12\Bin32\dblgen12.dll
C:\Program Files\SQL Anywhere 12\Bin64\dblgen12.dll

그래서, 닷넷 프로그램이 x86인 경우에는 \Bin32\dblgen12.dll 파일을 복사해서 EXE와 같은 폴더에 넣어 두어야 하고, x64인 경우에는 \Bin64\dblgen12.dll 파일을 사용해야 합니다. 따라서, AnyCPU로 닷넷 프로그램을 만들 수 없습니다.

어쨌든, 필요한 파일은 iAnywhere.Data.SQLAnywhere.dll 파일과 각각의 플랫폼에 따른 dblgen12.dll 파일이므로 배포하는 데 크게 불편함은 없습니다. XCopy 식의 배포가 된다는 것입니다.




그런데, 콘솔 유형의 프로그램이야 EXE 파일을 생성하는 것이니 dblgen12.dll 파일의 배포가 자유롭지만, 웹 애플리케이션인 경우는 그렇지 않습니다.

w3wp.exe가 C:\Windows\System32\inetsrv 폴더에 위치하고 있기 때문에 dblgen12.dll 파일을 그곳에 복사하기에는 좀 그렇습니다. iAnywhere.Data.SQLAnywhere.dll 파일을 역어셈블해보니, 다행히 dbdata12.dll 파일 위치가 거의 고정적입니다.

C:\Windows\Temp\{16AA8FB8-4A98-4757-B7A5-0FF22C0A6E33}_1201.x64_1\dbdata12.dll

{16AA8FB8-4A98-4757-B7A5-0FF22C0A6E33}: 고정된 값
1201: iAnywhere.Data.SQLAnywhere.dll 파일의 Major, Minor, Build 버전숫자
x64: 플랫폼
1: 1부터 시작해서 기존의 dbdata12.dll 파일을 삭제 시도, 불가능하면 +1씩 증가해서 될 때 까지 반복.

그런데... 이것도 좀 그렇습니다. 공식적인 것은 아니기 때문입니다.

그다음으로 생각해 볼 것이, %PATH%에 지정된 경로입니다. 아쉽게도, 이런 경로에 집어넣었다가는 x64/x86 프로세스에 상관없이 로드가 될 것이기 때문에 해당 컴퓨터에서 하나의 Sybase 접속을 하는 응용 프로그램만 실행된다면 상관없지만 그 외의 경우에는 자칫 원인모를 충돌을 발생시킬 수 있습니다. (이런 건... 시간 지나면 찾기 힘듭니다.)

다행인 것은, %PATH%에 지정된 경로 중에서도 부가적으로 \bin64 또는 \bin32 폴더를 붙여서도 검색한다는 점입니다. 즉, C:\SYTEST라는 폴더가 %PATH%에 추가되어 있으면 C:\SYTEST\dblgen12.dll뿐만 아니라 64비트 프로세스의 경우 C:\SYTEST\BIN64\dblgen12.dll에서도 검색한다는 것입니다.

그럼, 힌트는 모두 나왔습니다. %PATH% 경로는 프로세스가 시작된 이후에도 바꿀 수 있다는 점을 감안해서 Global.asax.cs에 다음의 코드를 추가하면 경로 문제는 해결됩니다.

void Application_Start(object sender, EventArgs e)
{
    string curPath = Server.MapPath("/");
    curPath = Path.Combine(curPath, "bin");

    string path = Environment.GetEnvironmentVariable("PATH");
    path = curPath + ";" + path;
    Environment.SetEnvironmentVariable("PATH", path);
}

그다음 웹 애플리케이션 프로젝트의 하위에 \Bin64, \Bin32 폴더를 만들고 각각 dblgen12.dll 파일을 추가해 줍니다. 그다음 Solution 탐색기에서 해당 파일을 선택해서 속성창의 "Build Action"은 "None"으로 설정하고, "Copy to Output Directory"에는 "Copy if newer" 설정을 해주면 됩니다.

첨부된 파일은 위의 설정이 포함된 예제 프로젝트입니다.

역시... XCopy 배포가 되어야! ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/5/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2020-03-16 11시40분
작업 스케줄러에 등록된 항목 삭제

if ($(Get-ScheduledTask -TaskName "sydb" -ErrorAction SilentlyContinue).TaskName -eq "sydb") {
    Unregister-ScheduledTask -TaskName "sydb" -Confirm:$False
}
정성태

... 31  32  33  34  35  36  37  38  39  40  41  42  43  [44]  45  ...
NoWriterDateCnt.TitleFile(s)
12549정성태3/4/20217872오류 유형: 700. VsixPublisher를 이용한 등록 시 다양한 오류 유형 해결책
12548정성태3/4/20218696개발 환경 구성: 546. github workflow/actions에서 nuget 패키지 등록하는 방법
12547정성태3/3/20219113오류 유형: 699. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly.
12546정성태3/3/20218770개발 환경 구성: 545. github workflow/actions에서 빌드시 snk 파일 다루는 방법 - Encrypted secrets
12545정성태3/2/202111524.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap) [10]
12544정성태2/26/202111737.NET Framework: 1025. C# - Control의 Invalidate, Update, Refresh 차이점 [2]
12543정성태2/26/202110058VS.NET IDE: 158. C# - 디자인 타임(design-time)과 런타임(runtime)의 코드 실행 구분
12542정성태2/20/202112396개발 환경 구성: 544. github repo의 Release 활성화 및 Actions를 이용한 자동화 방법 [1]
12541정성태2/18/20219643개발 환경 구성: 543. 애저듣보잡 - Github Workflow/Actions 소개
12540정성태2/17/20219939.NET Framework: 1024. C# - Win32 API에 대한 P/Invoke를 대신하는 Microsoft.Windows.CsWin32 패키지
12539정성태2/16/20219872Windows: 189. WM_TIMER의 동작 방식 개요파일 다운로드1
12538정성태2/15/202110298.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/202111338.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202110292개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/20219457개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/20219946개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/20219633개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/202110147개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [3]
12531정성태2/5/20219143개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/202113369개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
12529정성태2/4/202110360개발 환경 구성: 535. Wireshark + C#으로 확인하는 TCP 통신의 MIN RTO [1]
12528정성태2/1/20219754개발 환경 구성: 534. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 윈도우 환경
12527정성태2/1/20219942개발 환경 구성: 533. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 리눅스 환경파일 다운로드1
12526정성태2/1/20217774개발 환경 구성: 532. Azure Devops의 파이프라인 빌드 시 snk 파일 다루는 방법 - Secure file
12525정성태2/1/20217488개발 환경 구성: 531. Azure Devops - 파이프라인 실행 시 빌드 이벤트를 생략하는 방법
12524정성태1/31/20218649개발 환경 구성: 530. 기존 github 프로젝트를 Azure Devops의 빌드 Pipeline에 연결하는 방법 [1]
... 31  32  33  34  35  36  37  38  39  40  41  42  43  [44]  45  ...