Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Anmärkning
ENDAST EF6 – De funktioner, API:er osv. som beskrivs på den här sidan introducerades i Entity Framework 6. Om du använder en tidigare version gäller inte en del av eller all information.
Det här dokumentet beskriver hur du använder transaktioner i EF6, inklusive de förbättringar som vi har lagt till sedan EF5 för att göra det enkelt att arbeta med transaktioner.
Vad EF gör som standard
När du kör SaveChanges() för att infoga, uppdatera eller ta bort i databasen omsluts åtgärden i en transaktion i alla versioner av Entity Framework. Den här transaktionen varar bara tillräckligt länge för att köra åtgärden och slutförs sedan. När du kör en annan sådan åtgärd startas en ny transaktion.
Från och med EF6 Database.ExecuteSqlCommand() omsluts kommandot som standard i en transaktion om en inte redan fanns. Det finns överbelastningar av den här metoden som låter dig åsidosätta beteendet om du så önskar. Även i EF6-körning av lagrade procedurer som ingår i modellen via API:er som ObjectContext.ExecuteFunction() gör samma sak (förutom att standardbeteendet för närvarande inte kan åsidosättas).
I båda fallen är isoleringsnivån för transaktionen den isoleringsnivå som databasprovidern anser vara standardinställningen. Som standard är detta till exempel READ COMMITTED på SQL Server.
Entity Framework omsluter inte frågor i en transaktion.
Den här standardfunktionen är lämplig för många användare och i såfall behöver du inte göra något annat i EF6. skriv bara koden som du alltid gjorde.
Vissa användare behöver dock större kontroll över sina transaktioner – detta beskrivs i följande avsnitt.
Så här fungerar API:erna
Innan EF6 Entity Framework insisterade på att öppna själva databasanslutningen (det utlöste ett undantag om det skickades en anslutning som redan var öppen). Eftersom en transaktion bara kan startas på en öppen anslutning innebar detta att det enda sättet för en användare att omsluta flera åtgärder i en transaktion var att antingen använda ett TransactionScope eller använda egenskapen ObjectContext.Connection och börja anropa Open() och BeginTransaction() direkt på det returnerade EntityConnection-objektet . Dessutom skulle API-anrop som kontaktade databasen misslyckas om du hade startat en transaktion på den underliggande databasanslutningen på egen hand.
Anmärkning
Begränsningen att endast acceptera stängda anslutningar togs bort i Entity Framework 6. Mer information finns i Anslutningshantering.
Från och med EF6 tillhandahåller ramverket nu:
- Database.BeginTransaction() : En enklare metod för en användare att starta och slutföra transaktioner själva i en befintlig DbContext – vilket gör att flera åtgärder kan kombineras inom samma transaktion och därmed antingen alla checkas in eller återställas som en. Det gör det också möjligt för användaren att enklare ange isoleringsnivån för transaktionen.
- Database.UseTransaction() : som gör att DbContext kan använda en transaktion som startades utanför Entity Framework.
Kombinera flera åtgärder till en transaktion inom samma kontext
Database.BeginTransaction() har två åsidosättningar – en som tar en explicit IsolationLevel och en som inte tar några argument och använder standard-IsolationLevel från den underliggande databasprovidern. Båda åsidosättningarna returnerar ett DbContextTransaction-objekt som tillhandahåller metoderna Commit() och Rollback() som utför kommitt och återställning på den underliggande transaktionen i lagringsenheten.
DbContextTransaction är avsedd att avyttras när den har committerats eller återställts. Ett enkelt sätt att åstadkomma detta är syntaxen using(...) {...} som automatiskt anropar Dispose() när användningsblocket har slutförts:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void StartOwnTransactionWithinContext()
{
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
context.Database.ExecuteSqlCommand(
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'"
);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
dbContextTransaction.Commit();
}
}
}
}
}
Anmärkning
Om du påbörjar en transaktion måste den underliggande butiksanslutningen vara öppen. Om du anropar Database.BeginTransaction() öppnas anslutningen om den inte redan har öppnats. Om DbContextTransaction öppnade anslutningen stängs den när Dispose() anropas.
Skicka en befintlig transaktion till kontexten
Ibland vill du ha en transaktion som är ännu bredare i omfånget och som omfattar åtgärder i samma databas men utanför EF helt. För att åstadkomma detta måste du öppna anslutningen och starta transaktionen själv och sedan be EF a) att använda den redan öppnade databasanslutningen och b) för att använda den befintliga transaktionen på den anslutningen.
För att göra detta måste du definiera och använda en konstruktor i kontextklassen som ärver från en av DbContext-konstruktorerna som tar i) en befintlig anslutningsparameter och ii) kontextOwnsConnection boolean.
Anmärkning
Flaggan contextOwnsConnection måste vara inställd på false när den anropas i det här scenariot. Detta är viktigt eftersom det informerar Entity Framework om att det inte ska stänga anslutningen när den är klar med den (se till exempel rad 4 nedan):
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
}
}
Dessutom måste du starta transaktionen själv (inklusive IsolationLevel om du vill undvika standardinställningen) och meddela Entity Framework att en befintlig transaktion redan har startats på anslutningen (se rad 33 nedan).
Sedan kan du köra databasåtgärder antingen direkt på själva SqlConnection eller på DbContext. Alla sådana åtgärder utförs inom en transaktion. Du tar ansvar för att genomföra eller återställa transaktionen och anropa Dispose() på den, samt för att stänga och disponera databasanslutningen. Som exempel:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
}
}
}
}
Avsluta transaktionen
Du kan skicka null till Database.UseTransaction() för att rensa Entity Frameworks kunskaper om den aktuella transaktionen. Entity Framework varken commitar eller rullar tillbaka den befintliga transaktionen när du gör detta, så använd försiktigt och bara om du är säker på att det är vad du vill.
Fel i UseTransaction
Du ser ett undantag från Database.UseTransaction() om du skickar en transaktion när:
- Entity Framework har redan en befintlig transaktion
- Entity Framework fungerar redan inom ett TransactionScope
- Anslutningsobjektet i den skickade transaktionen är null. Transaktionen är alltså inte associerad med en anslutning – vanligtvis är detta ett tecken på att transaktionen redan har slutförts
- Anslutningsobjektet i den transaktion som skickades matchar inte Entity Framework-anslutningen.
Använda transaktioner med andra funktioner
Det här avsnittet beskriver hur ovanstående transaktioner interagerar med:
- Anslutningsmotståndskraft
- Asynkrona metoder
- TransactionScope-transaktioner
Anslutningsresiliens
Den nya funktionen Anslutningsåterhämtning fungerar inte med användarinitierade transaktioner. Mer information finns i Retrying Execution Strategies (Återförsök av körningsstrategier).
Asynkron programmering
Den metod som beskrivs i föregående avsnitt behöver inga ytterligare alternativ eller inställningar för att fungera med de asynkrona metoderna för fråga och spara. Men tänk på att beroende på vad du gör inom de asynkrona metoderna kan detta resultera i långvariga transaktioner – vilket i sin tur kan orsaka dödlägen eller blockering som är dåligt för det övergripande programmets prestanda.
TransactionScope-transaktioner
Före EF6 var det rekommenderade sättet att tillhandahålla transaktioner med större omfång att använda ett TransactionScope-objekt:
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
}
scope.Complete();
}
}
}
}
SqlConnection och Entity Framework kommer båda att använda den omgivande TransactionScope-transaktionen och därmed slutföras tillsammans.
Från och med .NET 4.5.1 har TransactionScope uppdaterats för att även fungera med asynkrona metoder med hjälp av TransactionScopeAsyncFlowOption-uppräkning :
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
public static void AsyncTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
using (var conn = new SqlConnection("..."))
{
await conn.OpenAsync();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
await sqlCommand.ExecuteNonQueryAsync();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
await context.SaveChangesAsync();
}
}
scope.Complete();
}
}
}
}
Det finns fortfarande vissa begränsningar i Metoden TransactionScope:
- Kräver .NET 4.5.1 eller senare för att fungera med asynkrona metoder.
- Det kan inte användas i molnscenarier om du inte är säker på att du har en och bara en anslutning (molnscenarier stöder inte distribuerade transaktioner).
- Det går inte att kombinera med metoden Database.UseTransaction() i föregående avsnitt.
- Det utlöser undantag om du utfärdar DDL och inte har aktiverat distribuerade transaktioner via MSDTC-tjänsten.
Fördelar med Metoden TransactionScope:
- Den uppgraderar automatiskt en lokal transaktion till en distribuerad transaktion om du gör mer än en anslutning till en viss databas eller kombinerar en anslutning till en databas med en anslutning till en annan databas inom samma transaktion (obs! du måste ha MSDTC-tjänsten konfigurerad för att distribuerade transaktioner ska fungera).
- Enkel kodning. Om du föredrar att transaktionen ska vara omgivande och hanteras implicit i bakgrunden i stället för att uttryckligen kontrolleras kan TransactionScope-metoden passa dig bättre.
Sammanfattningsvis, med de nya API:erna Database.BeginTransaction() och Database.UseTransaction() ovan, är Metoden TransactionScope inte längre nödvändig för de flesta användare. Om du fortsätter att använda TransactionScope bör du vara medveten om ovanstående begränsningar. Vi rekommenderar att du använder metoden som beskrivs i föregående avsnitt i stället där det är möjligt.