none
"Already an open datareader assiciated with this connection" Problem RRS feed

  • Allgemeine Diskussion

  • Hallo zusammen,

    eine Windows Forms Anwendung sieht in etwa so aus:

    1) ein "Haupt-" Grid
    2) weitere "Detail-" Grids mit den Details zu dem in 1) selektierten Datensatz (aus anderen Tabellen, mit teils sehr komplexer Select-Logik)

    Die Grids sind an eigene DataTables gebunden. Über das "CurrentChanged"-Event (CurrencyManager) werden die "Detail-" Grids jweiles neu befüllt, wenn sich der Datensatz im "Haupt-" Grid ändert. Für das Befüllen des "Haupt-" Grids und der anderen Grids wird ein und dieselbe DB-Connection verwendet.

    Funktioniert auch soweit ganz gut. Nun muss das Hauptgrid allerdings alle 10 Sekunden automatisch aktualisiert werden, was momentan über einen "Timer.tick" abläuft.

    Hier klappts dann nicht mehr, sobald die DataAdapter.Fill Methode für eines der "Child"-Grids ausgeführt wird: "There is already an open datareader assiciated with this connection, which must be closed first."

    Theoretisch läuft ja Folgendes ab:

    1. DataAdapterHaupt.Fill(DataTableHaupt)
    2. CurrencyManagerHaupt.CurrentChanged -> wird ausgelöst
    3. DataAdapterDetail1.Fill(DataTableDetail1) -> Fehler "Open Datareader"

    Wenn ich "manuell" den aktuellen Datensatz im Hauptgrid wechsle (Durch Klicken in andere Zeile), klappts, obwohl dabei ja eigentlich auch die obigen 3 Schritte ablaufen!? Liegt also irgendwie daran, dass es nicht klappt, wenn der ganze Vorgang "vom Timer.tick initiiert wurde", kann das sein?

    Schafft hier ein BackgroundWorker abhilfe?? Und falls ja, wie stellt man das an, da die Objekte ja wowohl vom Backgroundworker als auch vom Frontend / User Interface verwendet werden??? Habe mal gelesen, hier müsste man trennen...

    Lieben Gruß

    Jan

    Donnerstag, 30. Dezember 2010 07:55

Alle Antworten

  • Hallo Jan

    schließt du die Connection auch wieder nach dem du den Datareader gestartet hast ?

    Dim tmpDR As SqlClient.SqlDataReader
    Using tmpCon As New SqlClient.SqlConnection(ConStr)
     '
     Using tmpCmd As New SqlClient.SqlCommand("Select * from Tabelle", tmpCon)
      '
      tmpDR = tmpCmd.ExecuteReader()
      '
     End Using
     '
    End Using

     

    Donnerstag, 30. Dezember 2010 08:58
  • Hallo Jan,

    in der Standard-Einstellung lässt eine SqlConnection nur einen einzigen aktiven DataReader zu -
    und ein SqlDataAdapter verwendet einen solchen.

    Vordergründig kannst Du das Problem vermeiden, in dem Du MARS (Multiple active Resultsets) aktivierst,
    siehe Bearbeiten von Daten (MARS).

    In Deinem Falle wäre das nur eine vordergründige Lösung. Denn Du kannst durch Deinen Timer
    weitere Probleme bekommen, wenn sich wirkliche Änderungen an den Tabellen ergeben.
    Denn der "überlappende" DataReader ist nur ein Symptom dafür, dass die Abrufe sich bei
    den DataTables überschneiden. Was wiederum zu inkonsistenten Datenständen führen kann.
    (Und sich durch Ausnahmen beim DataSet. EnforceConstraints bemerkbar machen wird.)

    Zunächst solltest Du Dich fragen, ob zehn Sekunden nicht etwas viel des Guten ist.
    Finden wirklich viele Aktivitäten statt, so wird das bei echtem Mehrplatzbetrieb wiederum
    zu Blockierungen bis hin zu Deadlocks führen (und das verschärft die Sachlage nur).

    Auf Codeseite solltest Du sicherstellen, dass der Code für das Abrufen immer vollständig
    ausgeführt wird. Und mehrfaches Aufrufen durch Timer oder manuelle Akualisierung nicht
    stattfinden kann. Eine boolsche Variable reicht im allgemeinn schon.

    Gruß Elmar

     

    Donnerstag, 30. Dezember 2010 09:05
    Beantworter
  • Hi,

    naja, nicht wirklich. Es wird ein DataAdapter verwendet und der bekommt eine Connection mit, die immer offen ist... Ein explizites / von mir gesetztes .ExecuteReader gibts daher gar nicht...

    Gruss, Jan

    Donnerstag, 30. Dezember 2010 09:29
  • Hallo Elmar,

     

    toll, wie kann man bei diesen blöden webbasierte Foren denn gescheit antworten, also wie bei einem Newsreader???

    MARS (MultipleActiveResultSets=True) hatte ich schon versucht, aber das klappt nicht da MySQL zugrundeliegt...

    Was ich nicht verstehe: Ich habe ja keine einzelnen Threads, wieso können da also mehrere DRs gleichzeitig bei einer Connection vorkommen? Dachte, hier würde immer "nacheinander" abgearbeitet werden? Aber scheinbar wird das "CurrentChanged" schon ausgelöst, WÄHREND der DataAdapterHaupt.Fill noch läuft?

    Alternative wäre, für die einzelnen Grids / DataAdapter jeweils eine eingene Connection zu verwenden, aber ist das gut?

    Bzgl. der 10 Sekunden: Ja, das muss sein ;) In Zukunft muss evtl. noch eine Lösung her, die "noch mehr" kann... Weiss zwar noch nicht wie, aber irgendwie wollte ich die Daten serverseitig in einem Service vorhalten, und diesen Service dann von den Clients "direkt" anzapfen (Sockets, etc.?). So dass nicht mehr jeder einzelne Client die Daten direkt von der Datenbank abfragt...
    Meine Theorie "Idee" wäre hier, eine Art "serverseitigen" DataTable zu erzeugen (im Arbeitsspeicher des Servers, der wird alle 5 Sekunden vom Server aus der DB befüllt), und diesen dann über TCP/IP zu "binden". Also z.B. ein Grid, dessen DataSource ein über Netzwerk gebundenes Objekt ist. Geht sowas???

    Danke und Grüße, Jan

    Donnerstag, 30. Dezember 2010 09:53
  • Hallo Jan,

    zur Antwort vorher:
    Ein DataAdapter führt intern ein ExecuteReader aus, auch wenn Du das nicht siehst -
    darauf hatte ich bereits hingewiesen.

    MARS betrachte ich ebenfalls wie gesagt nicht als wirkliche Lösung -
    genausowenig sind das mehrere Verbindungen.

    Denn wenn die DataAdapter überlappen, kommt das DataSet intern
    mit  seiner Verwaltung über kurz oder lang durcheinander
    und Dir "fliegt" eine andere Ausnahme um die Ohren.

    Du solltest Dich darauf konzentrieren, nur einen Abruf zu gleicher Zeit durchzuführen.
    was über eine zentrale Methode möglich sein sollte.

    Was die 10 Sekunden (oder schneller angeht), solltest Du Dich fragen:
    Kannst das auch noch jemand lesen?
    Wenn am Ende nur der Bildschirm zappelt, ist das IMO sinnfrei.

    Gruß Elmar

    P. S.:  Wenn Du einen Newsreader gewöhnt bist, schau Dir die Community Forums NNTP bridge an.
    Damit kannst Du auf die MSDN und Answers Foren über einen Newsreader (Thunderbird usw.) zugreifen und antworten.

    Donnerstag, 30. Dezember 2010 15:30
    Beantworter
  • Hi Elmar,

    danke für die Antwort!

    Ja, habe jetzt einen Workaround, so dass nicht mehrere Abfragen gleichzeitig stattfinden. In diesem Zusammenhang ist dennoch Folgendes interessant: Bei einem CurrencyManager der entsprechenden BoindingSource wird das "CurrentChanged" Events mehrmals ausgelöst, wenn ein DataAdapter.Fill(DataTable) für den gebundenen DataTable ausgeführt wird. Wieso bzw. lässt sich das umgehen?

    Bzgl. der 10 Sekunden: Gut, vielleicht reichen auch 20 oder 30 Sekunden. Künftig müssen in der Applikation aber sich in kurzen Intervallen ändernde Daten (die durch eine recht komplexe Logik erzeugt werden) dargestellt werden, und das würde im Chaos enden (Performance), wenn jeder Client, um an diese Daten zu kommen, selbst direkt auf die Datenbank zugreift. Daher dachte ich, irgendwie muss ein "Databinding" doch auf übers Netz funktionieren, so dass mehrere Clients direkt auf einen "DataTable" zugreifen, der im Arbeitsspeicher des Servers ist...?

    Oder wie machen das andere Applikationen wie z.B. das Bloomberg Terminal? Dieses ist weltweit verbreitet, und Kursänderungen etc. sieht man sogut wie ohne Verzögerung auf dem Screen. Ich glaube nicht, dass hier jeder Client selbst die Datenbank abfragt...

    Grüße, Jan

    P.S. NNTP Bridge ist prima, danke ;) Muss meinem NR nur noch HTML Format beibringen...

    Donnerstag, 30. Dezember 2010 17:13
  • Hi Jan,
    Das Ereignis wird nur ausgelöst, wenn die gebundene Sicht neu erstellt
    wird. Wenn nur ein Refresh durchgeführt wird, wird bei mir kein Ereignis
    CurrentChanged ausgelöst. Zwischenzeitlich gelöschte Datensätze werden beim
    Refresh aber nicht erkannt.
     
    Jeder Client, der Daten benötigt, holt sich diese über das Netz und belastet
    damit das Netz. Je nachdem, wie der serverseitige Aufwand zu bewerten ist,
    kann es ausreichend sein, dass jede Anfrage direkt durch den Datenbankserver
    bedient wird. Wenn dieser Aufwand aber zu groß wird (z.B. komplexe
    Datenbankabfragen), sollte über Caching nachgedacht werden, z.B. über
    WebServices. Der WebService ruft regelmäßig die Datenbank ab und legt die
    aktualisierten Daten in einen Cache, aus dem dann für jeden Abfrage die
    daten bereitgestellt werden.
     
    --
    Viele Grüße
    Peter
     
     
    Freitag, 31. Dezember 2010 08:26
  • Hallo Jan,

    naja, ich bezweifele das Bloomberg mit DataSets arbeitet und oder auch mit Datenbindung,
    da dürfte ein komplexeres Modell verwendet werden, um eine effiziente Aktualisierung zu erreichen.

    Wenn man mit den Windows Forms Bordmitteln auskommen muss:

    Bei der Datenbindung hängt letztendlich alles an der IBindingList (via DataView).
    Und das CurrentChanged wird bei jeder Benachrichtigung über das dortige ListChanged Ereignis ausgelöst.
    Bei größeren Änderungen (via Fill etc.) kommt es auch zum ListChangedType .Reset.

    Um solche Zwangs-Aktualisierungen zu minimieren, solltest Du nicht Brute Force die gesamte DataTable aktualisieren.
    Möglich wäre u. a. ein Abrufen einer getrennten DataTable und ein Zusammenführen mit der gebundenen
    via DataTable.Merge oder DataSet.Merge - letzteres wäre für Master/Detail Beziehungen zu bevorzugen.

    Will man es ganz exakt machen und nur geänderte Spalten (ala Ticker) aktualisieren,
    kommt man aber um eine eigene Lösung manchmal nicht drum herum, die Einzelwerte
    vergleicht und nur dort Änderungen vornimmt, wo sie reell vorgekommen sind.

    Gruß Elmar

    P. S.: Die NNTP Community Bridge arbeitet auch im Textmodus, siehe Markup Guide
    Zum Fein-Tuning der Einstellungen kannst Du das Test-Forum abbonnieren.

     

    Freitag, 31. Dezember 2010 10:06
    Beantworter
  • Hi Peter,

    Das Ereignis wird nur ausgelöst, wenn die gebundene Sicht neu erstellt wird. Wenn nur ein Refresh durchgeführt wird, wird bei mir kein Ereignis CurrentChanged ausgelöst. Zwischenzeitlich gelöschte Datensätze werden beim Refresh aber nicht erkannt.

    Refresh? Welches Objekt meinst Du, was ein .Refresh hat? Stehe gerade auf dem Schlauch ;)

    Jeder Client, der Daten benötigt, holt sich diese über das Netz und belastet
    damit das Netz. Je nachdem, wie der serverseitige Aufwand zu bewerten ist, kann es ausreichend sein, dass jede Anfrage direkt durch den Datenbankserver
    bedient wird. Wenn dieser Aufwand aber zu groß wird (z.B. komplexe Datenbankabfragen), sollte über Caching nachgedacht werden, z.B. über WebServices. Der WebService ruft regelmäßig die Datenbank ab und legt die aktualisierten Daten in einen Cache, aus dem dann für jeden Abfrage die daten bereitgestellt werden.

    Das mit dem Caching ist ja in etwa das was mir auch einfiel, genau. Die Sache, die mich dabei interessiert, ist allerdings Foglende: Gibt es eine Alternative dazu, dass die Clients diesen Cache selbst "abfragen" ("Pull")? Also eine ("Push" ist vielleicht auch nicht das richtige Wort) Variante, so dass Änderungen im serverseitigen Cache automatisch bei den Clients ankommen? Das habe ich etwas ungeschickt als "Databinding ber Netzwerk" bezeichnet ;) Bei einer Winforms Anwendung ist es ja auch so, dass, wenn zwei Controls an den gleichen DataTable gebunden sind, eine Änderung im einen Control direkt im anderen Control zu sehen ist...

    LG, Jan

    Freitag, 31. Dezember 2010 12:36
  • Hallo Elmar,

    naja, ich bezweifele das Bloomberg mit DataSets arbeitet und oder auch mit Datenbindung, da dürfte ein komplexeres Modell verwendet werden, um eine effiziente Aktualisierung zu erreichen.

    Klar, das bezweifle ich auch ;) Habe auch nicht vor, das Terminal "nachzubauen". Mir geht es ja nur um einen theoretischen Ansatz bzw. Ansatzfindung / Ideenfindung.

    Wenn man mit den Windows Forms Bordmitteln auskommen muss:

    Bei der Datenbindung hängt letztendlich alles an der IBindingList <http://msdn.microsoft.com/de-de/library/system.componentmodel.ibindinglist.aspx> (via DataView). Und das CurrentChanged wird bei jeder Benachrichtigung über das dortige ListChanged Ereignis ausgelöst. Bei größeren Änderungen (via Fill etc.) kommt es auch zum ListChangedType <http://msdn.microsoft.com/de-de/library/system.componentmodel.listchangedtype.aspx> .Reset.

    Danke, die Zusammenhänge werde ich mir einmal genauer anschauen.

    Um solche Zwangs-Aktualisierungen zu minimieren, solltest Du nicht Brute Force die gesamte DataTable aktualisieren. Möglich wäre u. a. ein Abrufen einer getrennten DataTable und ein Zusammenführen mit der gebundenen via DataTable.Merge <http://msdn.microsoft.com/de-de/library/system.data.datatable.merge.aspx> oder DataSet.Merge <http://msdn.microsoft.com/de-de/library/system.data.dataset.merge.aspx> - letzteres wäre für Master/Detail Beziehungen zu bevorzugen.

    Das auch ;)

    Will man es ganz exakt machen und nur geänderte Spalten (ala Ticker) aktualisieren, kommt man aber um eine eigene Lösung manchmal nicht drum herum, die Einzelwerte vergleicht und nur dort Änderungen vornimmt, wo sie reell vorgekommen sind.

    Hört sich nach viel Arbeit an ;)

    P. S.: Die NNTP Community Bridge arbeitet auch im Textmodus, siehe Markup

    Wenn man die offizielle Version von MS benutzt sucht man nach dieser Einstellung vergeblich ;) Jetzt bin ich aber glücklich :)

    LG, Jan 

    Freitag, 31. Dezember 2010 13:20
  • Du hast geschrieben "... 1. DataAdapterHaupt.Fill(DataTableHaupt) ...". Mit
    MissingSchemaAction.AddWithKey wird ein Refresh das clientseitigen
    Datenpuffers mit den Daten aus der Datenbank entsprechend PK ausgeführt.
    Dieses Refresh bewirkt kein CurrentChanged des BindingManagers.
     
    Wenn Du willst, dass die Clients über Ereignisse informiert werden, musst Du
    eine zur Infrastruktur passende Lösung erarbeiten. Das kann beispielsweise
    MSMQ, Sockets mit Registrierung in einer Server-Anwendung, Remoting,
    NamedPipes usw. sein. Für jede genutzte Technologie gibt es zu beachtende
    Besonderheiten, z.B. arbeitet Remoting die Ereignisse synchron ab und der
    Ausfall eines Clientes führt zu starken Verzögerungen wegen Timeout. Das von
    Dir genannte NotifyPropertyChanged für die synchrone Anzeige mehrerer UI mit
    einer gemeinsamen Datenquelle ist nur innerhalb einer Anwendung nutzbar.
     
    Ich würde die Lösung erst einmal mit dem einfachen clientseitigem Polling
    erstellen und dann überlegen, welche Schichten an Infrastruktur und
    Datenvolumen besser anzupassen sind.
     
    --
    Viele Grüße
    Peter
     
     
    Freitag, 31. Dezember 2010 17:10
  • Hallo,

    eine Windows Forms Anwendung sieht in etwa so aus:

    1) ein "Haupt-" Grid
    2) weitere "Detail-" Grids mit den Details zu dem in 1) selektierten
        Datensatz (aus anderen Tabellen, mit teils sehr komplexer
        Select-Logik)

    Die Grids sind an eigene DataTables gebunden. Über das
    "CurrentChanged"-Event (CurrencyManager) werden die
    "Detail-" Grids jweiles neu befüllt, wenn sich der Datensatz im
    "Haupt-" Grid ändert.

    Was meinst Du mit "wenn sich der Datensatz im Haupt-Grid ändert?
    Der geeignete Zeitpunkt zum Erstellen einer DetailView wäre das
    PositionChanged-Ereignis des CurrencyManagers.

    Die log. Verknüpfung von Master- und Detailtabellen lässt sich
    über Relation-Objekte herstellen. Die zum jeweils ausgewählten
    Master-Datensatz passende DataView wird dann in
    CurencyManager_PositionChanged via

        DataViewDetail = DataViewMaster.CreateChildView(DataSet.Relations(x))

    erstellt und dem entsprechenden Grid als DataSource angeboten.

    Beispiele für Master-Dateil und Master-Dateil-Subdetail Ansichten
    mit 2 bzw. 3 DataGridViews findest Du unter

        www.gssg.de - > Visual Basic -VB.net
            -> DataGridView
                -> DataGridView Relation (Rev:01)
                -> DataGridView Relation 3

    Für das Befüllen des "Haupt-" Grids und der anderen Grids
    wird ein und dieselbe DB-Connection verwendet.

    Die Grids sollten eigentlich erst mal nichts mit der DB-Connection
    zu tun haben. Befüllen wirst Du vermutlich DataTables, deren
    zugehörige DataViews dann als DataSources für die Grids
    dienen.

    Gruß aus St.Georgen
    Peter Götz
    www.gssg.de (mit VB-Tipps u. Beispielprogrammen)

    Montag, 3. Januar 2011 17:52
  • Hi Peter,
     
    > Du hast geschrieben "... 1. DataAdapterHaupt.Fill(DataTableHaupt) ...". Mit
    > MissingSchemaAction.AddWithKey wird ein Refresh das clientseitigen
    > Datenpuffers mit den Daten aus der Datenbank entsprechend PK ausgeführt.
    > Dieses Refresh bewirkt kein CurrentChanged des BindingManagers.
     
    Das verstehe ich nicht: Das "CurrentChanged" wird doch immer ausgelöst,
    wenn die Daten neu in den Table geladen werden...? Das hat doch nichts
    mit "MissingSchemaAction.AddWithKey" zu tun... Da finde ich auch nichts
    auf MSDN...
     
    > Wenn Du willst, dass die Clients über Ereignisse informiert werden, musst Du
    > eine zur Infrastruktur passende Lösung erarbeiten. Das kann beispielsweise
    > MSMQ, Sockets mit Registrierung in einer Server-Anwendung, Remoting,
    > NamedPipes usw. sein. Für jede genutzte Technologie gibt es zu beachtende
    > Besonderheiten, z.B. arbeitet Remoting die Ereignisse synchron ab und der
    > Ausfall eines Clientes führt zu starken Verzögerungen wegen Timeout. Das von
    > Dir genannte NotifyPropertyChanged für die synchrone Anzeige mehrerer UI mit
    > einer gemeinsamen Datenquelle ist nur innerhalb einer Anwendung nutzbar.
     
    OK, zum Glück muss das noch nicht zwingend umgesetz werden, aber die
    Vorstellung reizt mich. Das wird aber dann ein gutes Stück Arbeit /
    Einarbeitung...
     > Ich würde die Lösung erst einmal mit dem einfachen clientseitigem Polling
    > erstellen und dann überlegen, welche Schichten an Infrastruktur und
    > Datenvolumen besser anzupassen sind.
     
    Ja, klingt nach einer guten Vorgehensweise :) Schritt für Schritt...
     
    Gruss, Jan
     
    Dienstag, 4. Januar 2011 22:47
  • Hi Jan,
    ich habe das gerade getestet. "CurrentChanged" wird bei mir nicht ausgelöst,
    wenn ich ein Refresh ausführe (MissingSchemaAction.AddWithKey).
     
    --
    Viele Grüße
    Peter
     
     
    Mittwoch, 5. Januar 2011 10:46