Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

TransactionScope에 사용자 정의 트랜잭션을 참여시키는 방법

오호~~~ 12월 6일 주간 닷넷에,

주간닷넷 2016년 12월 6일
; https://learn.microsoft.com/en-us/archive/blogs/eva/주간닷넷-2016년-12월-6일

재미난 글이 나왔군요. ^^

Let .NET framework care about transactions handling for you by implementing IEnlistmentNotification
; https://blog.goyello.com/2016/11/30/let-net-framework-care-about-transactions-handling-for-you-by-implementing-ienlistmentnotification/

바로 TransactionScope에 간이 사용자 정의 트랜잭션을 끼워 넣을 수 있는 IEnlistmentNotification 인터페이스에 대한 소개입니다.

IEnlistmentNotification
; https://learn.microsoft.com/en-us/dotnet/api/system.transactions.ienlistmentnotification

방법이 생각보다 간단합니다. 그냥 다음과 같이 인터페이스를 구현해 주고, 적당하게 트랜잭션 단계마다 필요한 작업만 해주면 됩니다.

using System;
using System.Transactions;

class TxObject : IEnlistmentNotification
{
    public TxObject(object objValue)
    {
        if (Transaction.Current != null)
        {
            Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
        }
    }

    public void Commit(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared(); // or, preparingEnlistment.ForceRollback();
    }

    public void Rollback(Enlistment enlistment)
    {
        enlistment.Done();
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var tx = new TransactionScope())
        {
            TxObject obj = new TxObject(null);

            tx.Complete();
        }
    }
}

예를 하나 들어볼까요? 가령, 웹 사이트에서 게시판 구현할 때 DB에 게시글 정보를 저장한 후 첨부 파일도 처리할 때가 종종 있습니다. 이럴 때, DB 저장과 첨부 파일 저장을 하나의 트랜잭션으로 묶는 것을 고려할 수 있습니다. 물론, 정확하게 구현하려면 이런 경우 비스타 운영체제부터 지원하는 TxF(Transactional NTFS) 기능을 이용해야 하는데,

Transactional NTFS (TxF) .NET
; https://txfnet.codeplex.com/

그냥 가볍게 다음과 같은 식으로 구현하는 것도 가능합니다.

using System;
using System.IO;
using System.Transactions;

class TxFile : IEnlistmentNotification
{
    string _savedPath;
    string _orgFilePath;
    string _contents;

    public TxFile(string filePath, string contents)
    {
        _orgFilePath = filePath;
        _savedPath = filePath;
        _contents = contents;

        if (Transaction.Current != null)
        {
            _savedPath = string.Format("{0}.in_tx", filePath);
            Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
        }
        else
        {
            File.WriteAllText(_orgFilePath, _contents);
        }
    }

    public void Commit(Enlistment enlistment)
    {
        File.Move(_savedPath, _orgFilePath);
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared();
    }

    public void Rollback(Enlistment enlistment)
    {
        if (File.Exists(_savedPath))
        {
            File.Delete(_savedPath);
        }

        enlistment.Done();
    }

    public void Save()
    {
        File.WriteAllText(_savedPath, _contents);
    }
}

class Program
{
    static void Main(string[] args)
    {
        string filePath = "c:\\temp\\test.txt";

        using (var tx = new TransactionScope())
        {
            // ... [트랜잭션에 참여하는 SQL 코드 생략] ...

            TxFile file = new TxFile(filePath, "test is good");
            file.Save();

            // throw new ApplicationException("Exception occurred!");

            tx.Complete();
        }
    }
}

위의 코드를 실행해 보면, tx.Complete(); 시점에 정상적으로 파일이 생성되는 반면 중간에 "// throw ..." 주석을 해제하거나 tx.Complete() 호출을 제거하면 Rollback 메서드의 호출로 인해 파일이 삭제가 됩니다.

물론, TxF(Transactional NTFS)만큼 정교한 트랜잭션을 구현하려면 트랜잭션 로그 파일도 기록하는 등의 작업도 해야 하지만 그럴 거면 차라리 TxF를 사용하는 것이 더 낫습니다.



그러고 보니, 예전에 Python에서 __enter__, __exit__ 기능을 활용한 with 문 구현에서 "값 변경에 따른 트랜잭션 구현"을 설명한 적이 있는데요.

Python의 zip과 with 문 context를 C#과 비교하면.
; https://www.sysnet.pe.kr/2/0/1371

이것도 다음과 같은 식으로 유사하게 구현해 볼 수 있습니다.

public class ValueTransaction<T> : IEnlistmentNotification
{
    T _orgValue;
    T _value;
    public T Value { get { return _value; } }

    public ValueTransaction(T value)
    {
        _orgValue = value;
        _value = value;

        if (Transaction.Current != null)
        {
            Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
        }
    }

    public void SetValue(T newValue)
    {
        _value = newValue;
    }

    public void Commit(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
    }

    public void Prepare(PreparingEnlistment preparingEnlistment)
    {
        preparingEnlistment.Prepared(); // or preparingEnlistment.ForceRollback();
    }

    public void Rollback(Enlistment enlistment)
    {
        _value = _orgValue;
        enlistment.Done();
    }
}

class Program
{
    static void Main(string[] args)
    {
        ValueTransaction<int> myValue = null;

        try
        {
            using (var tx = new TransactionScope())
            {
                myValue = new ValueTransaction<int>(6);
                myValue.SetValue(5);

                // throw new ApplicationException("TEST");

                tx.Complete();
            }
        } catch { }

        if (myValue != null)
        {
            Console.WriteLine(myValue.Value);
        }
    }
}

뭐,,, 대충 비슷한가요? ^^

(첨부 파일은 본문의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 5/31/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2017-01-09 03시05분
정성태
2017-01-09 03시36분
정성태

1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13413정성태9/14/20233777닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
13412정성태9/14/20237084닷넷: 2142. C# 12 - 인라인 배열(Inline Arrays) [1]
13411정성태9/12/20233574Windows: 252. 권한 상승 전/후 따로 관리되는 공유 네트워크 드라이브 정보
13410정성태9/11/20235112닷넷: 2141. C# 12 - Interceptor (컴파일 시에 메서드 호출 재작성) [1]
13409정성태9/8/20233938닷넷: 2140. C# - Win32 API를 이용한 모니터 전원 끄기
13408정성태9/5/20233916Windows: 251. 임의로 만든 EXE 파일을 포함한 ZIP 파일의 압축을 해제할 때 Windows Defender에 의해 삭제되는 경우
13407정성태9/4/20233631닷넷: 2139. C# - ParallelEnumerable을 이용한 IEnumerable에 대한 병렬 처리
13406정성태9/4/20233617VS.NET IDE: 186. Visual Studio Community 버전의 라이선스
13405정성태9/3/20234024닷넷: 2138. C# - async 메서드 호출 원칙
13404정성태8/29/20233599오류 유형: 876. Windows - 키보드의 등호(=, Equals sign) 키가 눌리지 않는 경우
13403정성태8/21/20233402오류 유형: 875. The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EB3E94ADBE1229CF
13402정성태8/20/20233503닷넷: 2137. ILSpy의 nuget 라이브러리 버전 - ICSharpCode.Decompiler
13401정성태8/19/20233756닷넷: 2136. .NET 5+ 환경에서 P/Invoke의 성능을 높이기 위한 SuppressGCTransition 특성 [1]
13400정성태8/10/20233594오류 유형: 874. 파이썬 - pymssql을 윈도우 환경에서 설치 불가
13399정성태8/9/20233514닷넷: 2135. C# - 지역 변수로 이해하는 메서드 매개변수의 값/참조 전달
13398정성태8/3/20234368스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
13397정성태7/23/20233851닷넷: 2134. C# - 문자열 연결 시 string.Create를 이용한 GC 할당 최소화
13396정성태7/22/20233629스크립트: 54. 파이썬 pystack 소개 - 메모리 덤프로부터 콜 스택 열거
13395정성태7/20/20233500개발 환경 구성: 685. 로컬에서 개발 중인 ASP.NET Core/5+ 웹 사이트에 대해 localhost 이외의 호스트 이름으로 접근하는 방법
13394정성태7/16/20233475오류 유형: 873. Oracle.ManagedDataAccess.Client - 쿼리 수행 시 System.InvalidOperationException
13393정성태7/16/20233653닷넷: 2133. C# - Oracle 데이터베이스의 Sleep 쿼리 실행하는 방법
13392정성태7/16/20233547오류 유형: 872. Oracle - ORA-01031: insufficient privileges
13391정성태7/14/20233565닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요?
13390정성태7/12/20233505스크립트: 53. 파이썬 - localhost 호출 시의 hang 현상
13389정성태7/5/20233552개발 환경 구성: 684. IIS Express로 호스팅하는 웹을 WSL 환경에서 접근하는 방법
13388정성태7/3/20233693오류 유형: 871. 윈도우 탐색기에서 열리지 않는 zip 파일 - The Compressed (zipped) Folder '[...].zip' is invalid. [1]파일 다운로드1
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...