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

... 46  47  48  49  50  51  52  53  [54]  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12293정성태8/24/202011104Windows: 171. "Administered port exclusions" 설명
12292정성태8/20/202012412.NET Framework: 932. C# - ETW 관련 Win32 API 사용 예제 코드 (1)파일 다운로드2
12291정성태8/15/202011399오류 유형: 638. error 1297: Device driver does not install on any devices, use primitive driver if this is intended.
12290정성태8/11/202012039.NET Framework: 931. C# - IP 주소에 따른 국가별 위치 확인 [8]파일 다운로드1
12289정성태8/6/20209503개발 환경 구성: 502. Portainer에 윈도우 컨테이너를 등록하는 방법
12288정성태8/5/20209574오류 유형: 637. WCF - The protocol 'net.tcp' does not have an implementation of HostedTransportConfiguration type registered.
12287정성태8/5/202010040오류 유형: 636. C# - libdl.so를 DllImport로 연결 시 docker container 내에서 System.DllNotFoundException 예외 발생
12286정성태8/5/202010836개발 환경 구성: 501. .NET Core 용 container 이미지 만들 때 unzip이 필요한 경우
12285정성태8/4/202011240오류 유형: 635. 윈도우 10 업데이트 - 0xc1900209 [2]
12284정성태8/4/202010523디버깅 기술: 169. Hyper-V의 VM에 대한 메모리 덤프를 뜨는 방법
12283정성태8/3/202010999디버깅 기술: 168. windbg - 필터 드라이버 확인하는 확장 명령어(!fltkd) [2]
12282정성태8/2/20209716디버깅 기술: 167. windbg 디버깅 사례: AppDomain 간의 static 변수 사용으로 인한 crash (2)
12281정성태8/2/202012295개발 환경 구성: 500. (PDB 연결이 없는) DLL의 소스 코드 디버깅을 dotPeek 도구로 해결하는 방법
12280정성태8/2/202011457오류 유형: 634. 오라클 (평생) 무료 클라우드 VM 생성 후 SSH 접속 시 키 오류 발생 [2]
12279정성태7/29/202012349개발 환경 구성: 499. 닷넷에서 접근해보는 InterSystems의 Cache 데이터베이스파일 다운로드1
12278정성태7/23/20209604VS.NET IDE: 149. ("Binary was not built with debug information" 상태로) 소스 코드 디버깅이 안되는 경우
12277정성태7/23/202011128개발 환경 구성: 498. DEVPATH 환경 변수의 사용 예 - .NET Reflector의 (PDB 연결이 없는) DLL의 소스 코드 디버깅
12276정성태7/23/202010431.NET Framework: 930. 개발자를 위한 닷넷 어셈블리 바인딩 - DEVPATH 환경 변수
12275정성태7/22/202012911개발 환경 구성: 497. 닷넷에서 접근해보는 InterSystems의 IRIS Data Platform 데이터베이스파일 다운로드1
12274정성태7/21/202012306개발 환경 구성: 496. Azure - Blob Storage Account의 Location 이전 방법 [1]파일 다운로드1
12273정성태7/18/202013993개발 환경 구성: 495. Azure - Location이 다른 웹/DB 서버의 경우 발생하는 성능 하락
12272정성태7/16/20208916.NET Framework: 929. (StrongName의 버전 구분이 필요 없는) .NET Core 어셈블리 바인딩 규칙 [2]파일 다운로드1
12271정성태7/16/202011008.NET Framework: 928. .NET Framework의 Strong-named 어셈블리 바인딩 (2) - 런타임에 바인딩 리디렉션파일 다운로드1
12270정성태7/16/202011773오류 유형: 633. SSL_CTX_use_certificate_file - error:140AB18F:SSL routines:SSL_CTX_use_certificate:ee key too small
12269정성태7/16/20208723오류 유형: 632. .NET Core 웹 응용 프로그램 - The process was terminated due to an unhandled exception.
12268정성태7/15/202010933오류 유형: 631. .NET Core 웹 응용 프로그램 오류 - HTTP Error 500.35 - ANCM Multiple In-Process Applications in same Process
... 46  47  48  49  50  51  52  53  [54]  55  56  57  58  59  60  ...