Hallo,
>...muss ich mich von den DataSet's verabschieden ...
nein.
Dieses Problem und die alternativen Lösungswege habe ich in meiner Tipps&Tricks-Session auf der BASTA!2006 vorgestellt, in der nächsten und übernächsten Ausgabe des
dot.net magazin wird der TableAdapter und das Transaktionsproblem vorgestellt .
>Mit jedem neuen TableAdapter wird eine neue SqlConnection geöffnet...
Aber nur dann, wenn
vorher die SqlConnection nicht bereits geöffnet war. Bei dem folgenden Beispiel tritt das Problem nicht auf:
testTblTableAdapter.Connection.ConnectionString = " < aktuelle Version >";
testTblTableAdapter.Connection.Open();
try
{
using (TransactionScope txScope = new TransactionScope())
{
TransactionInformation txInfo = Transaction.Current.TransactionInformation;
listBox1.Items.Add(String.Format("{0}: {1}",
txInfo.Status, txInfo.DistributedIdentifier));
testTblTableAdapter.Fill(tempdbDataSet.TestTbl);
tempdbDataSet.TestTbl.Rows[0]["wert"] = "Neu3";
testTblTableAdapter.Update(tempdbDataSet);
listBox1.Items.Add(String.Format("{0}: {1}",
txInfo.Status, txInfo.DistributedIdentifier));
txScope.Complete();
listBox1.Items.Add(String.Format("{0}: {1}",
txInfo.Status, txInfo.DistributedIdentifier));
}
}
finally
{
testTblTableAdapter.Connection.Close();
}
Zur Kontrolle kann man dies auch im SQL Server prüfen:
SELECT spid, status, program_name, cmd
FROM master.dbo.sysprocesses WITH (NOLOCK)
WHERE dbid = DB_ID('tempdb');
Über die partiellen Klassen kann man den TableAdapter jedoch auch um eigene bzw. überladene Methoden erweitern. Über diesen Weg kann mit einer Handvoll von Programmzeilen der "alte Weg" über
SqlTransaction nachgerüstet werden:
testTblTableAdapter.Connection.Open();
try
{
SqlTransaction aTran;
aTran = testTblTableAdapter.BeginTransaction(testTblTableA dapter.Connection);
try
{
testTblTableAdapter.Fill(tempdbDataSet.TestTbl);
tempdbDataSet.TestTbl.Rows[0]["wert"] = "Neu4";
testTblTableAdapter.Update(tempdbDataSet);
aTran.Commit();
}
catch
{
aTran.Rollback();
}
}
finally
{
testTblTableAdapter.Connection.Close();
}
Die Erweiterung des Beispiel sieht wie folgt aus:
using System;
using System.Data;
using System.Data.SqlClient;
namespace ClientCS
{
partial class tempdbDataSet
{
// eigene DataSet-Erweiterungen
}
}
namespace ClientCS.tempdbDataSetTableAdapters
{
partial class TestTblTableAdapter
{
public SqlTransaction BeginTransaction(SqlConnection aCon)
{
if (aCon.State != ConnectionState.Open)
{
throw new ArgumentException("Die Datenbankverbindung ist noch geschlossen!");
}
Connection = aCon;
SqlTransaction aTran = Connection.BeginTransaction();
foreach (SqlCommand cmd in this.CommandCollection)
{
if (cmd != null)
cmd.Transaction = aTran;
}
if ((Adapter.InsertCommand != null))
{
Adapter.InsertCommand.Transaction = aTran;
}
if ((Adapter.DeleteCommand != null))
{
Adapter.DeleteCommand.Transaction = aTran;
}
if ((Adapter.UpdateCommand != null))
{
Adapter.UpdateCommand.Transaction = aTran;
}
if ((Adapter.SelectCommand != null))
{
Adapter.SelectCommand.Transaction = aTran;
}
return aTran;
}
}
}
Da die voreingestellte Basisklasse
System.ComponentModel.Component des TableAdapters im Eigenschaftsdialog über die Eigenschaft
BaseClass ausgetauscht werden kann, kann allen im Projekt vorhandenen TableAdaptern eine eigene Erweiterung "untergeschoben" werden (auch wenn dies dann nur greift, wenn dort über Reflection die "nachfolgenden" TableAdapter-Teile geändert werden).
>Der ConnectionString wird für jedes einzelne Business Logic Projekt in der app.config gespeichert...
Diese Vorbelegung ist für den TableAdapter nicht zwingend notwendig. Auf Wunsch darf die app.config aus dem Projekt (Bsp: Class Library kapselt den TableAdapter ein) auch entfernt werden - dann allerdings muss vor dem Öffnen der Datenbankverbindung die Verbindungszeichenfolge in eigener Regie initialisiert werden.