C# – Verwendung von SQL-Transaktionen mit Dapper

C# – Verwendung von SQL-Transaktionen mit Dapper

Die Verwendung von TransactionScope ist die einfachste Möglichkeit, mehrere SQL-Befehle in einer Transaktion auszuführen. Hier ist ein Beispiel für die Verwendung:

using System.Transactions;

using (var trxScope = new TransactionScope())
{
	movieRepo.Insert(newMovie);
	movieRepo.Delete(movieToDelete);

	//Commits the transaction
	trxScope.Complete();
};
//Rolls back the transaction if Complete() wasn't called
Code language: C# (cs)

Wenn Sie TransactionScope.Complete() aufrufen, wird die Transaktion festgeschrieben. Wenn Sie Complete() nicht aufrufen, wird die Transaktion zurückgesetzt, sobald sie den TransactionScope-Block verlässt.

Dadurch bleibt der Code schön sauber und funktioniert gut mit dem Repository-Muster. Hier sind die Methoden Insert()/Delete() in der Repository-Klasse. Beachten Sie, dass es sich überhaupt nicht um Transaktionen kümmern muss?

using Dapper;
using System.Data.SqlClient;

public class MovieRepository
{
	public void Insert(Movie movie)
	{
		using (var con = new SqlConnection(connectionString))
		{
			con.Execute(INSERT_SQL, param: movie);
		}
	}
	public void Delete(Movie movie)
	{
		using (var con = new SqlConnection(connectionString))
		{
			con.Execute(DELETE_SQL,
				param: new { id = movie.Id });
		}
	}
	//rest of class
}
Code language: C# (cs)

Hinweis:Verwendung von .NET 5 für eine SQL Server 2016-Datenbank.

Jede Verbindung, die innerhalb des TransactionScope-Blocks geöffnet wird, wird automatisch in die Transaktion eingetragen.

Verteilte Transaktionen

Wenn eine Transaktion erstellt wird, beginnt sie als lokale Transaktion. Unter bestimmten Bedingungen wird es zu einer verteilten Transaktion eskaliert, die den Distributed Transaction Coordinator (MSDTC) erfordert Dienst ausgeführt werden. Es gibt zwei Hauptbedingungen, die dazu führen, dass Transaktionen eskalieren:

  • Explizites gleichzeitiges Öffnen von zwei Verbindungen im Transaktionsbereich.
  • Verwendung unterschiedlicher Verbindungszeichenfolgen (z. B. wenn Sie sich mit einem anderen Server verbinden).

Auch die von Ihnen verwendete Datenbank-Engine/Version spielt eine Rolle. Es ist am besten, früh im Entwicklungsprozess herauszufinden, ob Sie sich mit verteilten Transaktionen befassen müssen oder nicht. Dies liegt daran, dass sie eine architektonische Straßensperre darstellen können. Versuchen Sie im Idealfall, verteilte Transaktionen zu vermeiden.

Verteilte Transaktionen werden in .NET Core nicht unterstützt

Verteilte Transaktionen werden derzeit in plattformübergreifenden Versionen von .NET (.NET Core und höher) nicht unterstützt. Es ist möglich, dass Microsoft irgendwann Unterstützung dafür hinzufügt. Wenn Sie etwas tun, das eine Transaktionseskalation auslöst, erhalten Sie die folgende Ausnahme:

Wenn Sie zu .NET Core migrieren und verteilte Transaktionen benötigen, stellt dies eine große Blockade dar, die eine Neugestaltung erfordern würde, um verteilte Transaktionen zu eliminieren.

Hinweis:Sie erhalten möglicherweise den Fehler „MSDTC ist nicht verfügbar“, wenn der MSDTC-Dienst nicht ausgeführt wird, was verwirrend ist, weil es irrelevant ist. Wenn MSDTC ausgeführt wird, erhalten Sie die Ausnahme „Plattform nicht unterstützt“.

Verteilte Transaktionen in .NET Framework

Verteilte Transaktionen werden in .NET Framework unterstützt und erfordern den Distributed Transaction Coordinator (MSDTC) Dienst ausgeführt werden. Wenn eine Transaktion eskaliert wird und der MSDTC-Dienst nicht ausgeführt wird, erhalten Sie den Fehler:

Stellen Sie sicher, dass der MSDTC-Dienst ausgeführt wird und auf automatischen Start eingestellt ist.

Vermeiden einer Transaktionseskalation beim Verbinden mit verschiedenen Datenbanken auf demselben Server

Unterschiedliche Verbindungszeichenfolgen lösen eine Transaktionseskalation aus, selbst wenn Sie eine Verbindung zu verschiedenen Datenbanken auf demselben Server herstellen. Beispielsweise löst der folgende Code (ausgeführt in einem TransactionScope) eine Transaktionseskalation aus und schlägt mit der Ausnahme „Plattform nicht unterstützt“ (in plattformübergreifendem .NET) fehl:

public void Insert(Movie movie)
{
	using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbNew;Integrated Security=true"))
	{
		con.Execute(INSERT_SQL, param: movie);
	}
}
public void Delete(Movie movie)
{
	using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbOld;Integrated Security=true"))
	{
		con.Execute(DELETE_SQL,
			param: new { id = movie.Id });
}
Code language: C# (cs)

Eine Möglichkeit, die Transaktionseskalation zu vermeiden, besteht darin, dieselbe Verbindungszeichenfolge zu verwenden und mit USE :

zur Zieldatenbank zu wechseln
public void Insert(Movie movie)
{
	using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbNew;Integrated Security=true"))
	{
		con.Execute(INSERT_SQL, param: movie);
	}
}
public void Delete(Movie movie)
{
	using (var con = new SqlConnection("Server=MAKOLYTE;Database=MoviesDbNew;Integrated Security=true"))
	{
		con.Execute("USE MoviesDbOld");
		con.Execute(DELETE_SQL,
			param: new { id = movie.Id });
	}
}
Code language: C# (cs)

Da es sich um dieselbe Verbindungszeichenfolge handelt, wird die Transaktion nicht eskaliert.

Hinweis:Dies ist dasselbe wie das Aufrufen von con.Open() + con.ChangeDatabase(„MoviesDbOld“), nur einfacher, da ich es vorziehe, Dapper die Verbindung öffnen zu lassen.

Alternative zu TransactionScope – Connection.BeginTransaction()

Wenn Sie eine explizitere Kontrolle über die Transaktion bevorzugen, können Sie anstelle von TransactionScope den Stil Connection.BeginTransaction() verwenden. Hier ist ein Beispiel:

using Dapper;
using System.Data.SqlClient;

using(var con = new SqlConnection(connectionString))
{
	con.Open();
	using(var trx= con.BeginTransaction())
	{
		con.Execute(INSERT_SQL, param: movieToInsert, transaction: trx);
		con.Execute(DELETE_SQL, param: new { movieToDelete.Id }, transaction: trx);

		trx.Commit();
	}
}
Code language: C# (cs)

Beachten Sie, dass die Verbindung geöffnet werden muss, bevor BeginTransaction() aufgerufen wird.

Wenn Sie Commit() nicht aufrufen, wird die Transaktion automatisch zurückgesetzt, wenn sie den BeginTransaction using-Block verlässt. Sie können Rollback() auch selbst aufrufen.