티스토리 뷰
[.NET][TransactionScope][MSDTC] 여러 DB Server 작업 시, 트랜잭션 오류 관련 대체 예제(서버 내 DTC 이슈 등)
는세 2022. 8. 17. 11:50제목을 무엇으로 지어야할 지...
우선 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 속성이 초기화되지 않았습니다.
[ 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();
}
}
서론에 말씀드렸다시피 추천드리는 방법은 아니며, 저처럼 어쩔 수 없이 구글링을 박터지게 하셔야 하는 분들을 위한 로그성 글입니다. 더 좋은 방법이 있다면 댓글 남겨주시면 추가 수정하겠습니다.
※ 각 글에 대한 참고 링크는 본문 내에 삽입하였습니다.
'개발 > C#' 카테고리의 다른 글
[C#, JAVA] String Equals 비교 시, NULL 체크 간략화 ("Y".Equals() 사용) (0) | 2022.01.25 |
---|---|
[IIS] IIS 구성 시 오류 대응 방법 (0) | 2021.11.22 |
[C#] Enum Key 값으로 Value 찾기 (for, 반복문) (0) | 2021.09.27 |
- Total
- Today
- Yesterday