드디어 Strored Procedure(이하 SP)를 사용하는 프로젝트에 참여하고 있습니다. 아쉽게(?) 실질적으로 SP를 호출하고 결과를 받아서 UI에 보여주는 작업은 아닙니다.

SP로 실질적인 프로젝트를 하지 않아서 전부터 궁금하던 사실이 있었습니다. 'SP를 사용하면 굳이 서버쪽에 Layer가 필요한 것일까?' 란 의문이였습니다. 프로젝트의 규모가 작고 하나의 SP에서 대부분의 로직을 처리할 수 있다면 굳이 필요하지 않아보입니다만 또 하나의 의문은 SP로 작성된 구문 사이의 트랜잭션의 범위에 대한 의문이였습니다. 일반적으로 Biz Layer에서 트랜잭션을 발생시켜 주고 Dac Layer에서 여러 SP를 호출해줄 경우 Biz에서 발생하는 트랜잭션과 SP내의 트랜재션과의 관계와 그 결과에 대한 의문이였습니다. 

조금 더 이해를 돕기 위해서 Biz Layer에서는 TransactionScope 클래스를 사용하였다고 가정하고 SP내에서는 ADO.NET 트랜잭션 메소드(BeginTransaction/Commit/Rollback)를 사용하였다는 가정입니다. TransactionScope는 System.Transaction 네임스페이스 하위의 클래스이며 System.Transaction 네임스페이스에서 관리하는 트랜잭션은 항상 LTM(Lightweight Transaction Manager)을 통해서 관리된다고 합니다. 따라서 해당 테스트는 LTM과 ADO.NET 트랜잭션 메소드 사이의 관계에 대해서 설명하자고 하는 글입니다.

LTM에 대해서는 아래의 사이트를 참고하세요.

http://www.simpleisbest.net/articles/996.aspx


그럼 이제 테스트를 해보도록 하겠습니다. (참고 : 테스트 DB는 SQL Server 2008(R2는 아님)입니다.)

DB 데이터 베이스는 ID(identity), Name 가지고 있는 테이블입니다.

이제 SP를 DB에 만들어 보겠습니다. SP의 내용은 아래와 같습니다.

CREATE PROCEDURE EFSP

 

@name nvarchar(Max),

@txOutput int OUTPUT

 

AS

 

BEGIN TRAN

 

insert [dbo].[Entity1Set]([Name])

values (@name)

select [Id]

from [dbo].[Entity1Set]

where @@ROWCOUNT > 0 and [Id] = scope_identity()

 

-- 위의 insert 구문 2 추가

 

SET @txOutput = @@TRANCOUNT


COMMIT TRAN

GO 

Entity Framework에서 해당 SP Function으로 Import 있습니다. System.Data.SqlClient 사용하셔도 됩니다. 여기서 @@TRANCOUNT로 발생하는 트랜잭션의 개수를 SP의 output 파라미터에 대입해주는 것을 확인하실 있습니다. ADO.NET 트랜잭션 메소드는 SQL Profiler SQLTransaction으로 표현이 되는데 SQLTransaction 해당 테스트와 상관없이 너무나 자주 발생하여 트랜잭션이 발생하는 개수를 파악하기 위한 방법으로 사용되었습니다.

그러면 이제 단순히 SP 실행해보도록 하겠습니다.


=> 위에 말했던 대로 SP내부에서 사용한 ADO.NET 트랜잭션 메소드는 SQLTransaction으로 표시가 되며 EventSubClass 열로 트랜잭션이 어느 부분부터 시작되며 어느 부분에서 종료가 되는지 아실 있습니다. ObjectName 통해서는 사용자가 발생한 트랜잭션인지 SQL Server내부에서 발생하는 트랜잭션인지를 구분할 있습니다.

 

그러면 이제 TransactionScope 클래스와 함께 SP 실행해보도록 하겠습니다.


=> TransactionScope 클래스를 사용하였기 때문에 TM(LTM) 관여하는 것을 보실 있으며 DBM_INIT이라는 내부 Object 의해서 무엇인지는 정확하게 없지만 SQLTransaction 발생한 것도 있습니다. 여기서 중요한 것은 바로 sp_reset_connection 함수입니다. 함수는 커넥션 풀링이 되었다는 의미입니다. 이상합니다. 하나의 SP만을 실행준 것인데 커넥션 풀링이 필요한 것일까요?

 

TransactionScope 클래스를 사용한 구문을 보도록 하겠습니다.

using (TransactionScope scope = new TransactionScope())

{

    // 하나의 SP실행

 

    scope.Complete();

} 

=> 앞에서 말했듯이 System.Transaction 네임스페이스의 트랜잭션은 TM 관리하였습니다. 단순히 SP만을 실행해줄 경우에는 SQLTransaction에서 commit 이후에 커넥션이 닫혔습니다. 그러나 TransactionScope안에서 SP 실행할 경우에 SQLTransaction commit되기 전에 커넥션이 먼저 닫히게 됩니다. 이것으로 보아 TransactionScope 사용하게 되면서 ADO.NET 트랜잭션 메소드가 TM 관리하는 트랜잭션으로 관리된다는 것을 있습니다. TM Commit 되기 전에 커넥션이 닫혔기 때문에 TM에서는 커넥션 풀링을 통해서 다시 커넥션을 맺고 내부의 SQLTransaction 발생한 이후에 SQLTransaction commit해주며 이후에 TM Commit 되고 마지막으로 커넥션을 닫아주는 것입니다.

가지 테스트를 통해서 TransactonScope 옵션(TransactionScopeOption) 따라서 ADO.NET 트랜잭션 메소드가 참여하게 된다는 사실을 확인할 있었습니다.

이렇게 하여 LTM SQLTransaction 관계에 대해서 대부분(?!) 파악이 되었습니다. 가지 의문점은 SP안에서는 트랜잭션의 Scope 범위(TransactionScopeOption) 지정하는 부분은 없어보이지만 IsolationLevel 대해서는 지정을 있습니다. 또한 TransactionScope에서도 IsolationLevel 대해서 지정할 있습니다. 그래서 IsolationLevel 따른 LTM SQLTransaction 관계가 어떻게 될지도 살짝 궁금해집니다. 하지만 실질적으로 이러한 것을 프로젝트에서 정확하게 알고 사용하는 개발자가 얼마나 될지도 의문이며 IsolationLevel 트랜잭션 사이의 잠금(lock) 대한 것이기 때문에 성능보다는 안전성을 중요시하는 우리 나라의 대부분의 프로젝트에서는 ReadCommitted 사용하는 것이 개발자의 정신건강에 이로워 보입니다. ^^

Posted by resisa
,
솔루션 파일의 스키마에 대해서 알아보도록 하겠습니다.


위와 같이 솔루션을 만들고 Project Dependencies에서 ClassLibrary1 프로젝트에서 ClassLibrary2을 참조해줍니다. 그리고 솔루션 파일을 열어보도록 하겠습니다.

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{F7C941E2-B6DA-4652-957A-923EB41C155F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary2", "ClassLibrary2\ClassLibrary2.csproj", "{3FA19692-3C13-427F-9662-57BBC612915C}"
ProjectSection(ProjectDependencies) = postProject
{F7C941E2-B6DA-4652-957A-923EB41C155F} = {F7C941E2-B6DA-4652-957A-923EB41C155F}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionFolder", "{7FB05BE4-9794-4E4C-92A8-1E4FFC56A7A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F7C941E2-B6DA-4652-957A-923EB41C155F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7C941E2-B6DA-4652-957A-923EB41C155F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7C941E2-B6DA-4652-957A-923EB41C155F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7C941E2-B6DA-4652-957A-923EB41C155F}.Release|Any CPU.Build.0 = Release|Any CPU
{3FA19692-3C13-427F-9662-57BBC612915C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FA19692-3C13-427F-9662-57BBC612915C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FA19692-3C13-427F-9662-57BBC612915C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FA19692-3C13-427F-9662-57BBC612915C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F7C941E2-B6DA-4652-957A-923EB41C155F} = {7FB05BE4-9794-4E4C-92A8-1E4FFC56A7A2}
EndGlobalSection
EndGlobal
노란색에 해당하는 항목에 대해서 정리하면 아래와 같습니다.

1. Project ~ EndProject
  • 솔루션 파일에 추가할 수 있는 프로젝트를 의미합니다.
  • 형식은 '추가하는 항목의 Guid = 프로젝트명, 프로젝트 파일 위치, 프로젝트 Guid' 입니다.
  • 여기서 추가하는 항목의 Guid는 아래와 같습니다.
    • 솔루션 폴더 : "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"
    • 프로젝트 :  "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"
  • 솔루션 파일에 추가할 수 있는 항목은 솔루션 폴더와 프로젝트로 구분 할 수 있습니다.
  • 여기서 프로젝트란 모든 형식(Console, Winform, WPF, WebForm)의 프로젝트를 말합니다.

2. ProjectSection(ProjectDependencies) = postProject ~ EndProjectSection
  • 솔루션 파일이나 프로젝트 파일에서 오른쪽 버튼을 누른 후에 'Project Dependencies'속성에서 수동으로 프로젝트에 대한 의존관계에 대한 내용입니다.
  • 형식은 '참조되는 프로젝트의 Guid = 참조되는 프로젝트의 Guid' 입니다.
  • 프로젝트 참조에 대한 정보는 솔루션 파일에는 존재하지 않습니다.

3. GlobalSection(SolutionConfigurationPlatforms, ProjectConfigurationPlatforms) = preSolution ~ EndGlobalSection
이 부분은 그림으로 설명해 보도록 하겠습니다.
  • SolutionConfigurationPlatforms에 해당하는 내용은 파란색 박스입니다.
  • ProjectConfigurationPlatforms에 해당하는 내용은 빨간색 박스입니다.
  • 솔루션의 Configuration를 변경하면 해당 솔루션에 포함된 모든 프로젝트의 Configuration이 변경되며 특정 프로젝트만 Configuration를 변경하고 싶을 경우에 빨간색 박스의 내용을 변경하면 됩니다.
  • Build의 체크 박스 표시 여부에 따라서 Debug|Any CPU.Build.0 가 포함된 행이 솔루션 파일에 포함되는지가 결정되며 당연히 체크 표시가 없다면 해당 프로젝트의 빌드는 Skip 됩니다.

4. GlobalSection(NestedProjects) = preSolution ~ EnbGlobalSection
  • 이 구역이 바로 솔루션 폴더와 해당 솔루션 폴더에 포함되는 프로젝트에 대한 내용입니다.
  • 형식은 '프로젝트 Guid = 솔루션 폴더 Guid' 입니다.
다음에는(완전 미정 ㅋ) 프로젝트 파일에 대한 스키마에 대해서 알아보도록 하겠습니다. ^^;
Posted by resisa
,

프로젝트 개수가 많아지다 보면 솔루션 파일을 열어 Visual Studio상에서 빌드를 하는 것보다는 MSBuild를 사용하여 빌드를 하는 것이 현명한 선택일 것입니다. MSBuild를 사용할 경우에 단순히 솔루션 파일을 지정해주면 빌드가 정상적으로 완료되지 않습니다. 바로 프로젝트 간에 참조 문제 때문입니다. MSBuild에서는 파라미터로 솔루션 파일을 지정해주면 해당 솔루션 파일에 정의된 프로젝트 순서대로 빌드 순서를 정해버립니다.
아래의 사이트는 이러한 문제점을 해결해줍니다.
http://serialize.wordpress.com/2009/12/16/msbuild-task/

처음 MSBuild를 접하시는 분은 위의 사이트를 눈을 크게 뜨고 보아도 실행이 잘되지 않을 것입니다.(제가 그랬습니다. ㅋ)
<Project DefaultTargets="BuildProjects" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

  <Target Name="BuildProjects">

 

    <GetProjecsInOrder Solution="$(MSBuildProjectDirectory)\MySolution.sln">

      <Output ItemName="ProjectFiles" TaskParameter="Output"/>

    </GetProjecsInOrder>

   

    <Message Text="%(ProjectFiles.FullPath)"/>

   

    <MSBuild Projects="%(ProjectFiles.FullPath)"

      Targets="Build"

      Properties="Configuration=Release"/>

  </Target>

 

  <UsingTask AssemblyFile="MSBuildTasks.dll" TaskName="GetProjecsInOrder" />

 

</Project>

=> MSBuildTasks.dll이라는 사용하는 구문을 몰라서였습니다. -_- MSBuildTasks.dll소스를 열어보면 Task라는 클래스(빌드가 되는 단위 ITaskItem이 프로젝트와 매칭되는 빌드의 단위)를 상속받아서 구현하고 있는 것을 볼 수 있습니다. 솔루션 파일를 정규식으로 필터링을 해서 프로젝트의 정보를 얻습니다. 이 프로젝트 정보로 해당 프로젝트 파일를 열어 프로젝트 파일에 있는 프로젝트 참조에 대한 정보를 정규식을 통해서 얻습니다. 이렇게 모든 정보를 얻고 나서 Recusive한 알고리즘(?!)을 통해서 빌드되는 프로젝트의 순서를 정해주게 됩니다. 위의 Message 앨리먼트를 사용하게 되면 이러한 빌드 순서에 대해서 화면에 찍어주게 됩니다.
한 가지 아쉬운 점이라면 바로 프로젝트 참조에 대해서만 빌드 순서를 정해준다는 것입니다. 물론 일반적인 솔루션이라면 당연히 프로젝트 참조 대해서만 빌드 순서를 정해주면 되지만 모든지 예외는 존재하는 것 같습니다. 그닥 어렵지는 않습니다. 이전 포스트에서 소개해드린 솔루션 파일에 어셈블리 참조에 대한 Project Dependencies 정보가 사전에 필요합니다. 그리고 이 정보를 사용하여 어셈블리 참조에 대한 정보를 얻어 위에서 사용한 Recursive한 루틴을 태우면 됩니다. 프로젝트 참조와 어셈블리 참조 모두를 적용하면 어떻게 될지도 궁금하네요 ^^

<2010-05-12>
MSBuild에서 솔루션 파일안에 프로젝트 파일의 정보 순서대로 빌드를 해준다고 생각했었는데 다시 해보니 Project Dependency에 의해서 빌드 순서를 정해서 빌드를 합니다. 위의 사이트의 해결방법이 그렇다면 쓸모없는 방법이라는 결론입니다. 하지만 700정도 되는 프로젝트를 단순히 솔루션 파일에 의해서 MSBuild를 하면 제대로 빌드가 되지 않습니다. 무엇인가 정확하게 알지 못하는 부분이 있는 것 같아 정확하게 원인이 파악되면 다시 글을 수정하도록 하겠습니다.

<2010-05-23>
Project Dependency를 나타내는 방법은 아래의 두 가지입니다.
1. 프로젝트 참조를 한다.
2. Project Dependencies 메뉴에서 수동으로 Dependency를 정의한다.
2번 째 방법은 솔루션 파일에 ProjectSection(ProjectDependencies)에서 찾아볼 수 있지만 1번 째 방법은 그렇지 않습니다.

MSBuild시에 솔루션 파일을 지정하고 빌드할 경우 테스트해 본 결과 1번째 2번째 방법 모두 빌드 오더를 MSBuild 자체적으로 계산하여 정확하게 빌드를 해주는 것을 확인하였습니다. 위의 사이트의 해결방법이 쓸모없는 방법인 것으로 판단됩니다. --;
Posted by resisa
,