.NET Core 트랜잭션: SaveChanges, Database.BeginTransaction, TransactionScope 완벽 가이드

.NET Core에서 트랜잭션을 구현하는 방법은 크게 세 가지가 있습니다: EF Core의 SaveChanges, Database.BeginTransaction, 그리고 TransactionScope입니다. 각 방식은 사용 상황과 범위가 다르므로, 이를 정확히 이해하는 것이 중요합니다.

1. EF Core SaveChanges 자동 트랜잭션

SaveChangesDbContext에서 추적 중인 모든 엔티티의 변경 사항을 하나의 원자적(Atomic) 작업으로 데이터베이스에 제출합니다. 즉, 모든 변경이 성공하거나 모두 실패합니다.

예를 들어, 아래와 같이 Name 속성의 최대 길이를 2로 설정했다고 가정합니다.

public class StudentConfig : IEntityTypeConfiguration<Student>
{
    public void Configure(EntityTypeBuilder<Student> builder)
    {
        builder.ToTable("Students");
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).HasMaxLength(2);
    }
}

다음 코드에서 첫 번째 학생("小")은 조건을 만족하지만, 두 번째 학생("小明明")은 최대 길이(2)를 초과합니다.

string mysqlCon = "server=192.168.0.168;port=3306;database=UserDDD;pwd=123456;uid=root;";

AppliactionContext context = new AppliactionContext(
    new DbContextOptionsBuilder<AppliactionContext>()
    .UseMySql(mysqlCon, new MySqlServerVersion(new Version(8, 0, 27))).Options);

context.Students.Add(new Student("小"));
context.Students.Add(new Student("小明明"));
context.SaveChanges();

트랜잭션의 원자성 덕분에 SaveChanges 호출 시 두 데이터 모두 저장되지 않습니다. 첫 번째 데이터가 유효하더라도, 두 번째 데이터의 오류로 인해 전체 트랜잭션이 롤백되고 예외가 발생하며, 데이터베이스에는 어떤 새로운 데이터도 추가되지 않습니다.

2. Database.BeginTransaction: SQL 명령어와 함께 사용

EF Core 작업 중에 ExecuteSql과 같은 원시 SQL 명령어를 함께 사용해야 한다면 Database.BeginTransaction을 사용해야 합니다. 이를 통해 SQL 작업과 EF Core 작업을 동일한 트랜잭션 내에서 관리할 수 있습니다.

AppliactionContext context = new AppliactionContext(
    new DbContextOptionsBuilder<AppliactionContext>()
    .UseMySql(mysqlCon, new MySqlServerVersion(new Version(8, 0, 27))).Options);

using (IDbContextTransaction transaction = context.Database.BeginTransaction())
{
    try
    {
        context.Students.Add(new Student("小"));
        context.Database.ExecuteSql($"insert Students(Name) VALUES({'小明明'})");
        context.SaveChanges();
        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        throw;
    }
}

위 예제에서 SQL 명령어가 실행될 때 이미 예외가 발생하므로, context.SaveChanges()까지 도달하지 못하고 catch 블록으로 이동하여 트랜잭션이 롤백됩니다.

3. Database.UseTransaction: 여러 DbContext 간 트랜잭션 공유

동일한 데이터베이스에 대해 여러 DbContext 인스턴스를 사용하고, 이들 간에 하나의 트랜잭션을 공유해야 한다면 Database.UseTransaction을 사용합니다. 중요한 점은 모든 DbContext가 동일한 DbConnection을 공유해야 한다는 것입니다.

string mysqlCon = "server=192.168.0.168;port=3306;database=UserDDD;pwd=123456;uid=root;";

AppliactionContext appContext = new AppliactionContext(
    new DbContextOptionsBuilder<AppliactionContext>()
    .UseMySql(mysqlCon, new MySqlServerVersion(new Version(8, 0, 27))).Options);

DbConnection sharedConnection = appContext.Database.GetDbConnection();

IntegrationEventLogContext eventLogContext = new IntegrationEventLogContext(
    new DbContextOptionsBuilder<IntegrationEventLogContext>()
    .UseMySql(sharedConnection, new MySqlServerVersion(new Version(8, 0, 27))).Options);

using (var transaction = appContext.Database.BeginTransaction())
{
    try
    {
        eventLogContext.IntegrationEventLogs.Add(
            new IntegrationEventLogEntry(new EventBus.Events.IntegrationEvent(), Guid.NewGuid()));
        eventLogContext.Database.UseTransaction(transaction.GetDbTransaction(), transaction.TransactionId);
        eventLogContext.SaveChanges();

        appContext.Students.Add(new Student("小") { Id = 199 });
        appContext.SaveChanges();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        throw;
    }
}

위 코드는 appContext에서 시작된 트랜잭션을 eventLogContext에서도 연결하여 두 DbContext의 모든 변경 사항이 하나의 트랜잭션으로 처리되도록 합니다.

4. TransactionScope: 분산 트랜잭션 지원

TransactionScope는 단일 데이터베이스뿐만 아니라 여러 데이터베이스나 분산 리소스를 아우르는 트랜잭션을 처리할 때 사용됩니다. .NET Core에서도 사용할 수 있으며, 내부적으로 MSDTC(Microsoft Distributed Transaction Coordinator)를 활용할 수 있습니다.

using (TransactionScope scope = new TransactionScope())
{
    // 여러 데이터베이스 작업
    scope.Complete();
}

TransactionScope는 범위 내의 모든 작업이 성공해야 커밋되며, 하나라도 실패하면 전체가 롤백됩니다.

참고 자료

태그: EF Core SaveChanges Database.BeginTransaction TransactionScope IDbContextTransaction

6월 17일 01:12에 게시됨