티스토리 뷰

반응형

제목을 무엇으로 지어야할 지...

우선 MS DTC 관련하여 일반적인 방법을 기술하고, 발생했던 오류들과 대안책으로 사용한 방법을 작성하겠습니다. 아래 설명드린 방법들은, 저처럼 어쩔 수 없는 환경에서 작업하기 위해 구글링을 하셔야 하는 분들을 위해 로그성으로 남기는 글입니다. 혹시나 더 좋은 방법이 있다면 댓글로 설명 부탁드립니다.

 

우선 다중 DB 서버를 하나의 트랜잭션으로 묶어서 (=분산 트랜잭션 처리를 할 때) 처리하는 방식은 아래와 같습니다. 일반적으로 A 서버와 B 서버가 모두 TRUE 인 경우 => COMMIT / 하나라도 FALSE 인 경우 ROLLBACK 으로 처리할 때 많이 사용됩니다.

 

[ Q - 1 ] 기본 트랜잭션 관리자 오류

        using (TransactionScope scope = new TransactionScope())
        {
            using (Schema1Entities db1 = new Schema1Entities())
            {
                // ....
            }
            using (Schema2Entities db2 = new Schema2Entities())
            {
                // ....
            }
            scope.Complete();
        }

 

하지만, [MS DTC - 기본 트랜잭션 관리자와의 통신에 실패했습니다. ] [The partner transaction manager has disabled its support for remote/network transactions.] 와 같은 오류가 발생 할 수 있습니다.

그런 경우, 서버의 DTC 설정을 확인해보아야 합니다.

 

[ A - 1 ] MSDTC 설정 관련

참고 : https://blog.naver.com/rokag3/220545234382

 

 

[ Q - 2 ] MSDTC 설정 이후 오류

그러나, MSDTC 를 켜고, 차단된 방화벽이 없음에도 오류가 발생할 수 있습니다.

그럴 땐 아래와 같이 소스를 변경하여 해결했다는 코멘트가 있습니다.

 

 오류/수정 관련 참고 : https://stackoverflow.com/questions/38235541/multiple-databases-multiple-dbcontexts-in-one-transaction-using-transactionsco

 

[ A - 2 ] 소스 변경

using (TransactionScope scope = new TransactionScope())
{

    using (conn1 = new SqlConnection(connString1))
    {
        conn1.Open();
        // ....
        
        // If we reach here, means that above statements succeded.
        using (conn2 = new SqlConnection(connString2))
        {
            conn2.Open();
            // ....

        }
    }

    scope.Complete();
}

 

 

>>>>>>>>>>>>>>>>>>>>>> 여기서부터 본론입니다.

 

[ Q - 3 ] TransactionScope를 사용 할 수 없는 경우

그런데 저의 경우는, 해당 서버들의 관리 포인트가 본인이 아니고 신규 서버도 아닌 기존에 사용하던 서버였기 떄문에 MSDTC 설정에는 다소 무리가 있었습니다. 

 

이하 서술할 A-3 는 저와 같은 분들이 사용하시면 되겠습니다.

 

[ A - 3 ] 예제 소스

아래와 같이 각각 커넥션 오픈/실행을 scope 로 묶지않고, 별도 처리하는 것으로 해결하였습니다.

 

A-3-1) using 문 사용

DBHelper conn1 = null;
DBHelper conn2 = null;


// 1. 시작
using (conn1 = new DBHelper("connString1"))
using (conn2 = new DBHelper("connString2")) {
	conn1.BeginTransaction();
	conn2.BeginTransaction();

	// 2. 실행
	var result1 = conn1.SaveAction();
	var result2 = conn2.SaveAction();

	// conn1.SaveAction2();
	// conn2.SaveAction2();

	// 3. 커밋 or 롤백
	if (result1 == result2) {
		conn1.CommitTransaction();
		conn2.CommitTransaction();
	}
}

 

* try ~ catch 구문

using문 내에 try 구문을 사용하는 것과, using 문 밖에서 try 구문을 사용하는 것에 대한 논의를 보았는데,

이 경우 취향 차이로 봐주시면 될 것 같습니다.

 

참고 : https://stackoverflow.com/questions/4590490/try-catch-using-right-syntax

 

위 논의를 요약하자면,

using (var obj= new Class("Name")) 의 생성/초기화 구문에서 오류가 발생할 여지가 있다면 try 문을 외부에, 아니라면 내부에 사용해도 될 것 같습니다.

 

 

 

A-3-2) try ~ finally 사용
아래 소스는 using 문을 사용하지 않기 때문에, 별도 dispose 처리를 해주어야 합니다.

DBHelper conn1 = null;
DBHelper conn2 = null;


try {
	// 1. 시작
	conn1 = new DBHelper("connString1");
	conn1.BeginTransaction();

	conn2 = new DBHelper("connString2");
	conn2.BeginTransaction();

	// 2. 실행
	var result1 = conn1.SaveAction();
	var result2 = conn2.SaveAction();

	// conn1.SaveAction2();
	// conn2.SaveAction2();

	// 3. 커밋 or 롤백
	if (result1 == result2) {
		if (conn1 != null) conn1.CommitTransaction();
		if (conn2 != null) conn2.CommitTransaction();
	}
} catch (e) {
	//..... 
	if (conn1 != null) conn1.RollBackTransaction();
	if (conn2 != null) conn2.RollBackTransaction();
}

// 4. 해제
finally {
	if (conn1 != null) conn1.Dispose();
	if (conn2 != null) conn2.Dispose();
}

참고 : https://mentum.tistory.com/571

 

 

[ Q - 4 ] Dapper 와 DDL Action 

그런데 액션이 각 DB 별 1개 씩인 경우에는 큰 문제가 안되는데,

하나의 트랜잭션으로 묶여야 하는 Action이 여러개인 경우 (위 예제 소스의 SaveAction2()가 필요한 경우)에는 현재 transaction 을 계속 유지해 줘야할 필요가 있습니다.

 

Dapper.dll 을 보시면, 동일 메소드가 오버로딩 되어 있습니다. 일반적으로 conenction 과 command 만 입력하는 메소드를 사용하셨다면, 이번에는 transaction 을 파라미터로 받는 메소드를 사용하면 됩니다.

=> Action 내부에서 using 문을 사용했거나, transaction 이 없을 시에는 첫번째 실행에서 종료된 것으로 간주하여, 다음 Action 실행 시에 오류가 발생합니다.

 

오류 : 명령에 할당된 연결이 보류 중인 로컬 트랜잭션에 연결되어 있는 경우 {0}을(를) 사용하려면 트랜잭션이 있어야 합니다. 명령의 Transaction 속성이 초기화되지 않았습니다.

 

Dapper.dll

[ A - 4 ] 예제 소스

public object ExceScalar (DbConnection conn, string sql, object param, ... , bool isTran = false) {

	if (IsTran) {
		return conn.ExecuteScalar(sql, param, ..... , transaction: trans);
	} else {
		return conn.ExecuteScalar(sql, param ....... );
	}
}

 

기존 트랜잭션을 계속 유지할 경우와 아닌 경우 (신규 생성되도록) 를 분기 처리해주는 소스로 해결이 가능합니다.

 

 

만약, dapperHelper 를 사용하는 경우, 신규 트랜잭션 시에만 기존과 같이 using 을 그대로 사용 할거라면 아래처럼 사용할 수 있습니다. (아래 예제소스 내의 SaveAction() 내부라고 보면 됨)

public object SaveAction (DbConnection conn, dapper = null, ... ) {

	if (trans) {
		result = ExecuteScalar(sql, param, ..... , dapper);
	} else {
    	        using (var dapper = new DBHelper()) {
		   result = ExecuteScalar(sql, param, dapper ....... );
		}
	}
    
    return result;
}

 

참고 : https://stackoverflow.com/questions/4389686/c-sharp-conditional-using-block-statement

 

 

A-3 번 소스를 변형하면 아래와 같이 전달이 가능합니다.

DBHelper conn1 = null;
DBHelper conn2 = null;


// 1. 시작
using (conn1 = new DBHelper("connString1"))
using (conn2 = new DBHelper("connString2")) {
	IDbTransaction trans = conn1.BeginTransaction();
	IDbTransaction trans2 = conn2.BeginTransaction();

	// 2. 실행
	var result1 = conn1.SaveAction(transaction:trans);
	var result2 = conn2.SaveAction(transaction:trans2);

	var result3 = conn1.SaveAction2(transaction:trans);
	var result4 = conn2.SaveAction2(transaction:trans2);

	// 3. 커밋 or 롤백
	if (result1 == result2 && result3 == result4) {
		conn1.CommitTransaction();
		conn2.CommitTransaction();
	} else {
  		conn1.RollbackTransaction();
		conn2.RollbackTransaction();  
    }
}

 

 

서론에 말씀드렸다시피 추천드리는 방법은 아니며,  저처럼 어쩔 수 없이 구글링을 박터지게 하셔야 하는 분들을 위한 로그성 글입니다. 더 좋은 방법이 있다면 댓글 남겨주시면 추가 수정하겠습니다.

 

 

※ 각 글에 대한 참고 링크는 본문 내에 삽입하였습니다.

반응형
댓글