트랜잭션을 사용하면 여러 데이터베이스 작업을 원자성 방식으로 처리할 수 있습니다. 트랜잭션이 커밋되면 모든 작업이 데이터베이스에 성공적으로 적용됩니다. 트랜잭션이 롤백되면 데이터베이스에 어떤 작업도 적용되지 않습니다.
팁 (조언)
GitHub에서 이 문서의 샘플 볼 수 있습니다.
기본 트랜잭션 동작
기본적으로 데이터베이스 공급자가 트랜잭션을 지원하는 경우 SaveChanges
대한 단일 호출의 모든 변경 내용이 트랜잭션에 적용됩니다. 변경 내용이 실패하면 트랜잭션이 롤백되고 데이터베이스에 변경 내용이 적용되지 않습니다. 즉, SaveChanges
완전히 성공하거나 오류가 발생할 경우 데이터베이스를 수정되지 않은 상태로 둡니다.
대부분의 애플리케이션에서 이 기본 동작은 충분합니다. 애플리케이션 요구 사항이 필요하다고 판단되는 경우에만 트랜잭션을 수동으로 제어해야 합니다.
트랜잭션 제어
DbContext.Database
API를 사용하여 트랜잭션을 시작, 커밋 및 롤백할 수 있습니다. 다음 예제에서는 두 SaveChanges
작업과 LINQ 쿼리가 단일 트랜잭션에서 실행되는 것을 보여 줍니다.
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
await context.SaveChangesAsync();
var blogs = await context.Blogs
.OrderBy(b => b.Url)
.ToListAsync();
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
모든 관계형 데이터베이스 공급자는 트랜잭션을 지원하지만 트랜잭션 API가 호출되면 다른 공급자 형식이 throw되거나 no-op 수 있습니다.
비고
이러한 방식으로 트랜잭션을 수동으로 제어하는 것은 암시적으로 호출된 재시도 실행 전략과 호환되지 않습니다. 자세한 내용은 연결 복원력 참조하세요.
세이브 포인트
SaveChanges
호출되고 컨텍스트에서 트랜잭션이 이미 진행 중인 경우 EF는 데이터를 저장하기 전에 저장점 자동으로 만듭니다. 저장점은 오류가 발생하거나 다른 이유로 인해 나중에 롤백될 수 있는 데이터베이스 트랜잭션 내의 지점입니다.
SaveChanges
오류가 발생하면 트랜잭션을 저장점으로 자동으로 롤백하여 트랜잭션이 시작되지 않은 것과 동일한 상태로 남습니다. 이렇게 하면 특히 낙관적 동시성 문제가 발생할 때 문제를 수정하고 저장을 다시 시도할 수 있습니다.
경고
저장점은 SQL Server의 MARS(다중 활성 결과 집합)와 호환되지 않습니다. MARS가 적극적으로 사용되지 않더라도 연결에서 MARS를 사용하도록 설정하면 EF에서 저장점이 생성되지 않습니다. SaveChanges 중에 오류가 발생하면 트랜잭션이 알 수 없는 상태로 남을 수 있습니다.
트랜잭션과 마찬가지로 저장점을 수동으로 관리할 수도 있습니다. 다음 예제에서는 트랜잭션 내에서 저장점을 만들고 실패할 때 다시 롤백합니다.
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
await context.SaveChangesAsync();
await transaction.CreateSavepointAsync("BeforeMoreBlogs");
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception)
{
// If a failure occurred, we rollback to the savepoint and can continue the transaction
await transaction.RollbackToSavepointAsync("BeforeMoreBlogs");
// TODO: Handle failure, possibly retry inserting blogs
}
컨텍스트 간 트랜잭션
여러 컨텍스트 인스턴스에서 트랜잭션을 공유할 수도 있습니다. 이 기능은 관계형 데이터베이스 공급자를 사용하는 경우에만 사용할 수 있습니다. 관계형 데이터베이스와 관련된 DbTransaction
및 DbConnection
사용해야 하기 때문입니다.
트랜잭션을 공유하려면 컨텍스트가 둘 다 DbConnection
및 DbTransaction
을 공유해야 합니다.
외부로 연결을 제공할 수 있도록 허용
DbConnection
를 공유하려면 연결을 생성할 때 이를 컨텍스트에 전달할 수 있어야 합니다.
DbConnection
외부에서 제공할 수 있도록 허용하는 가장 쉬운 방법은 DbContext.OnConfiguring
메서드 사용을 중지하여 컨텍스트를 구성하고 외부에서 DbContextOptions
만들어 컨텍스트 생성자에 전달하는 것입니다.
팁 (조언)
DbContextOptionsBuilder
DbContext.OnConfiguring
컨텍스트를 구성하는 데 사용한 API이므로 이제 외부에서 이 API를 사용하여 DbContextOptions
만듭니다.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
대안은 DbContext.OnConfiguring
를 계속 사용하기 위해, 저장된 후 DbConnection
에서 사용되는 DbContext.OnConfiguring
을 수락하는 것입니다.
public class BloggingContext : DbContext
{
private DbConnection _connection;
public BloggingContext(DbConnection connection)
{
_connection = connection;
}
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connection);
}
}
연결 및 트랜잭션 공유
이제 동일한 연결을 공유하는 여러 컨텍스트 인스턴스를 만들 수 있습니다. 그런 다음 DbContext.Database.UseTransaction(DbTransaction)
API를 사용하여 동일한 트랜잭션에 두 컨텍스트를 모두 참여합니다.
using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using var context1 = new BloggingContext(options);
await using var transaction = await context1.Database.BeginTransactionAsync();
try
{
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context1.SaveChangesAsync();
using (var context2 = new BloggingContext(options))
{
await context2.Database.UseTransactionAsync(transaction.GetDbTransaction());
var blogs = await context2.Blogs
.OrderBy(b => b.Url)
.ToListAsync();
context2.Blogs.Add(new Blog { Url = "http://dot.net" });
await context2.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
외부 DbTransactions 사용(관계형 데이터베이스에만 해당)
여러 데이터 액세스 기술을 사용하여 관계형 데이터베이스에 액세스하는 경우 이러한 다양한 기술에서 수행하는 작업 간에 트랜잭션을 공유할 수 있습니다.
다음 예제에서는 동일한 트랜잭션에서 ADO.NET SqlClient 작업 및 Entity Framework Core 작업을 수행하는 방법을 보여줍니다.
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "DELETE FROM dbo.Blogs";
command.ExecuteNonQuery();
// Run an EF Core command in the transaction
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
await context.Database.UseTransactionAsync(transaction);
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
System.Transactions 사용
더 큰 범위에서 조정해야 하는 경우 앰비언트 트랜잭션을 사용할 수 있습니다.
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
await command.ExecuteNonQueryAsync();
// Run an EF Core command in the transaction
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
scope.Complete();
}
catch (Exception)
{
// TODO: Handle failure
}
}
명시적 트랜잭션에 등록할 수도 있습니다.
using (var transaction = new CommittableTransaction(
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var connection = new SqlConnection(connectionString);
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
await context.Database.OpenConnectionAsync();
context.Database.EnlistTransaction(transaction);
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
await command.ExecuteNonQueryAsync();
// Run an EF Core command in the transaction
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
await context.Database.CloseConnectionAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
}
TransactionScope
및 주변 트랜잭션에 대한 자세한 내용은 설명서를 참조하세요.
System.Transactions의 제한 사항
EF Core는 데이터베이스 공급자를 사용하여 System.Transactions에 대한 지원을 구현합니다. 공급자가 System.Transactions에 대한 지원을 구현하지 않으면 이러한 API에 대한 호출이 완전히 무시될 수 있습니다. SqlClient에서 지원합니다.
중요합니다
API가 트랜잭션 관리에 의존하기 전에 공급자와 올바르게 작동하는지 테스트하는 것이 좋습니다. 그렇지 않은 경우 데이터베이스 공급자의 유지 관리자에게 문의하는 것이 좋습니다.
System.Transactions의 분산 트랜잭션 지원은 Windows용 .NET 7.0에만 추가되었습니다. 이전 .NET 버전 또는 비 Windows 플랫폼에서 분산 트랜잭션을 사용하려는 시도는 실패합니다.
TransactionScope는 비동기 커밋/롤백을 지원하지 않습니다. 즉, 삭제하면 작업이 완료될 때까지 실행 중인 스레드가 동기적으로 차단됩니다.
.NET