Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Med transaktioner kan flera databasåtgärder bearbetas på ett atomiskt sätt. Om transaktionen bekräftas tillämpas alla åtgärder framgångsrikt på databasen. Om transaktionen återställs tillämpas ingen av åtgärderna på databasen.
Tips
Du kan visa den här artikelns exempel på GitHub.
Standardbeteende för transaktioner
Om databasprovidern som standard stöder transaktioner tillämpas alla ändringar i ett enda anrop till SaveChanges i en transaktion. Om någon av ändringarna misslyckas återställs transaktionen och ingen av ändringarna tillämpas på databasen. Det innebär att SaveChanges garanteras antingen lyckas helt eller lämnar databasen oförändrad om ett fel inträffar.
För de flesta program räcker det här standardbeteendet. Du bör endast kontrollera transaktioner manuellt om dina programkrav anser att det är nödvändigt.
Styra transaktioner
Du kan använda DbContext.Database-API:et för att starta, checka in och återställa transaktioner. I följande exempel visas två SaveChanges åtgärder och en LINQ-fråga som körs i en enda transaktion:
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
}
Även om alla relationsdatabasprovidrar stöder transaktioner kan andra typer av leverantörer utlösa eller no-op när transaktions-API:er anropas.
Anmärkning
Manuell styrning av transaktioner på det här sättet är inte kompatibel med implicita anropade körstrategier för återförsök. Mer information finns i Anslutningsstabilitet.
Spara poäng
När SaveChanges anropas och en transaktion redan pågår i kontexten, skapar EF automatiskt en sparpunkt innan några data sparas. Savepoints är punkter i en databastransaktion som senare kan återställas till, om ett fel inträffar eller av någon annan anledning. Om SaveChanges stöter på något fel återställs transaktionen automatiskt till sparandepunkten, vilket lämnar transaktionen i samma tillstånd som om den aldrig hade startats. På så sätt kan du eventuellt korrigera problem och försöka spara igen, särskilt när problem med optimistisk samtidighet uppstår.
Varning
Savepoints är inte kompatibla med SQL Server:s flera aktiva resultatuppsättningar (MARS). Savepoints skapas inte av EF när MARS är aktiverat på anslutningen, även om MARS inte används aktivt. Om ett fel uppstår under SaveChanges kan transaktionen lämnas i ett okänt tillstånd.
Det går också att hantera sparandepunkter manuellt, precis som med transaktioner. I följande exempel skapas en återställningspunkt i en transaktion, och rullas tillbaka till den vid fel:
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
}
Transaktion mellan kontexter
Du kan också dela en transaktion över flera kontextinstanser. Den här funktionen är endast tillgänglig när du använder en relationsdatabasprovider eftersom den kräver användning av DbTransaction och DbConnection, som är specifika för relationsdatabaser.
För att dela en transaktion måste kontexterna dela både en DbConnection och en DbTransaction.
Tillåt att anslutningen tillhandahålls externt
Om du delar en DbConnection måste du kunna föra över en anslutning till en kontext när du konstruerar den.
Det enklaste sättet att tillåta att DbConnection tillhandahålls externt är att sluta använda metoden DbContext.OnConfiguring för att konfigurera kontexten och skapa DbContextOptions externt och skicka dem till kontextkonstruktorn.
Tips
DbContextOptionsBuilder är det API som du använde i DbContext.OnConfiguring för att konfigurera kontexten ska du nu använda den externt för att skapa DbContextOptions.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
Ett alternativ är att fortsätta använda DbContext.OnConfiguring, men acceptera en DbConnection som sparas och sedan används i 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);
}
}
Dela anslutning och transaktion
Nu kan du skapa flera kontextinstanser som delar samma anslutning. Använd sedan api:et DbContext.Database.UseTransaction(DbTransaction) för att registrera båda kontexterna i samma transaktion.
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
}
Använda externa DbTransactions (endast relationsdatabaser)
Om du använder flera dataåtkomsttekniker för att få åtkomst till en relationsdatabas kanske du vill dela en transaktion mellan åtgärder som utförs av dessa olika tekniker.
I följande exempel visas hur du utför en ADO.NET SqlClient-åtgärd och en Entity Framework Core-åtgärd i samma transaktion.
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
}
Att använda System.Transactions
Du kan använda omgivande transaktioner om du behöver samordna över ett större omfång.
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
}
}
Det går också att registrera sig i en explicit transaktion.
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
}
}
Anmärkning
Om du använder asynkrona API:er måste du ange TransactionScopeAsyncFlowOption.Enabled i TransactionScope konstruktorn för att säkerställa att den omgivande transaktionen flödar över asynkrona anrop.
Mer information om TransactionScope och omgivande transaktioner finns i den här dokumentationen.
Begränsningar i System.Transactions
EF Core förlitar sig på databasprovidrar för att implementera stöd för System.Transactions. Om en provider inte implementerar stöd för System.Transactions är det möjligt att anrop till dessa API:er ignoreras helt. SqlClient stöder det.
Viktigt!
Vi rekommenderar att du testar att API:et fungerar korrekt med din provider innan du förlitar dig på det för att hantera transaktioner. Du uppmanas att kontakta databasleverantörens underhållare om den inte gör det.
Stöd för distribuerade transaktioner i System.Transactions lades endast till i .NET 7.0 för Windows. Alla försök att använda distribuerade transaktioner på äldre .NET-versioner eller på andra plattformar än Windows misslyckas.
TransactionScope stöder inte asynkron kommittering/återställning; det innebär att när den hanteras synkront blockeras den exekverande tråden tills åtgärden är slutförd.