Benutzer mit den meisten Antworten
Select WHERE IN mit 4000 IDs

Frage
Antworten
-
Hallo Peter,
Zum einen: Für 4000 Zeilen sollte es keine 20 Sekunden dauern, alles über einer Sekunde dürfte auf anderem Wege zustande kommen.
Was Dir das im Wege stehen könnte ist die Datenbindung, die während der Aktion prüft. ob sie ihre Daten zu aktualisieren muss (oder es gar tut). Wenn Du eine BindingSource verwendest, klemme sie solange via SuspendBinding ab und stelle sie am Ende via ResumeBinding wieder her.
Zum zweiten: Wie hier schon mehrfach gesagt, sollte man größere Mengen via IN (...) eher meiden. Mit SQL Server 2008 R2 und später gibt es die Möglichkeit über Tabellenwertparameter gleiches zu erreichen.
Ein Beispiel für die AdventureWorks, wobei hier die Detail Daten zu einer größeren Anzahl (siehe Kommentar) abgerufen werden:
Imports System.Data.SqlClient Imports System.Text ''' <summary> ''' Erforder einen Tabellen Wert Typ in der AdventureWorks: ''' CREATE TYPE dbo.SalesOrderTableType AS TABLE ( SalesOrderID int PRIMARY KEY); ''' </summary> Public Class SqlTableParameter Public Shared Sub Run() Dim c As New SqlTableParameter() c.SelectWithInClause() c.SelectWithTableParameter() End Sub Public Sub SelectWithTableParameter() Dim orderTable = GetSalesOrders() ' hier: 4594 Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count) Dim detailsTable = SelectOrderDetailsWithTableType(orderTable) ' hier 16865 Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count) End Sub Public Sub SelectWithInClause() Dim orderTable = GetSalesOrders() ' hier: 4594 Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count) Dim detailsTable = SelectOrderDetailsWithInClause(orderTable) ' hier 16865 Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count) End Sub Private Function GetSalesOrders() As DataTable Dim watch = Stopwatch.StartNew() Dim table As New DataTable("SalesOrderHeader") table.Columns.Add("SalesOrderID", GetType(Integer)) Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString) Using adapter = New SqlDataAdapter( "SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE TerritoryID = 1", connection) adapter.Fill(table) End Using End Using Console.WriteLine("GetSalesOrders: {0} ms", watch.ElapsedMilliseconds) Return table End Function Private Function SelectOrderDetailsWithInClause(orderTable As DataTable) As DataTable Dim watch = Stopwatch.StartNew() Dim inBuilder As New StringBuilder(orderTable.Rows.Count * 6) For Each row As DataRow In orderTable.Rows inBuilder.Append(CInt(row("SalesOrderID")).ToString(System.Globalization.CultureInfo.InvariantCulture)) inBuilder.Append(","c) Next inBuilder.Length -= 1 Dim selectSql = "SELECT * FROM Sales.SalesOrderDetail AS Sod " & _ "WHERE SalesOrderID IN ( " & inBuilder.ToString() & ");" 'Console.WriteLine("SelectSQL Length: {0}, Text: '{1}'", selectSql.Length, selectSql) Dim table As New DataTable("SalesOrderDetail") Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString) Dim selectCommand = New SqlCommand(selectSql, connection) Using adapter = New SqlDataAdapter(selectCommand) adapter.MissingMappingAction = MissingMappingAction.Passthrough adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey adapter.Fill(table) End Using End Using Console.WriteLine("SelectOrderDetailsWithInClause: {0} ms", watch.ElapsedMilliseconds) Return table End Function Private Function SelectOrderDetailsWithTableType(orderTable As DataTable) As DataTable Dim watch = Stopwatch.StartNew() Dim table As New DataTable("SalesOrderDetail") Dim selectSql = "SELECT sod.* FROM Sales.SalesOrderDetail AS sod " & _ "INNER JOIN @SalesOrderIds AS soi ON sod.SalesOrderID = soi.SalesOrderID;" Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString) Dim selectCommand = New SqlCommand(selectSql, connection) ' DataTable als Parameter Dim tableParameter = selectCommand.Parameters.AddWithValue("@SalesOrderIds", orderTable) tableParameter.SqlDbType = SqlDbType.Structured tableParameter.TypeName = "dbo.SalesOrderTableType" Using adapter = New SqlDataAdapter(selectCommand) adapter.MissingMappingAction = MissingMappingAction.Passthrough adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey adapter.Fill(table) End Using End Using Console.WriteLine("SelectOrderDetailsWithTableType: {0} ms", watch.ElapsedMilliseconds) Return table End Function End Class
Die Laufzeit ist so ziemlich gleich, aber hat nicht mehr das Problem einer elendig langen SELECT Klausel, die doch irgendwann ans Ende kommt.
Gruß Elmar
- Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:04
-
Hallo Peter,
setze zudem RaiseListChangedEvents auf False, denn durch den Filter werden sonst immer noch reichlich Ereignisse verschickt.
Die Zeiten (für alle):
Rows loaded 19973 ' Ohne RaiseListChangedEvents Time spent: Choose=True => 2612 ms
Time spent: Choose=False => 1669 ms ' Mit RaiseListChangedEvents Time spent: Choose=True => 12463 ms
Time spent: Choose=False => 26722 ms(Das Zappeln in der Titelzeile war dabei abgestellt, kostet auch ein, zwei Sekunden).
Wobei ich noch rätsele, warum False (Standard) doppelt solange braucht wie True bei aktiven Ereignissen (und umgekehrt wenn aus); bei angeglichener Codestrecke:
Public Class ContactForm Private Sub ContactForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim rowCount = Me.ContactTableAdapter.Fill(Me.AdventureWorksDataSet.Contact) Console.WriteLine("Rows loaded {0}", rowCount) ContactBindingSource.Filter = "Choose = True" End Sub Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click SetChooseState(True) End Sub Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click SetChooseState(False) End Sub #Const SuppressListChanged = False Private Sub SetChooseState(chooseState As Boolean) Dim watch = Stopwatch.StartNew() Me.ContactBindingSource.SuspendBinding() #If (SuppressListChanged) Then Me.ContactBindingSource.RaiseListChangedEvents = False #End If Try For Each row In Me.AdventureWorksDataSet.Contact row.Choose = chooseState 'Me.Text = String.Format("Choose {0} ({1})", chooseState, row.ContactID) Next Finally #If (SuppressListChanged) Then Me.ContactBindingSource.RaiseListChangedEvents = True Me.ContactBindingSource.ResetBindings(False) #End If Me.ContactBindingSource.ResumeBinding() End Try watch.Stop() Debug.WriteLine("Time spent: Choose={0} => {1} ms", chooseState, watch.ElapsedMilliseconds) End Sub End Class
Gruß Elmar- Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:00
Alle Antworten
-
Hallo Peter,
schau mal hier:
Bei derart vielen Werten ist es meist sinnvoller, die Werte in eine Liste zu stecken (Tablevariable, temp. Tabelle, ...) und die dann in der IN Klausel abzufragen, also bspw.:
SELECT ... FROM ... WHERE ... IN ( SELECT ... FROM <Liste> )
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community
- Bearbeitet Stefan FalzModerator Montag, 4. Februar 2013 12:14
-
Hallo Peter,
bis zu bestimmten Grenzen geht das. Aber dennoch wirst Du wahrscheinlich irgendwann auf einen Fehler laufen. Daher wäre es trotzdem sinnvoll, mit einer anderen Liste zu arbeiten
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community -
Am 04.02.2013 12:11, schrieb peter haus:> Hallo zusammen,>> mal ne Frage:> spricht irgend etwas grundsätzlich dagegen, wenn ein Select Statement> abgesetzt wird, in dem ein WHERE id IN mit 4000 IDs vorhanden ist?Evtl die Länge des SQL-Statements? Das Statement bekommt ja eineZeichenlänge im mittleren 5-stelligen Bereich. Keine Ahnung inwieweit daBeschränkungen bei den Strings oder beim SQL bestehen.Davon abgesehen sollte man IN () eigentlich vermeiden. Ein Join gegeneine Tabelle mit den 4000 IDs sollte deutlich schneller sein!
-
Am 04.02.2013 15:44, schrieb peter haus:> Vielen Dank für Eure Antworten.>> Kann ich mich um eine zusätzliche Tabelle (Liste) drücken, indem ich>> Select * From table Where Col IN (123,123,222,....) or Col IN> (456,878,888,....)> verwende?Warum willst du dich denn um eine zusätzliche Tabelle drücken? Wo hastdu da Schwierigkeiten?Je nachdem welche Datenbank du nutzt und wie die genaue Aufgabenstellungist, hast du auch noch andere Möglichkeiten - temporäre Tabellen,Tabellenwertfunktionen u.s.w.Wenn du das Problem etwas genauer erläuterst, kann man dir vielleichtauch bessere Alternativen aufzeigen.
-
Hallo Stefan
herzlichen Dank für Deine Antwort.
Ich habe mir die Links angeschaut.
Bin mir aber in der Fülle an Infos unsicher.Kann ich auf dem SQL Server einfach eine Liste mit einer Spalte Guids anlegen,
diese vor dem neu befüllen mit einem Delete * From Liste löschen?Gruss Peter
-
Hallo Peter,
das könntest Du, wenn Du sicher bist, dass nur ein einziger Aufruf/Zugriff gleichzeit erfolgen kann. Sobald das nicht 100%ig gegeben ist, musst Du mit einer eigenen Liste für den jeweiligen Aufruf arbeiten. Welche Option für dich in Frage kommt, hängt u.a. davon ab, wie genau die Daten wo und wann erzeugt und in die Liste geschrieben werden sollen. Das einfachste (nicht unbedingt das beste) wäre wohl, einfach eine temporäre Tabelle zu erzeugen und deine Werte dort reinzuschreiben.
Die Spalte "Guids" sagt mir, dass Du mehrere Werte auf einmal dort ablegen willst. Das wiederum geht nicht, sondern Du musst einen Wert pro Zeile anlegen.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community -
Hi Stafan,
ich dachte daran, eine Spalte GUID und darin n Datansätze ablegen, genau so viele wie ich in der IN Abfrage durch Komma separierte IDs habe.
Da nehrere Benutzer zugreifen können, brauche ich einen Liste.
Hättest du grade eine Link parat wie das gemacht wird?
Danke
Gruss PeterPS. Schaust du mal Bitte auf meine Antwort an LutzChemnitz.
- Bearbeitet peter haus Dienstag, 5. Februar 2013 09:10
-
Hallo Lutz,
ich versuche mein Problem zu schildern.
Auf dem GUI habe ich einen TreeView mit Checkbox der Kunde->Projekte anzeigt.
Projekte können checked sein oder nicht. Es sind ca. 4000 Projekte.
Zu allen Projekten die checked sind, werden in einem Grid die darauf angefallenen Leistungen angezeigt.
In der DataTable Projekt und Leistungen gibt es eine ungebundene Spalte "choose", die ich in Bindingsource.Filter verwende um die Leistungen anzuzeigen oder nicht.Wenn im Tree ein Projekt checked gesetzt wird, wird rowProjekt.chosse=True sowie alle rowProjekt.Childrows("Leistung") auch choose=True und somit im Grid angezeigt bzw. nicht falls Checked weg genommen wird.
Nach dem Laden von Leistungen über einen Zeitbereich muss ich über alle Projekt Rows iterieren, um alle rowProjekt.Childrows("Leistung") auch choose=True/False zu setzen.
Das dauert je nach Anzahl Leistungen fast eine Minute. (4000 Leistungen ca. 20 sec).
Das stösst auf wenig Freude.Das Laden der Leistungen mit entsprechender idProjekt IN (Liste) nur eine Sekunde.
Für Ideen bin ich dankbar!
Gruss Peter -
Hallo Peter,
Zum einen: Für 4000 Zeilen sollte es keine 20 Sekunden dauern, alles über einer Sekunde dürfte auf anderem Wege zustande kommen.
Was Dir das im Wege stehen könnte ist die Datenbindung, die während der Aktion prüft. ob sie ihre Daten zu aktualisieren muss (oder es gar tut). Wenn Du eine BindingSource verwendest, klemme sie solange via SuspendBinding ab und stelle sie am Ende via ResumeBinding wieder her.
Zum zweiten: Wie hier schon mehrfach gesagt, sollte man größere Mengen via IN (...) eher meiden. Mit SQL Server 2008 R2 und später gibt es die Möglichkeit über Tabellenwertparameter gleiches zu erreichen.
Ein Beispiel für die AdventureWorks, wobei hier die Detail Daten zu einer größeren Anzahl (siehe Kommentar) abgerufen werden:
Imports System.Data.SqlClient Imports System.Text ''' <summary> ''' Erforder einen Tabellen Wert Typ in der AdventureWorks: ''' CREATE TYPE dbo.SalesOrderTableType AS TABLE ( SalesOrderID int PRIMARY KEY); ''' </summary> Public Class SqlTableParameter Public Shared Sub Run() Dim c As New SqlTableParameter() c.SelectWithInClause() c.SelectWithTableParameter() End Sub Public Sub SelectWithTableParameter() Dim orderTable = GetSalesOrders() ' hier: 4594 Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count) Dim detailsTable = SelectOrderDetailsWithTableType(orderTable) ' hier 16865 Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count) End Sub Public Sub SelectWithInClause() Dim orderTable = GetSalesOrders() ' hier: 4594 Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count) Dim detailsTable = SelectOrderDetailsWithInClause(orderTable) ' hier 16865 Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count) End Sub Private Function GetSalesOrders() As DataTable Dim watch = Stopwatch.StartNew() Dim table As New DataTable("SalesOrderHeader") table.Columns.Add("SalesOrderID", GetType(Integer)) Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString) Using adapter = New SqlDataAdapter( "SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE TerritoryID = 1", connection) adapter.Fill(table) End Using End Using Console.WriteLine("GetSalesOrders: {0} ms", watch.ElapsedMilliseconds) Return table End Function Private Function SelectOrderDetailsWithInClause(orderTable As DataTable) As DataTable Dim watch = Stopwatch.StartNew() Dim inBuilder As New StringBuilder(orderTable.Rows.Count * 6) For Each row As DataRow In orderTable.Rows inBuilder.Append(CInt(row("SalesOrderID")).ToString(System.Globalization.CultureInfo.InvariantCulture)) inBuilder.Append(","c) Next inBuilder.Length -= 1 Dim selectSql = "SELECT * FROM Sales.SalesOrderDetail AS Sod " & _ "WHERE SalesOrderID IN ( " & inBuilder.ToString() & ");" 'Console.WriteLine("SelectSQL Length: {0}, Text: '{1}'", selectSql.Length, selectSql) Dim table As New DataTable("SalesOrderDetail") Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString) Dim selectCommand = New SqlCommand(selectSql, connection) Using adapter = New SqlDataAdapter(selectCommand) adapter.MissingMappingAction = MissingMappingAction.Passthrough adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey adapter.Fill(table) End Using End Using Console.WriteLine("SelectOrderDetailsWithInClause: {0} ms", watch.ElapsedMilliseconds) Return table End Function Private Function SelectOrderDetailsWithTableType(orderTable As DataTable) As DataTable Dim watch = Stopwatch.StartNew() Dim table As New DataTable("SalesOrderDetail") Dim selectSql = "SELECT sod.* FROM Sales.SalesOrderDetail AS sod " & _ "INNER JOIN @SalesOrderIds AS soi ON sod.SalesOrderID = soi.SalesOrderID;" Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString) Dim selectCommand = New SqlCommand(selectSql, connection) ' DataTable als Parameter Dim tableParameter = selectCommand.Parameters.AddWithValue("@SalesOrderIds", orderTable) tableParameter.SqlDbType = SqlDbType.Structured tableParameter.TypeName = "dbo.SalesOrderTableType" Using adapter = New SqlDataAdapter(selectCommand) adapter.MissingMappingAction = MissingMappingAction.Passthrough adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey adapter.Fill(table) End Using End Using Console.WriteLine("SelectOrderDetailsWithTableType: {0} ms", watch.ElapsedMilliseconds) Return table End Function End Class
Die Laufzeit ist so ziemlich gleich, aber hat nicht mehr das Problem einer elendig langen SELECT Klausel, die doch irgendwann ans Ende kommt.
Gruß Elmar
- Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:04
-
Hi Elmar,
vielen Dank für die Info.
Ich bin erst morgen wieder am PC und werde mich überzeugen, ob ich SuspendBinding einsetze.
Ich denke ja !? Muss es mal kontrollieren.Mich hat es auch sehr gewundert, dass ein Durchlauf mit ca 4000 Projekten und 4000 Leistungen so lange braucht. (17 sec)
Etwa so (Pseudocode , mit LeistungBindingSource.Filter= "choose=True")
For each rowProjekt in ProjekteBindingSource
rowsLeistungen=rowProjekt.Childrows("Leistung")
for each rowL in rowsLeistungen
rowL.choose=rowProjekt.choose
next
NextFür die Hilfe wirklich sehr dankbar
Peter -
Hallo Peter,
kann es sein, dass die Anwendung noch im Debugmodus läuft? Dazu reicht es, wenn man die Anwendung als Debug kompiliert und die dann weitergibt. Bei ASP.NET reicht dazu auch ein Eintrag in der web.config (<compilation debug="true" ... />).
In dem Fall wäre das Verhalten normal, aber auch behebbar (durch umstellen auf Release)
Zusätzlich wäre es aber ggfs. auch sinnvoll, rowProjekt.choose innerhalb der Schleife in eine lokale Variable zu schreiben, dann muss nicht immer auf das Objekt und auf dessen Eigenschaft zugegriffen werden.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community
- Bearbeitet Stefan FalzModerator Dienstag, 5. Februar 2013 15:20
-
Hallo Peter,
der Debugmode kann erhebliche Performanceeinbrüche bewirken. Wie viel das effektiv bei dir ausmacht, muss man sich anschauen. Um den Faktor 10 langsamer würde mich nicht wundern.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community -
Hallo
ich habe meine Zeit mit testen verbraucht.
Als erstes habe ich ein Testprogramm erstellt, in dem ich die Projekte und Leistungen lade, sowie die Leistungen in ein DatGridView schreibe.
Für etwa je. 4000 Datensätze, braucht das Programm 3.7 sec, zu etwa 17 sec im eigentlichen Programm. Code:
For Each drv As DataRowView In TabProjekteBindingSource.List
rowProjekt = drv.Row
rowsL = rowProjekt.GettabZeitRows
If rowProjekt.Choose Then
For Each rowL In rowsZ
rowL.Choose = True
Next
End If
NextWas ich nicht feststellen konnte, ist ein Unterschied in der Debug oder Release Version.
Das Testprojekt, ConnectionString und DataSet habe ich in beiden Projekten gleich benannt. Somit konnte ich das DataSet des eigentlichen Programms, in das Testprojekt übernehmen.
Mit der Feststellung, dass das DataSet die Bremse ist.
Das lesen der gleichen Tabellen, mit den gleichen Expression-Spalten im neu angelegten DataSet ist etwas sechs mal schneller.
Woher das kommt, da habe ich keine Ahnung und davon jede Menge.
Ist es sinnvoll den DataSet neu aufzubauen und hoffen das dann alles gut ist ??Zudem hat das setzen von BindingSource.SuspendBinding keine Wirkung, der DataGridView dessen DataSource an das BindingSource gebunden ist, zappelt munter darauf los.
Vielen Dank, über eure Meinung freue ich mich.
Gruss Peter -
Hallo Peter,
wenn die Anzeige "zappelt" hast Du vermutlich die falsche oder zuwenige BindingSource "suspended". Das Zappeln ist ein deutlicher Hinweis darauf, dass viel Zeit mit der Anzeige verbraten wird.
Beachte dass nicht nur die BindingSource für TabProjekteBindingSource, sondern auch untergeordnete die (direkt oder indirekt) an GettabZeitRows gebunden sind, "beruhigt" werden müssen.
Gruß Elmar
- Bearbeitet Elmar BoyeEditor Mittwoch, 6. Februar 2013 18:14
-
Hallo Elmar,
Das kapiere ich mal wieder nicht, wie ich zu wenig BindingSource.Suspend aufrufen kann. Der einzige Grid.DataSource den ich finden kann, ist der eine BindingSource.
Der Grid ist entstanden, indem die Tabelle vom DataSource auf die Form gezogen wurde.Ich habe mal mit der AdventureWorks getestet.
Leider kann ich keinen Unterschied feststellen, ob BindingSource.SuspendBinding() verwendet wird oder nicht.
Es macht auch keinen Unterschied, ob ich BindingSource.SuspendBinding() aufrufe und danach kein BindingSource.ResumeBinding()
Meinen Test-Code habe ich angehängt.Zum Test habe ich die Tabelle "Contact" verwendet.
Im Designer eine Spalte "choose" Typ Boolean hinzugefügt.
Sowie im TableAdapter -> Konfigurieren WHERE (ContactID <= 6000) hinzugefügt.
Aus dem Designten DataSet die Tabelle Contact auf die Form gezogen, noch Button1 und Button2 dazu.Damit es richtig was zu schaffen gibt, die Form im Vollbild angezeigt.
Mit rumzappeln, verstehe ich, dass die Srollbar rumzappelt, mit Click eines Button bleibt der Inhalt des Grid stehen und wird danach aufgebaut.
Beziehungsweise, wenn alle auf rows auf True sind und ich erst ab dem 50ten auf False setze, bleibt der Grid so stehen wie er ist und die
Scrollbar zappelt los.
Jedoch kann ich kein anderes Vehalten erkennen, ob BindingSource.SuspendBinding() aufgerufen wird oder nicht.
Blicke ich irgend etwas überhaupt nicht?
Dir vielen Dank.
Gruss Peter
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim i As Integer
i = Me.ContactTableAdapter.Fill(Me.AdventureWorksDataSet.Contact)
ContactBindingSource.Filter = "choose = True"End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim row As AdventureWorksDataSet.ContactRow
Dim i As IntegerDim watch As New Stopwatch()
watch.Start()'ContactBindingSource.SuspendBinding()
For i = 0 To AdventureWorksDataSet.Contact.Rows.Count - 1
row = AdventureWorksDataSet.Contact.Rows(i)
row.choose = True
Me.Text = "setze choose=True " & i.ToString
Next
'ContactBindingSource.ResumeBinding()watch.Stop()
Debug.WriteLine("Time spent: Choose=True " & watch.Elapsed.ToString)
Debug.WriteLine("Ende choose=True")
End SubPrivate Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
Dim row As AdventureWorksDataSet.ContactRow
Dim i As IntegerDim watch As New Stopwatch()
watch.Start()'ContactBindingSource.SuspendBinding()
For i = 0 To AdventureWorksDataSet.Contact.Rows.Count - 1
row = AdventureWorksDataSet.Contact.Rows(i)
row.choose = False
Me.Text = "setze choose=False " & i.ToString
Next
' ContactBindingSource.ResumeBinding()watch.Stop()
Debug.WriteLine("Time spent: Choose=False " & watch.Elapsed.ToString)
Debug.WriteLine("Ende choose=False")
End Sub -
Hallo Peter,
setze zudem RaiseListChangedEvents auf False, denn durch den Filter werden sonst immer noch reichlich Ereignisse verschickt.
Die Zeiten (für alle):
Rows loaded 19973 ' Ohne RaiseListChangedEvents Time spent: Choose=True => 2612 ms
Time spent: Choose=False => 1669 ms ' Mit RaiseListChangedEvents Time spent: Choose=True => 12463 ms
Time spent: Choose=False => 26722 ms(Das Zappeln in der Titelzeile war dabei abgestellt, kostet auch ein, zwei Sekunden).
Wobei ich noch rätsele, warum False (Standard) doppelt solange braucht wie True bei aktiven Ereignissen (und umgekehrt wenn aus); bei angeglichener Codestrecke:
Public Class ContactForm Private Sub ContactForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim rowCount = Me.ContactTableAdapter.Fill(Me.AdventureWorksDataSet.Contact) Console.WriteLine("Rows loaded {0}", rowCount) ContactBindingSource.Filter = "Choose = True" End Sub Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click SetChooseState(True) End Sub Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click SetChooseState(False) End Sub #Const SuppressListChanged = False Private Sub SetChooseState(chooseState As Boolean) Dim watch = Stopwatch.StartNew() Me.ContactBindingSource.SuspendBinding() #If (SuppressListChanged) Then Me.ContactBindingSource.RaiseListChangedEvents = False #End If Try For Each row In Me.AdventureWorksDataSet.Contact row.Choose = chooseState 'Me.Text = String.Format("Choose {0} ({1})", chooseState, row.ContactID) Next Finally #If (SuppressListChanged) Then Me.ContactBindingSource.RaiseListChangedEvents = True Me.ContactBindingSource.ResetBindings(False) #End If Me.ContactBindingSource.ResumeBinding() End Try watch.Stop() Debug.WriteLine("Time spent: Choose={0} => {1} ms", chooseState, watch.ElapsedMilliseconds) End Sub End Class
Gruß Elmar- Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:00
-
Hallo Elmar,
herzlichen Dank für deine Untersuchung des verhaltens.
Inzwischen habe ich festgestellt wo bei mir die eigentlich wirklich grosse Bremse ist.
Ich habe in der Taballe vier ungebundene Expression Spalten hinzugefügt, mit
Parent(Rel_Name).ParentFeld. Dabei macht es von der Laufzeit keinen Unterschied ob es nur eine ist oder vier Expression Spalten sind. Die Zeiten sind bei 4000 Datensätzen 13,6sec, bei 2000 3,1sec, bei 1000 0,8 sec.
Diese Zeiten ergeben sich aber nur beim iterieren (setzen von choose) mit SuspendBinding und RaiseListChangedEvents=False. Seltsamerweise nicht nach dem laden der Daten mit TableAdapter.Fill.
Es kommt nicht oft vo, dass so viele Datensätze geladen werden, aber es sorgt für argen Unmut beim Benutzer.
Hättest du mir eine Idee wie ich das anders löse?
Die Daten werden in einem Readonly-Grid angezeigt, editiert wird in einer extra Form.
Nochmals ganz vielen Dank.
Grüsse Peter -
Hallo Peter,
Ausdrücke verwenden intern ähnliche Mechanismen wie die Benachrichtigungen. Wird ein Ausdruck an eine Spalte gebunden, wird die DataColumn "benachrichtigt", um ihren neuen Wert zu ermitteln. Abgewöhnen kann man ihr das nicht.
Um Vorschläge zu machen, fehlen mir die Informationen. Mir wären die vielen Choose Werte an sich suspekt. (Wer setzt schon 4000 Häkchen manuell - und das in weniger als 10 Sekunden ;)
Was man evtl. machen könnte, wäre die Auswahlspalte nicht direkt in der Tabelle zu verwalten, sondern in einer separaten DataTable oder einer Auflistung.
Gruß Elmar
-
Hi Elmar,
ich freue mich sehr, dass du mir auf mein Problem antwortest!
Wie oben beschrieben, ist der Ursprung der abhäkelei ein TreeView.
Dort gibt es die Möglichkeit auch Kunden und deren Zweig, sprich Projekte, rein oder raus zu nehmen. Alle auf unchecked und nur einzelne Kunden/Projekte anzuzeigen, einen eigenen Filter (checked Nodes) abspeichern und laden, u.a.m.Anscheinend gibt es ja keinen extremen bzw. merklichen Zeitverlust durch das setzen der Expression Spalten, direkt beim laden mit Fill.
Und wieso steigt die benötigte Zeit mit Anzahl der Rows nicht linear an, sondern so extrem ?
Spekulativ: Gibt es evtl. die Möglichkeit, dass erst nach Durchlauf aller Rows der Tabelle, der interne Mechanismus angeschups wird, zum setzen der Expression Spalten, alls Möglicherweise bei jedem Zugriff auf eine Row im Loop?Würdest du mir sagen, wie du es machen würdest, die Spaten die ich per Parent()-Expression in die DataTable aufnehme, darzustellen?
Dank und Gruss
Peter -
Hallo Peter,
das die Werte nicht linear ansteigen, dürfte an den internen Strukturen liegen. Aber daran herumzudoktern ist sowieso nicht möglich.
Und ich kann Dir leider auch nicht wirklich sagen, wie Du es anders machen könntest, denn dafür kenne ich Dein Projekt nicht genug.
Ich würde es zunächst mit einer Ableitung von TreeNode versuchen. Da ein TreeView sowieso keine Datenbindung kennt, kann man die Auswahl dort speichern und nur einen Verweis auf die DataRow verwalten.
Dann würden Änderungen der Auswahl die DataTable nicht tangieren und somit (zumindest dort) keine Probleme auftauchen.
Gruß Elmar
-
Hallo Elmar,<o:p></o:p>
ich bin dir wirklich ausserordentlich Dankbar für deine Antworten.<o:p></o:p>
Das TreeView ist nicht das Problem, ich habe eine Ableitung eines Knoten mit Row.
Aber da habe ich die Projekte und nicht die Leistungen die im Grid angezeigt werden.Ich möchte eine bestimmte Filterung im Grid sehen. Was ohne die Expression-Columns nicht das geringste Problem ist. Das Laden der Daten und Filtern per BindingSource geht schnell (sogar mit Expressions. Nur eben beim iteriren über die Rows ist es schlecht).
Wie du geschrieben hast, ist wohl die interne Struktur der DataTable das
Problem.
Da war meine Hoffnung, du kennst evt. ein Event das man ausschalten kann,
sodass erst nach der iteration über die rows der Tabelle, nur einmal
aktualisiert wird. Anstatt wie Möglicherweise bei jedem Zugriff auf eine row der Tabelle, etwas an geschupst wird.
Da der Grid nur zur Anzeige und nicht zur Éingabe benutzt wird.
Als letzte "Klimmzug-Lösung" überlege ich, kann ich die DataTable mit einer Abfrage mit einem Join laden, sodass ich zu den entsprechenden Feldern komme?
Herzliche Grüsse und dir gutes gelingen
Peter- Bearbeitet peter haus Dienstag, 12. Februar 2013 07:45
-
Hallo Peter,
was da dazwischenfunkt, kann ich leider nicht genau bestimmen. Schon beim obigen Test war ich etwas irritiert, wie nachzulesen; den Grund habe ich bisher aber nicht evaluieren können.
Wenn es nur für die Anzeige ist: Probieren könnte man eine Kopie der DataTable mit den ausgewählten Daten zu verwenden. Oder aber die DataTable durch eine Klasse plus Auflilstung zu ersetzen. Ohne Kenntnis des Gesamtprojekts ist das aber nur phantasieren.
BTW: Zum IN Problem ein aktueller Blog-Artikel:
Under rare conditions, using IN clause can cause unexpected SQL behaviorAuch wenn es eine eher exotische Variante ist und many nicht näher quantifiziert wird, zeigt es das IN Probleme machen kann.
Gruß Elmar -
Hallo Elmar,
ich denke ich habe eine bzw. zwei mögliche Lösungen für mein Problem.
Die eine die ich wohl anwende ist, vor dem iterieren über die rows, die Column Expression auf String.Empty setzen. Nach dem iterieren wieder setzen.
quickie-Code im Testprogramm:
Private _Kunde As String = "Parent(FK_Projekt_Zeit).Kunde"
Private _Projekt As String = "Parent(FK_Projekt_Zeit).Projektbezeichnung"
Private _Initialen As String = "Parent(FK_tabZeit_tabPersonal).Initialen"
Private _KategorieName As String = "Parent(ProjektKategorie_tabZeit).KategorieName"Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
Me.ZrfDataSet.tabZeit.Columns("KategorieName").Expression = ""
Me.ZrfDataSet.tabZeit.Columns("Kunde").Expression = ""
Me.ZrfDataSet.tabZeit.Columns("Projekt").Expression = ""
Me.ZrfDataSet.tabZeit.Columns("Initiale").Expression = ""TabZeitBindingSource.SuspendBinding()
Me.tabZeitBindingSource.RaiseListChangedEvents = FalseFor Each drv As DataRowView In TabProjekteBindingSource.List
rowProjekt = drv.Row
rowsL = rowProjekt.GettabZeitRows
If rowProjekt.Choose Then
For Each rowL In rowsL
rowL.Choose = True
Next
End If
NextMe.ZrfDataSet.tabZeit.Columns("KategorieName").Expression = _KategorieName
Me.ZrfDataSet.tabZeit.Columns("Kunde").Expression = _Kunde
Me.ZrfDataSet.tabZeit.Columns("Projekt").Expression = _Projekt
Me.ZrfDataSet.tabZeit.Columns("Initiale").Expression = _InitialenMe.tabZeitBindingSource.RaiseListChangedEvents = True
Me.tabZeitBindingSource.ResetBindings(False)TabZeitBindingSource.ResumeBinding()
End SubWas zuvor 13,6 sec brauchte, benötigt jetzt 0,27 sec.
Die Andere Variante.
Ich habe im SQL Server einen View erstellt. In der vom Designer incl. TableAdapter erstellten Tabelle, einen zusätzlicher TableAdapter erstellt, in dem ich den View aufrufe. Die ungebundenen Spalten der Tabelle KategorieName, Kunde, Projekt, Initialen zu gebundenen gemacht (da jetzt zusätzlich im View). Ein Update funktioniert auch weiter auf die Ursrungstabelle, die Update, ... Anweisungen blieben erhalten.Nochmals Dank für deine Hilfe.
Grüsse Peter
- Bearbeitet peter haus Mittwoch, 13. Februar 2013 12:51
-
Hallo Elmar,
die Lösung ist jetzt eine andere als die ursprüngliche Frage, aber ich bin total froh, ine Lösung für das Problem gefunden zu haben. Mit den Wartezeiten war es in Breichen sehr unerfreulich.
Dir vielen herzlichen Dank und gute Wünsche
Peter