Fragensteller
"Already an open datareader assiciated with this connection" Problem

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- Typ geändert Robert BreitenhoferModerator Freitag, 21. Januar 2011 14:59 Keine Rückmeldung des Fragenstellender
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
- Bearbeitet Robert BreitenhoferModerator Freitag, 21. Januar 2011 14:53 Formatierung
-
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
-
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
-
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. -
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...
-
Hi Jan,Das Ereignis wird nur ausgelöst, wenn die gebundene Sicht neu erstelltwird. Wenn nur ein Refresh durchgeführt wird, wird bei mir kein EreignisCurrentChanged ausgelöst. Zwischenzeitlich gelöschte Datensätze werden beimRefresh aber nicht erkannt.Jeder Client, der Daten benötigt, holt sich diese über das Netz und belastetdamit das Netz. Je nachdem, wie der serverseitige Aufwand zu bewerten ist,kann es ausreichend sein, dass jede Anfrage direkt durch den Datenbankserverbedient wird. Wenn dieser Aufwand aber zu groß wird (z.B. komplexeDatenbankabfragen), sollte über Caching nachgedacht werden, z.B. überWebServices. Der WebService ruft regelmäßig die Datenbank ab und legt dieaktualisierten Daten in einen Cache, aus dem dann für jeden Abfrage diedaten bereitgestellt werden.--Viele GrüßePeter
-
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. -
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
-
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
-
Du hast geschrieben "... 1. DataAdapterHaupt.Fill(DataTableHaupt) ...". MitMissingSchemaAction.AddWithKey wird ein Refresh das clientseitigenDatenpuffers 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 Dueine zur Infrastruktur passende Lösung erarbeiten. Das kann beispielsweiseMSMQ, Sockets mit Registrierung in einer Server-Anwendung, Remoting,NamedPipes usw. sein. Für jede genutzte Technologie gibt es zu beachtendeBesonderheiten, z.B. arbeitet Remoting die Ereignisse synchron ab und derAusfall eines Clientes führt zu starken Verzögerungen wegen Timeout. Das vonDir genannte NotifyPropertyChanged für die synchrone Anzeige mehrerer UI miteiner gemeinsamen Datenquelle ist nur innerhalb einer Anwendung nutzbar.Ich würde die Lösung erst einmal mit dem einfachen clientseitigem Pollingerstellen und dann überlegen, welche Schichten an Infrastruktur undDatenvolumen besser anzupassen sind.--Viele GrüßePeter
-
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 viaDataViewDetail = 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 unterwww.gssg.de - > Visual Basic -VB.net
-> DataGridView
-> DataGridView Relation (Rev:01)
-> DataGridView Relation 3Fü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) -
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 nichtsmit "MissingSchemaAction.AddWithKey" zu tun... Da finde ich auch nichtsauf 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 dieVorstellung 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