트랜잭션 사용
트랜잭션을 사용하면 여러 데이터베이스 작업을 원자성 방식으로 처리할 수 있습니다. 트랜잭션이 커밋되면 모든 작업이 성공적으로 데이터베이스에 적용됩니다. 트랜잭션이 롤백되면 데이터베이스에 아무 작업도 적용되지 않습니다.
팁
GitHub에서 이 문서의 샘플을 볼 수 있습니다.
기본 트랜잭션 동작
기본적으로 데이터베이스 공급자가 트랜잭션을 지원하는 경우 SaveChanges
에 대한 단일 호출의 모든 변경 내용이 트랜잭션에 적용됩니다. 변경이 실패하면 트랜잭션이 롤백되고 변경 내용이 데이터베이스에 적용되지 않습니다. 즉, SaveChanges
이 완전히 성공하도록 보장되거나 오류가 발생하는 경우 데이터베이스가 수정되지 않은 상태로 유지됩니다.
대부분의 애플리케이션에서는 이 기본 동작이면 충분합니다. 애플리케이션 요구 사항에서 필요하다고 생각되는 경우에만 트랜잭션을 수동으로 제어해야 합니다.
트랜잭션 제어
DbContext.Database
API를 사용하여 트랜잭션을 시작하고 커밋하고 롤백할 수 있습니다. 다음 예제에서는 단일 트랜잭션에서 실행되는 두 개의 SaveChanges
작업과 LINQ 쿼리를 보여 줍니다.
using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();
try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
context.SaveChanges();
var blogs = context.Blogs
.OrderBy(b => b.Url)
.ToList();
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
모든 관계형 데이터베이스 공급자는 트랜잭션을 지원하지만 트랜잭션 API가 호출될 때 다른 공급자 형식이 throw 또는 no-op될 수 있습니다.
참고 항목
이러한 방식으로 트랜잭션을 수동으로 제어하는 것은 암시적으로 호출된 다시 시도 실행 전략과 호환되지 않습니다. 자세한 내용은 연결 복원력을 참조하세요.
저장점
SaveChanges
가 호출되고 트랜잭션이 컨텍스트에서 이미 진행 중이면 EF는 데이터를 저장하기 전에 자동으로 ‘저장점’을 만듭니다. 저장점은 나중에 오류가 발생한 경우나 다른 이유로 롤백될 수 있는 데이터베이스 트랜잭션 내의 지점입니다. SaveChanges
에서 오류가 발생하는 경우 트랜잭션을 자동으로 저장점으로 롤백하여 트랜잭션을 시작되지 않은 것처럼 동일한 상태로 유지합니다. 이렇게 하면 특히 낙관적 동시성 문제가 발생할 때 문제를 수정하고 다시 저장할 수 있습니다.
Warning
저장점은 SQL Server의 MARS(Multiple Active Result Set)와 호환되지 않습니다. MARS가 적극적으로 사용되지 않더라도 연결에서 MARS를 사용하도록 설정하면 EF에서 저장점이 생성되지 않습니다. SaveChange 중에 오류가 발생하면 트랜잭션이 알 수 없는 상태로 남아 있을 수 있습니다.
트랜잭션과 마찬가지로 저장점을 수동으로 관리할 수도 있습니다. 다음 예제에서는 트랜잭션 내에 저장점을 만들고 오류 발생 시 해당 저장점으로 롤백합니다.
using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();
try
{
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
context.SaveChanges();
transaction.CreateSavepoint("BeforeMoreBlogs");
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
context.SaveChanges();
transaction.Commit();
}
catch (Exception)
{
// If a failure occurred, we rollback to the savepoint and can continue the transaction
transaction.RollbackToSavepoint("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
을 계속 사용하지만 DbContext.OnConfiguring
에서 저장된 후 사용되는 DbConnection
을 허용합니다.
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);
using var transaction = context1.Database.BeginTransaction();
try
{
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context1.SaveChanges();
using (var context2 = new BloggingContext(options))
{
context2.Database.UseTransaction(transaction.GetDbTransaction());
var blogs = context2.Blogs
.OrderBy(b => b.Url)
.ToList();
context2.Blogs.Add(new Blog { Url = "http://dot.net" });
context2.SaveChanges();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
외부 DbTransactions 사용(관계형 데이터베이스만 해당)
여러 데이터 액세스 기술을 사용하여 관계형 데이터베이스에 액세스하는 경우 이러한 서로 다른 기술에서 수행하는 작업 간에 트랜잭션을 공유할 수 있습니다.
다음 예제에서는 동일한 트랜잭션에서 ADO.NET SqlClient 작업 및 Entity Framework Core 작업을 수행하는 방법을 보여줍니다.
using var connection = new SqlConnection(connectionString);
connection.Open();
using var transaction = connection.BeginTransaction();
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))
{
context.Database.UseTransaction(transaction);
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
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);
connection.Open();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
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))
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
}
// 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))
{
context.Database.OpenConnection();
context.Database.EnlistTransaction(transaction);
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
command.ExecuteNonQuery();
// Run an EF Core command in the transaction
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
context.Database.CloseConnection();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
}
참고 항목
비동기 API를 사용하는 경우 TransactionScope
생성자에서 TransactionScopeAsyncFlowOption.Enabled를 지정하여 비동기 호출 간에 앰비언트 트랜잭션이 흐르도록 해야 합니다.
TransactionScope
및 앰비언트 트랜잭션에 대한 자세한 내용은 설명서를 참조하세요.
System.Transactions의 제한 사항
EF Core는 데이터베이스 공급자를 사용하여 System.Transactions에 대한 지원을 구현합니다. 공급자가 System.Transactions에 대한 지원을 구현하지 않는 경우 이러한 API에 대한 호출을 완전히 무시해도 됩니다. SqlClient는 이 기능을 지원합니다.
Important
트랜잭션을 관리하는 데 사용하기 전에 API가 공급자에서 올바르게 동작하는지 테스트하는 것이 좋습니다. 그렇지 않으면 데이터베이스 공급자의 유지 관리자에게 문의하는 것이 좋습니다.
System.Transactions의 분산 트랜잭션 지원은 Windows용 .NET 7.0에만 추가되었습니다. 이전 .NET 버전 또는 비 Windows 플랫폼에서 분산 트랜잭션을 사용하려는 모든 시도는 실패합니다.
TransactionScope는 비동기 커밋/롤백을 지원하지 않습니다. 즉, 삭제하면 작업이 완료될 때까지 실행 중인 스레드가 동기적으로 차단됩니다.
.NET