성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>Microsoft.SqlServer.Types.SqlGeography 형변환 시 null 반환하는 문제</h1> <div style='text-decoration: line-through;'> <p> SQL Server 2008부터 SqlGeography 타입이 지원됩니다. 저도 그동안 쓸 일이 없다가 최근에야 사용해 보았는데요. SqlGeography 타입의 컬럼을 SELECT하고 DataReader에서 값을 읽어 SqlGeoGraphy로 형변환을 하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > object data = reader.GetValue("Location"); // not null SqlGeography geoData = data as SqlGeography; // null </pre> <br /> geoData 변수의 값이 null이 나왔습니다. 이상하군요. Visual Studio 디버거로 data 변수를 살펴보면 정상적으로 Microsoft.SqlServer.Types.SqlGeography를 가리켰습니다.<br /> <br /> <img alt='geography_is_null_1.png' src='/SysWebRes/bbs/geography_is_null_1.png' /> <br /><br /> 하지만, Watch 창에 "data as SqlGeoGraphy"라고 입력했더니 다음과 같이 형변환이 안되었습니다.<br /> <br /> <img alt='geography_is_null_2.png' src='/SysWebRes/bbs/geography_is_null_2.png' /> <br /><br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> The type 'Microsoft.SqlServer.Types.SqlGeography' exists in both 'Microsoft.SqlServer.Types.dll' and 'Microsoft.SqlServer.Types.dll'<br /> </div><br /> <br /> 그래도 메시지에 원인이 나왔습니다. Visual Studio의 "Modules" 창을 통해 확인해 보면 Microsoft.SqlServer.Types.dll 어셈블리가 10.0.0.0, 12.0.0.0 버전으로 2개 로드된 것을 볼 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='geography_is_null_3.png' src='/SysWebRes/bbs/geography_is_null_3.png' /><br /> <br /> 문제를 분석하니 다음과 같은 동작을 확인할 수 있었습니다.<br /> <br /> <ol> <li>SQL 쿼리 실행 시 SQL 서버로부터 12.0.0.0 Microsoft.SqlServer.Types.dll 버전의 SqlGeography 타입이 반환됨.</li> <li>코드에서 "data as SqlGeography" 형변환시 GAC로부터 10.0.0.0 버전의 Microsoft.SqlServer.Types.dll 어셈블리가 로드됨</li> </ol> <br /> 결정적인 원인은 밝혀졌지만 상황이 더욱 재미있어졌습니다. ^^ 왜냐하면 제 C# 프로젝트는 12.0.0.0 버전의 Microsoft.SqlServer.Types.dll을 참조하고 있기 때문이었습니다.<br /> <br /> 그런데, 왜? 코드 수행 시 10.0.0.0 버전이 로드된 것일까요? 원인을 찾아보니, 제 C# 프로젝트는 .NET 4.0 대상이었고, C:\Windows\Microsoft.NET\assembly\GAC_MSIL 경로의 .NET 4.0 GAC 저장소에는 12.0.0.0 버전의 Microsoft.SqlServer.Types.dll이 등록이 안되어 있었습니다. 대신 .NET 2.0 GAC(C:\Windows\assembly) 저장소에는 10.0.0.0, 11.0.0.0, 12.0.0.0이 모두 등록되어 있었는데 그중에서 10.0 버전이 선택된 것입니다.<br /> <br /> 그래도 이상하군요. 분명히 프로젝트 참조에서 12.0.0.0을 지정했는데, .NET 4.0 GAC 대신 .NET 2.0 GAC가 선택되면서 낮은 버전의 어셈블리가 로드된 것입니다. (이 부분은 나중에 한번 더 테스트를 해봐야겠습니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 어쨌든 현상이 그렇기 때문에 해결을 해야 하는데요. 이에 대해서는 다음의 글에 나와 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Breaking Changes to Database Engine Features in SQL Server 2012 ; <a target='tab' href='https://docs.microsoft.com/en-us/sql/database-engine/breaking-changes-to-database-engine-features-in-sql-server-2016'>https://docs.microsoft.com/en-us/sql/database-engine/breaking-changes-to-database-engine-features-in-sql-server-2016</a> </pre> <br /> 2가지 방법이 나오는데요. 하나는 .NET 4.5 부터 지원된다고 하는 SQL ConnectionString의 "Type System Version" 속성을 지정하는 것이 있고, 두번째는 app.config에 다음과 같은 바인딩 정보를 추가해 주는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <dependentAssembly> <assemblyIdentity name="Microsoft.SqlServer.Types" publicKeyToken="89845dcd8080cc91" culture="neutral" /> <bindingRedirect oldVersion="10.0.0.0-11.0.0.0" newVersion="12.0.0.0" /> </dependentAssembly> </pre> <br /> 그런데, 이것이 정말 최선일까요? ^^ 만약 SQL 서버 관리자가 데이터베이스를 (향후 미래의) SQL Server 2016으로 마이그레이션했다고 가정하면 그 때는 다시 13.0.0.0 버전의 Microsoft.SqlServer.Types.dll에 있는 SqlGeography 타입이 직렬화되어 응용 프로그램에 전달될 것이고 이로 인해 응용 프로그램은 어느 날 갑자기 동작하지 않게 될 것입니다. 물론, app.config에 bindingRedirect 정보를 변경하는 것으로 간단하게 해결은 할 수 있겠지만, 현실적으로 이런 오류는 잡기까지 시간이 걸립니다.<br /> <br /> 그래서 제가 추천하는 3번째 방법이 있습니다. 바로 dynamic 예약어를 사용하는 것!<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > object data = reader.GetValue("Location"); // not null if (data == null) { return; } dynamic geoData = data; double lat = geoData.Lat.Value; double long = geoData.Long.Value; </pre> <br /> 오~~~ 멋지죠! ^^ 예전 같으면 복잡하게 .NET Reflection으로 해결해야 하지만, 이제는 dynamic이 있어 좀 더 깔끔한 해결책이 나옵니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> Microsoft.SqlServer.Types.dll 어셈블리를 사용한 경우 다른 컴퓨터에 배포한다면 (SQL 서버를 설치하지 않으면 없기 때문에) 꼭 함께 배포해야 합니다. 아니면 해당 어셈블리에 포함된 타입을 사용한 메서드가 실행되는 순간 예외가 발생합니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.SqlServer.Types, Version=12.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The system cannot find the file specified. <br /> File name: 'Microsoft.SqlServer.Types, Version=12.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' <br /> at TesteLib.Place.GetByRadius(String center, String southWest, String northEast) <br /> at TesteWebApp.PlaceController.Get(String center, String southWest, String northEast) <br /> <br /> WRN: Assembly binding logging is turned OFF. <br /> To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. <br /> Note: There is some performance penalty associated with assembly bind failure logging. <br /> To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].<br /> </div><br /> <br /> 그런데, Web API에 실어서 서비스를 하니 웹 브라우저 종단에는 "500 (Internal Server Error)"가 떨어졌습니다. 에러 잡기 힘들군요. ^^ 암튼 Microsoft.SqlServer.Types 어셈블리를 사용하면 꼭 참조에 "Copy Local" 옵션을 "True"로 해주는 것이 좋겠습니다. ^^ <br /> <br /> <hr style='width: 50%' /><br /> <br /> Microsoft.SqlServer.Types.dll 어셈블리 배포만으로 안 끝나는군요. ^^ 이어서 다음과 같은 오류도 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.DllNotFoundException: Unable to load DLL 'SqlServerSpatial120.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E) at Microsoft.SqlServer.Types.GLNativeMethods.GeodeticPointDistance(Point p1, Point p2, EllipsoidParameters ep) at Microsoft.SqlServer.Types.SqlGeography.STDistance(SqlGeography other) at TesteLib.Place.GetByRadius(String center, String southWest, String northEast) at TesteWebApp.PlaceController.Get(String center, String southWest, String northEast) </pre> <br /> SqlServerSpatial120.dll은 Native 모듈인데 이 때문에 아쉽게도 x86/x64로 나뉘게 됩니다. 그래도 요즘엔 대개의 경우 x64로 작업하기 때문에 64비트 SqlServerSpatial120.dll을 프로젝트 파일에 추가한 다음 "<a target='tab' href='http://www.sysnet.pe.kr/2/0/1410'>Copy to Output Directory</a>"을 True로 설정해 주시면 됩니다.<br /> <br /> [1156] System.IO.FileLoadException: Could not load file or assembly 'Microsoft.SqlServer.Types, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) <br /> [1156] File name: 'Microsoft.SqlServer.Types, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' <br /> [1156] at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) <br /> [1156] at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks) <br /> [1156] at System.Reflection.Assembly.Load(AssemblyName assemblyRef) <br /> [1156] at System.Data.SqlClient.SqlConnection.ResolveTypeAssembly(AssemblyName asmRef, Boolean throwOnError) <br /> [1156] at System.TypeNameParser.ResolveAssembly(String asmName, Func`2 assemblyResolver, Boolean throwOnError, StackCrawlMark& stackMark) <br /> [1156] at System.TypeNameParser.ConstructType(Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark) <br /> [1156] at System.TypeNameParser.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError, Boolean ignoreCase, StackCrawlMark& stackMark) <br /> [1156] at System.Type.GetType(String typeName, Func`2 assemblyResolver, Func`4 typeResolver, Boolean throwOnError) <br /> [1156] at System.Data.SqlClient.SqlConnection.CheckGetExtendedUDTInfo(SqlMetaDataPriv metaData, Boolean fThrow) <br /> [1156] at System.Data.SqlClient.SqlDataReader.GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaData) <br /> [1156] at System.Data.SqlClient.SqlDataReader.GetValue(Int32 i) <br /> [1156] at SysnetLib.Dac.KnownPlaceDac.<.cctor>b__0(IDataReader reader, Dictionary`2 ordinal) <br /> [1156] at Sysnet.Framework.DacBase.FillToObjectList(String query, IDbDataParameter[] parameters, ReaderMapper functor, IList list, Boolean cacheOrdinalTable) <br /> [1156] at SysnetLib.Dac.KnownPlaceDac.GetByRadius(Int32 level, SqlGeography center, Double meters) <br /> [1156] at SysnetLib.Biz.KnownPlace_NTx.GetByRadius(Int32 level, String center, String southWest, String northEast) <br /> [1156] at SysnetWebApp.heyri.KnownPlaceController.Get(Int32 level, String center, String southWest, String northEast) <br /> <br /> <br /> 이에 대해 검색해 보면 다음의 글이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Microsoft.SqlServer.Types NuGet Package (Spatial on Azure) ; <a target='tab' href='http://blogs.msdn.com/b/adonet/archive/2013/12/09/microsoft-sqlserver-types-nuget-package-spatial-on-azure.aspx'>http://blogs.msdn.com/b/adonet/archive/2013/12/09/microsoft-sqlserver-types-nuget-package-spatial-on-azure.aspx</a> </pre> <br /> PM> Install-Package Microsoft.SqlServer.Types<br /> <br /> 아하~~~ 마이크로소프트에서도 Azure에서의 문제를 인식하고 NuGet 패키지를 배포하고 있었군요. ^^ 그래도 저는 그냥 간단하게 프로젝트 추가하고 bin 폴더에 내보내도록 구성을 해서 완료했습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, dynamic 예약어 사용시 다음과 같은 컴파일 오류가 발생한다면?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > One or more types required to compile a dynamic expression cannot be found. Are you missing a reference? Predefined type 'Microsoft.CSharp.RuntimeBinder.Binder' is not defined or imported </pre> <br /> 다음의 해결책을 참고하시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > One or more types required to compile a dynamic expression cannot be found. Are you missing references to Microsoft.CSharp.dll and System.Core.dll? ; <a target='tab' href='http://stackoverflow.com/questions/11725514/one-or-more-types-required-to-compile-a-dynamic-expression-cannot-be-found-are'>http://stackoverflow.com/questions/11725514/one-or-more-types-required-to-compile-a-dynamic-expression-cannot-be-found-are</a> </pre> <br /> 즉, "Microsoft.CSharp.dll" 어셈블리를 새롭게 참조하시면 됩니다.<br /> </p><br /> </div><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1519
(왼쪽의 숫자를 입력해야 합니다.)