Laden...

[gelöst] 2. Open innerhalb eines TransactionsScope geht schief....

Erstellt von Howard vor 15 Jahren Letzter Beitrag vor 15 Jahren 7.649 Views
Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren
[gelöst] 2. Open innerhalb eines TransactionsScope geht schief....

verwendetes Datenbanksystem: <SQL Server2005>

(Okay also mach ich mal nen neuen Thread auf..... 8) )

Ich bekomme folgende Fehlermeldungen bei nachfolgendem Code obwohl der DTC an und auf Netzwerkzugriff eingestellt ist:

  1. "Der Netzwerkzugriff für den Manager für verteilte Transaktionen (MSDTC) wurde deaktiviert. Aktivieren Sie DTC für den Netzwerkzugriff in der Sicherheitskonfiguration für MSDTC, indem Sie das Verwaltungstool Komponentendienste verwenden."
    System.Transactions.TransactionManagerCommunicationException

  2. "Der Transaktions-Manager hat die Unterstützung für Remote-/Netzwerktransaktionen deaktiviert. (Ausnahme von HRESULT: 0x8004D024)"
    System.Runtime.InteropServices.COMException


public void Main()
{
    MessageBox LeseAusDB();
    LöscheDaten();
}

public String LeseAusDB()
{
    using(DbConnection con = new DbConnection(....))
    {
        //lese mal Daten aus der DB
        ....
    }

    return Daten;
}

public void LöscheDaten()
{
    using(TransactionScope ts = new TransactionScope(....))
    {
        using(DbConnection con = new DbConnection(....))
        {
            Daten = LeseAusDB(); <-- DER GEHT SCHIEF wegen der neuen Connection
            //lösche mal alle Daten die gelesen wurden
            ....
        }

    }
}

Ich muss also die lesen Funktion einmal zur Anzeige benutzen und anderes Mal fürs Löschen (absulut vereinfacht dargestellt)
Problem ist das in der LeseAusDB() Funktion, wenn sie innerhalb der Transaction läuft, der con.Open() mit oben genannten Exceptions schief geht. Wenn ich da auch noch ne Transaction rumbaue dann gehts natürlich aber das kann doch nich der Sinn sein das ich auch noch allen Lesen-Functions eine Transaction verpassen muß....ODER??? geschockt

F
10.010 Beiträge seit 2004
vor 15 Jahren

Wenn du mal die Fehlermeldung liesst, wirst Du feststellen, das der für den
TransactionScope zuständige MSDTC keinen Netzwerkzugriff hat.
Dies bedeutet meist, das eine Firewall diesen zugriff blockt
( auch auf dem selben rechner ).

Und solange der nicht für die Transactionen sorgen kann, wirst Du immer selber welche machen müssen.

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

Okay also ich hab gestern dann den gaaaaanzen Tag damit zugebracht das ganze irgendwie ans laufen zu bekommen. Unter Vista hab ichs dann irgendwie hinbekommen mit RegistryEinträgen auf dem Server, Firewalleinstellungen und was weiß ich noch alles....
Aber ich denke ich brauch da noch mal ne grundsätzliche Erklärung zu dem ganzen Wahnsinn....
Also ich hab das so verstanden:

Der DTC(wenn er mal funktioniert dann) kümmert sich genau darum, mein Open (und sicher weitere Dinge)welches nicht explizit (durch using) von einer Transaction umschlossen wird, irgendwie doch verarbeiten zu können.
Ich kann andererseits aber auch ohne diese ganze DTC Nummer auskommen wenn ich mich selber um jedes einzelne Open kümmere und das entweder mit ner Transaction umschließe oder irgendwas wie EnlistTransaction(...) mache .....

Hab ich das Grundsätzlich korrekt auf dem Schirm ?(

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

TransactionScope wurde eingeführt um verschiedene Transaktionen zusammenzuknüpfen,
die auch nicht unbedingt etwas mit Datenbanken zu tun haben müssen.

Wenn Du also "nur" Datenbanktransaktionen hast, dann ist das auch
durchaus nur mit den std TransActions zu machen.

Vielleicht hast du ja auch nur den hier ( u.a. von mir ) immer wieder gebrauchten
Satz, das Connections nur für eine Aktion angelegt und benutzt werden sollen etwas
falsch ausgelegt.

Die Connection sollte für eine zusammengehörigen Aktion erzeugt werden,
nicht mehr aber auch nicht weniger.

Wenn du jetzt also eine Hilfsklasse hast, die dir die Connections liefert,
kannst du dort auch gleich die Transaction erzeugen, und solange diese
nicht beendet ist, immer die selbe Connection liefern.
Erst nach Comit/Rollback oder ohne Transaction wird eine neue Connection geliefert.

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

ich hab tatsache sowas wie ne "Hilfsfunktion" die mir ein connectionObjekt zurückliefert. Dann hab ich da noch ne "Hilfsfunktion" dir mir nen TransActionScope liefert. beides zunächst unabhängig voneinander und ich schreibe dann (so hatte ich es verstanden)
sowas hier:


   try
   {
	using(TransactionScope ts = TransactionBegin())
	{
		using (DbConnection connection = OpenConnection())
		{
			AbfrageAusfuehren(.....,connection);
                 ........

Okay wenn ich dich nun richtige verstanden hab kann man auch die gleiche Connection zurückliefern solange man "noch in der gleichen Aktion" ist. Das wäre "noch im limit" ja? 😁

also ich mache "nur" Datenbanktransaktionen. Was meinst du mit std TransActions ????

Howard

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

okay also mit kurz vor Wahnsinnig werden hab ich nun endlich meinen "großen" Fehler entdeckt


	using(TransactionScope ts = TransactionBegin())
	{
		using (DbConnection connection = OpenConnection())
		{
			AbfrageAusfuehren(....,connection);
		}
		TransactionCommit(ts);//<-diese Zeile hatte ich doch glatt vergessen
	}

Okay passiert. Und ich dachte die ganze Zeit das sich nachfolgende Functions nicht in die Transaction reinhängen.

eine Frage entsteht nun aber doch noch am Ende:
@Golo: Ich hoffe das passt noch immer zum Thema (ich meine JA aber kann mich auch irren) ?(

Ich habe deine Idee mit dem benutzen EINER Connection während eine zusammenhängenden Aktion umgesetzt. ABER wenn es mehrere Functions gibt wie diese hier:


	using (DbConnection connection = OpenConnection())
	{
		....
	}

...dann wird nach der ersten Function die "Main"Connection geschlossen.
Was ja nicht weiter schlimm ist ich kann sie ja auch wieder auf machen
ABER ab dem 2. Öffnen (womit wir wieder beim Thema wären 😄) hängt sich die ganze Sache in den DTC. Genau DAS würde ich aber gern verhindern. Da ich dann wieder auf allen Clients die local Firewall Einstellungen anpassen muss.

Ich dachte ich komm über die beiden Ereignisse "Disposed" und "StateChange" irgendwie da dran um das Closen zu verhindern.
Auch nach einem erneuten Open() ein


.EnlistTransaction(System.Transactions.Transaction.Current);

zu machen hält das Prog nicht davon ab sich in den DTC zu hängen.
Irgendeine Idee wie ich das sonnst noch machen kann?

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

Wenn Du sowieso nur DB Transactions hast, zu ein und dem selben Server, ist
der TransactionScope überdimensioniert.

Dann ist es deutlich einfacher dir eine hilfsklasse zu erstellen, die
selber IDisposable implementiert, und die eine Connection liefert,
und auch ein Begintransaction/Comit/Rollback macht.

Diese Hilfsklasse legst du dann ausserhalb der aktion an.
Aber auch wirklich nur ausserhalb einer komplett am stück ablaufenden aktion,
nicht Transaction öffnen, irgendwas lesen, dann Dialog aufmachen, und danach
ggf speichern.
Soetwas ist nicht gemeint.


public class DBHelper : IDisposable
{
    string _ConnectionString;
    DBConnection _con;
    DBTransaction _trans;
    public DBHelper(string connectionString)
    {
        _ConnectionString = connectionString;     
    }
    
    public DBConnection GetConnection()
    {
        if( _con == null )
        {
            _con = new DieConnectionHalt(_ConnectionString);
        }
        return _con;
    }
    public void BeginTransaction()
    {
        if( _con==null)
            throw new Exception("Keine offene Connection...");

        _trans = _con.BeginTransaction();
    }
    public void RollBack()
    {
        if( _trans==null)
            throw new Exception("Keine offene Transaction...");
        
        _trans.Rollback();
    }    
    public void Comit()
    {
        if( _trans==null)
            throw new Exception("Keine offene Transaction...");
        
        _trans.Comit();
    }    

    public void Dispose()
    {
        if( _trans!=null)
            _trans.Rollback();
        if( _con != null )
            _con.Close();
    }
}

Hier fehlt noch eine echte fehlerbehandlung, vernünftiges IDisposable usw.

Und so machst du dann den zugriff.


using ( DBHelper db = new DBHelper( connectionString) )
{
        db.BeginTransaction();
        LeseDatenFürTabelle( db );
        SchreibeneueDaten( db );
        db.Commit();
}

Deine IDisposable implementation kann dann im Dispose() die Transaction
ggf per Rollback beenden, und danach die Connection schliessen.

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

Danke für deine ausführliche Antwort und die Idee mit dem DBHelper. In deinem Dispose wird allerdings auch die connection geschlossen. Bedeutet es wenn ich DBTransaction benutze wird auf jedenfall NICHT der DTC eingeschaltet?? Also der DTC reagiert nur auf TransactionScope??

Zum TransactionScope: die Studio Hilfe meint man solle auf jedenfall besser TransactionScope benutzen und wenn ich Golon in seinem Artikel ("Transactions.Erklärt()" 04/08 dotnetpro) richtig verstanden hab ist er auch der gleichen Meinung. wo ist der überhaupt? Lässt sich hier gar nicht mehr blicken?? Issa sauer auf mich oder wie 😁

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

TransaktionScope ist dann sinnvoll, wenn man sich weder um die Connections
noch sonstiges kümmern will, und kein Problem mit dem DTC hat.

Ansonsten ist bei einer einzelnen Verbindung die DBTransaction sinnvoller.
Und ja, das mit dem schliessen der Connection ist sinnvoll:
[Artikel] Ressourcen schonen - Datenbanken richtig öffnen und schließen

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

ja iss mir schon klar das ditt schließen sinnvoll ist!!!!

Es geht nur darum: aktuell klemmt sich jede NEU connection an den DTC was ja bei deiner Idee ebenfalls passieren würden WENN ja wenn die "normalen" Transactions ebend **nicht ** genau so reagieren wie nen TransCope. Genau das war dann auch die Frage ob die halt anders reagieren????

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

Die normalen DBTransactions hängen an einer Connection, und bewirken
da genau das, was der TranactionScope macht, denn der "vereint" nur mehrere
transactionen ( egal ob DB oder COM+ ).

Und Du musst mal definieren, was Du damit meinst

Es geht nur darum: aktuell klemmt sich jede NEU connection an den DTC was ja bei deiner Idee ebenfalls passieren würden WENN ja wenn die "normalen" Transactions ebend nicht genau so reagieren wie nen TransCope. Genau das war dann auch die Frage ob die halt anders reagieren????

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

also ich meine folgendes:

wenn ich sowas habe:


void Main()
{
    using(TransactionScope ts = TransactionBegin())
    {
        using (DbConnection connection = OpenConnection())
        {
            ....
        }

        Function2()
        TransactionCommit(ts);
    }
}

void Function2()
{
    using(TransactionScope ts = TransactionBegin())
    {
        using (DbConnection connection = OpenConnection())
        {
            ....
        }
        TransactionCommit(ts);
    }
}


dann wird ja NACH dem using in Main die Connection geschlossen.
Wenn ich dann in Function2 bin wird natürlich eine neue Connection geöffnet und nach dem using geschlossen (okay ja intern nicht wirklich geschlossen)
Aber genau bei diesem Öffnen hängt sich das ganze in den DTC vorher noch nicht also bei der ersten connection (zumindest wenn den SQL Profiler richtig interpretiert hab). Die Frage ist nun bezieht sich diese Verhalten nur auf TransactionScope oder auch auf deine "normalen" Transactions?? Wenn ja dann hab ich ja nix gewonnen...Verstehste was ich mein?? Ich hoffe... 😁

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

Sorry, aber irgendwie scheinst du da immer irgendwas durcheinander zu bringen.

Eine Connection wird für eine zusammengehörige Aktion benutzt, dann
reicht eine normale Transaction.

Wenn du also aufhörst das ganze irgendwie zu verschachteln, und es besser zu
koordinieren ist der TransactionScope nicht nötig.

Aber erzähl doch mal, warum du meinst, das du sogar das lesen der daten in eine
Transaction haben willst.

Für mich sieht es so aus, als wenn du in alte Zeiten willst, und über dieses Verfahren
ein Serverbelastendes Locking von Datensätzen zu erreichen, statt das
ADO.NET zu grunde liegendes Verfahren zu nutzen.

Ausserdem ist das doch sogar mit der miniklasse da oben leicht möglich.


 using(DBHelper db = DBHelper(ConnectionString))
    {
        Func1(db);

        Function2(db)
        db.Commit();
    }

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

...bin ja mal gespannt wie lang der Thread noch wird 😁

so ich versuchs noch mal mit (Pseudo)Code:


public Adressen LeseAlleAdressen()
{
	//Hier brauch ich ne EIGENE Connection
	//denn diese Function wird nicht nur  
	//von AdressenLöschen aufgerufen
	using(GetConnection())
	{
		....
	}
}

public Personen LeseAllePersonen()
{
	//Hier brauch ich ne EIGENE Connection
	//denn diese Function wird nicht nur  
	//von AdressenLöschen aufgerufen
	using(GetConnection())
	{
		....
	}
}

public Orte LeseAlleOrte()
{
	//Hier brauch ich ne EIGENE Connection
	//denn diese Function wird nicht nur  
	//von AdressenLöschen aufgerufen
	using(GetConnection())
	{
		....
	}
}

public void AdressenLöschen()
{
	using(trans,GetConnection())//einfach dargestellt
	{	
		//nach dieser Zeile ist die connection geschlossen wenn 
		//ich für alle die gleiche benutzen möchte. wegen dem Using in LeseAlleAdressen()
		//bisher wurde noch nicht der DTC benutzt
		adressen = LeseAlleAdressen();
		
		//diese Function holt sich nen neue Connection weil die aktuelle ja schon geschlossen ist
		//UND landet dadurch im DTC (keine Ahnung warum aber zeigt so der Profiler)
		LöscheAlleAdressen(adressen); 
												
		LöscheAllePersonenInOrt(LeseAllePersonen(),LeseAlleOrte()); 
	}
}

private void LöscheAlleAdressen(Adressen,con)
{
	using(GetConnection())
	{
		....
	}
}

private void LöscheAllePersonenInOrt(Personen,Orte)
{
	using(GetConnection())
	{
		....
	}
}

private DBConnection GetConnection()
{
	if(Conn geschlossen)
	{
		mach ma neue auf
	}
	else
	{
		gib ma aktuelle zurück
	}
}

Kann ja sein das ich da noch was nich ganz kapiert hab oder als alter C++ Freak die Dinge im alten Style benutze.
Aber an diesem Pseudo-Code iss ja nichts aufregendes an C# Stuff drann aber er zeigt ca. wie bei uns die abläufe sind.

Ich hoffe du erkennst was ich meine ... sonnst einfach noch mal fragen 😁

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

Und wo ist da jetzt das Problem?


public Adressen LeseAlleAdressen(DBHelper db)
{
    //Hier brauch ich ne EIGENE Connection
    //denn diese Function wird nicht nur  
    //von AdressenLöschen aufgerufen
    DBCommand cmd = db.GetConnection().CreateCommand();
        ....
}

public Personen LeseAllePersonen(DBHelper db)
{
    //Hier brauch ich ne EIGENE Connection
    //denn diese Function wird nicht nur  
    //von AdressenLöschen aufgerufen
    DBCommand cmd = db.GetConnection().CreateCommand();
    ...
}

public Orte LeseAlleOrte(DBHelper db)
{
    //Hier brauch ich ne EIGENE Connection
    //denn diese Function wird nicht nur  
    //von AdressenLöschen aufgerufen
    DBCommand cmd = db.GetConnection().CreateCommand();
    ...
}

private void LöscheAlleAdressen(DBHelper db,Adressen,con)
{
    DBCommand cmd = db.GetConnection().CreateCommand();
    ...
}

private void LöscheAllePersonenInOrt(DBHelper db,Personen,Orte)
{
    DBCommand cmd = db.GetConnection().CreateCommand();
    ...
}


public void AdressenLöschen()
{
    using(DBHelper db = new DBHelper(Connstring))
    {
        adressen = LeseAlleAdressen(db);

        LöscheAlleAdressen(db, adressen);

        LöscheAllePersonenInOrt(db, LeseAllePersonen(db),LeseAlleOrte(db));
    }
}



Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

okay das Problem ist:

ich kann auf keinen Fall sowas machen wie


public Adressen LeseAlleAdressen(DBHelper db)
{
    //Hier brauch ich ne EIGENE Connection
    //denn diese Function wird nicht nur  
    //von AdressenLöschen aufgerufen
    DBCommand cmd = db.GetConnection().CreateCommand();
        ....
}

da diese Function über nen Interface (alles schön im Westphal Softwarezellen Style 😉 ) nach draußen geht und der Aufrufer draußen überhaupt keinen DBHelper hat, diesen also auch nicht reingeben kann! Somit muss sich die also die Funktion die Connection intern besorgen. Was (nach meinen Ideen) nur so geht wie ichs beschrieben hab.

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

Und warum ist das so?

Ich könnte jetzt haufenweise was zu solcher Architektur schreiben,
da mir aber der Hintergrund fehlt, warum ihr das so implementiert habt,
kann ich dir auch nicht mehr dazu sagen.

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

Ich zitiere mal aus Golo Rodens Blog:

"Softwarezellen sind ein Konzept von Ralf Westphal, mit dessen Hilfe sich die Architektur einer Anwendung auf jeder Granularitätsebene darstellen lässt, und das etliche der gängigen Nachteile des klassischen Schichtenmodells vermeidet. Nähere Informationen hierzu finden sich in Ralfs Blog unter dem Tag Software Cells."
Hier zum Lesen

Es gibt sicher viel andere (mehr oder weniger) tolle Möglichkeiten Software Design zu machen. Wir haben uns dafür entschieden und ich muss sagen ich hab echt noch nie sooo geil Software entwickelt wie damit.... Ich könnte noch stundenlang weiter schwärmen 😁

Okay iss aber nen anderes Thema sonnst kriegen wa gleich wieder Golos Eiserne Faust zu spüren 😁

Back to the Thema: Nun haben wir also diese Gegebenheiten. Was könn wir tun? Wird unter diesen Umständen ein verhindern des DTC's möglich sein ?(

Howard

F
10.010 Beiträge seit 2004
vor 15 Jahren

Also ich persönlich halte zwar nichts von Ralfs Zellen, aber auch mit seinem
Ansatz ist es für mich nicht nachvollziebar, warum das nicht so möglich sein soll.

  1. In der DLL, die die allgemeinen Interfaces hat, kommt eben eine fürs Repository
    also der IRepository ( DBHelper ) Schnittstelle mit weiteren Funktionen wie GetList usw.

  2. Dieses Repository wird jeder Funktion die irgendwas mit Datenbanken machen will
    eben mit übergeben ( auch bei Ralf sind übergabeparameter erlaubt ), oder

  3. z.b. durch ein MicroKernel/DI Container/Service Locator bereitgestellt.

Dadurch hast Du jeglich DB Aktionen Weggekapselt, kannst alles gut testen, und ggf
"einfach" eine andere DB benutzen.

P.S.:
Golo ist hier "nur noch" ein normales Mitglied.

Howard Themenstarter:in
84 Beiträge seit 2007
vor 15 Jahren

Okay sorry war ne Woche nicht da deswegen erst jetzt die Antwort.
Ich hab das mal kurz mit Ralf Westphal durch Diskutiert:

Die Transaktions sind auf jeden Fall von außen zu steuern wie du schon gesagt hast.
andererseits sagt er "vorsicht bei premature optimization" und verweist auf CleanCodeDeveloper
Klar hatt er Recht allerdings musste ich das ganze dennoch IRGENDWIE zum laufen bekommen.

Also ich danke dir für die Ausdauer und würde sagen bis aufs nächste Problem 😁

Howard