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

... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...
NoWriterDateCnt.TitleFile(s)
11985정성태7/17/201912780개발 환경 구성: 452. msbuild - csproj에 환경 변수 조건 사용 [1]
11984정성태7/9/201921234개발 환경 구성: 451. Microsoft Edge (Chromium)을 대상으로 한 Selenium WebDriver 사용법 [1]
11983정성태7/8/201911482오류 유형: 556. nodemon - 'mocha' is not recognized as an internal or external command, operable program or batch file.
11982정성태7/8/201911751오류 유형: 555. Visual Studio 빌드 오류 - result: unexpected exception occured (-1002 - 0xfffffc16)
11981정성태7/7/201914446Math: 64. C# - 3층 구조의 신경망(분류)파일 다운로드1
11980정성태7/7/201924865개발 환경 구성: 450. Visual Studio Code의 Java 확장을 이용한 간단한 프로젝트 구축파일 다운로드1
11979정성태7/7/201914281개발 환경 구성: 449. TFS에서 gitlab/github등의 git 서버로 마이그레이션하는 방법
11978정성태7/6/201913138Windows: 161. 계정 정보가 동일하지 않은 PC 간의 인증을 수행하는 방법 [1]
11977정성태7/6/201917991오류 유형: 554. git push - error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 Request Entity Too Large
11976정성태7/4/201912072오류 유형: 553. (잘못 인증 한 후) 원격 git repo 재인증 시 "remote: HTTP Basic: Access denied" 오류 발생
11975정성태7/4/201921166개발 환경 구성: 448. Visual Studio Code에서 콘솔 응용 프로그램 개발 시 "입력"받는 방법
11974정성태7/4/201916435Linux: 22. "Visual Studio Code + Remote Development"로 윈도우 환경에서 리눅스(CentOS 7) C/C++ 개발
11973정성태7/4/201915461Linux: 21. 리눅스에서 공유 라이브러리가 로드되지 않는다면?
11972정성태7/3/201918846.NET Framework: 847. JAVA와 .NET 간의 AES 암호화 연동 [1]파일 다운로드1
11971정성태7/3/201915933개발 환경 구성: 447. Visual Studio Code에서 OpenCvSharp 개발 환경 구성
11970정성태7/2/201913979오류 유형: 552. 웹 브라우저에서 파일 다운로드 후 "Running security scan"이 끝나지 않는 문제
11969정성태7/2/201914547Math: 63. C# - 3층 구조의 신경망파일 다운로드1
11968정성태7/1/201920757오류 유형: 551. Visual Studio Code에서 Remote-SSH 연결 시 "Opening Remote..." 단계에서 진행되지 않는 문제 [1]
11967정성태7/1/201914866개발 환경 구성: 446. Synology NAS를 Windows 10에서 iSCSI로 연결하는 방법
11966정성태6/30/201914525Math: 62. 활성화 함수에 따른 뉴런의 출력을 그리드 맵으로 시각화파일 다운로드1
11965정성태6/30/201914845.NET Framework: 846. C# - 2차원 배열을 1차원 배열로 나열하는 확장 메서드파일 다운로드1
11964정성태6/30/201916666Linux: 20. C# - Linux에서의 Named Pipe를 이용한 통신
11963정성태6/29/201916522Linux: 19. C# - .NET Core Unix Domain Socket 사용 예제
11962정성태6/27/201914438Math: 61. C# - 로지스틱 회귀를 이용한 선형분리 불가능 문제의 분류파일 다운로드1
11961정성태6/27/201913488Graphics: 37. C# - PLplot - 출력 모음(Family File Output)
11960정성태6/27/201914430Graphics: 36. C# - PLplot의 16색 이상을 표현하는 방법과 subpage를 이용한 그리드 맵 표현
... 61  62  63  64  65  66  67  68  69  70  71  72  [73]  74  75  ...