Microsoft MVP성태의 닷넷 이야기
.NET Framework: 362. C# - 닷넷 응용 프로그램에서 Sybase DB 사용 [링크 복사], [링크+제목 복사]
조회: 23917
글쓴 사람
정성태 (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
}
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13608정성태4/26/2024224닷넷: 2249. C# - 부모의 필드/프로퍼티에 대해 서로 다른 자식 클래스 간에 Reflection 접근이 동작할까요?파일 다운로드1
13607정성태4/25/2024221닷넷: 2248. C# - 인터페이스 타입의 다중 포인터를 인자로 갖는 C/C++ 함수 연동
13606정성태4/24/2024247닷넷: 2247. C# - tensorflow 연동 (MNIST 예제)파일 다운로드1
13605정성태4/23/2024549닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동파일 다운로드1
13604정성태4/22/2024599오류 유형: 901. Visual Studio - Unable to set the next statement. Set next statement cannot be used in '[Exception]' call stack frames.
13603정성태4/21/2024797닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동파일 다운로드1
13602정성태4/20/2024856닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)파일 다운로드1
13601정성태4/19/2024890닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/2024912닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024878닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024913닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024896닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/20241077닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/20241061닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241079닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241089닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241226C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241201닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241081Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241158닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241274닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신 [2]파일 다운로드1
13587정성태3/27/20241358오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241532Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241313Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241273개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...