Code zur Anzeige von Daten aus DataSet optimieren (Ausführung dauert zu lange)
- VB.NET
- .NET (FX) 4.5–4.8
Sie verwenden einen veralteten Browser (%browser%) mit Sicherheitsschwachstellen und können nicht alle Funktionen dieser Webseite nutzen.
Hier erfahren Sie, wie einfach Sie Ihren Browser aktualisieren können.
Hier erfahren Sie, wie einfach Sie Ihren Browser aktualisieren können.
Es gibt 98 Antworten in diesem Thema. Der letzte Beitrag () ist von DerSmurf.
-
-
-
-
VB.NET-Quellcode
- Partial Class DtsSettings
- Public IncomeRecord As DailyIncomeRow
- Public CustomersRecord As DailyIncomeRow
- Public ReadOnly ProductGroupRecords As New List(Of Distribution_TableRow)
- Public ReadOnly MonthlyGroupRecords As New List(Of Tuple(Of Date, ProductGroupRow, Integer))
- Public UsatzMonatRekord As Tuple(Of Date, Integer)
- Public KundenMonatRekord As Tuple(Of Date, Integer)
- Private Sub CalculateRekordMonate()
- Dim monthInfos = Aggregate tag In DailyIncome
- Let dt = tag._Date, dtMonth = New Date(dt.Year, dt.Month, 1)
- Group By dtMonth Into umsatz = [Select](tag.DailyIncome)
- Select dtMonth, monthSum = umsatz.Sum Into ToArray
- Dim maxMonthSum = monthInfos.MaxBy(Function(x) x.monthSum)
- UsatzMonatRekord = Tuple.Create(maxMonthSum.dtMonth, maxMonthSum.monthSum)
- monthInfos = Aggregate tag In DailyIncome
- Let dt = tag._Date, dtMonth = New Date(dt.Year, dt.Month, 1)
- Group By dtMonth Into kunden = [Select](tag.CustomerCount)
- Select dtMonth, monthSum = kunden.Sum Into ToArray
- maxMonthSum = monthInfos.MaxBy(Function(x) x.monthSum)
- KundenMonatRekord = Tuple.Create(maxMonthSum.dtMonth, maxMonthSum.monthSum)
- End Sub
- '...
-
ErfinderDesRades schrieb:
Aber die Anzeige musste selbst basteln.
Vielen Dank! und kein Problem.
Aber eins haben wir (ich) jetzt vergessen. Den letzten Punkt der TODO Liste.
DerSmurf schrieb:
Man sieht aber das Ziel der Ausgabe, nämlich- Monat mit höchstem Gesamtumsatz (Umsatz und Monat - also Datum)
- Monat mit den meisten Kunden
- Monate mit den höchsten Einnahmen für alle Warengruppen
- außerdem hätte ich gerne noch eine neue Funktion - den Monat mit höchstem pro Kopf Umsatz
[/list]
Die Anzeige des höchsten pro Kopf Umsatzes könnte doch in obige Funktion rein, oder?
Denn wenn ich es richtig verstehe hält monthinfos bei der ersten Verwendung doch sämtliche Monatsumsätze - im zweiten Zugriff sämtliche Monatskundenzahlen.
Kann ich diese durchgehen und mir so für jeden Monat den pro Kopf Umsatz ausrechnen, oder gehört das in eine eigene Sub? -
Was meinst du mit "Anzeige" - der Code zeigt doch garnix an?
Was meinst du mit "obige Funktion" - der gezeigte Code enthält doch nur eine Funktion?
Vielleicht meinst du, dass man beides - Income und CustomerCount - in einem Durchlauf hätte ermitteln können.
Ja, da hättest du recht - geht glaub so (ungetestet)VB.NET-Quellcode
- Private Sub CalculateRekordMonate()
- Dim monthInfos = Aggregate tag In DailyIncome
- Let dt = tag._Date, dtMonth = New Date(dt.Year, dt.Month, 1)
- Group By dtMonth Into umsatz = [Select](tag.DailyIncome), kunden = [Select](tag.CustomerCount)
- Select dtMonth, umsatzSum = umsatz.Sum, kundenSum = kunden.Sum Into ToArray
- Dim maxUmsatzMonth = monthInfos.MaxBy(Function(x) x.umsatzSum)
- Dim maxKundenMonth = monthInfos.MaxBy(Function(x) x.kundenSum)
- '...
- End Sub
-
Das meine ich nicht, aber danke für den Einschub.
Ein Durchlauf des Dts sollte ja doppelt so schnell sein, wie zwei Durchläufe.
Ich habe das jetzt ein wenig angepasst - englische Benennung der Variablen:
Spoiler anzeigen VB.NET-Quellcode
- Sub CalculateMonthlyRecords()
- Dim monthInfos = Aggregate tag In DailyIncome
- Let dt = tag._Date, dtMonth = New Date(dt.Year, dt.Month, 1)
- Group By dtMonth Into Income = [Select](tag.DailyIncome), customers = [Select](tag.CustomerCount)
- Select dtMonth, IncomeSum = Income.Sum, CustomerSum = customers.Sum Into ToArray
- Dim maxIncomeMonth = monthInfos.MaxBy(Function(x) x.IncomeSum)
- Dim maxCustomerMonth = monthInfos.MaxBy(Function(x) x.CustomerSum)
- MonthlyIncomeRecord = Tuple.Create(maxIncomeMonth.dtMonth, maxIncomeMonth.IncomeSum)
- MonthlyCustomersRecord = Tuple.Create(maxCustomerMonth.dtMonth, maxCustomerMonth.CustomerSum)
- CalculateMonthlyGroupRecords()
- End Sub
Ich muss ja noch für jeden Monat der pro Kopf Umsatz errechnen (Monatsumsatz / Kundenzahl), um den höchsten zu bekommen.
ErfinderDesRades schrieb:
Was meinst du mit "Anzeige" - der Code zeigt doch garnix an?
Was meinst du mit "obige Funktion" - der gezeigte Code enthält doch nur eine Funktion?
mit "Anzeige" meine ich errechnen. Genau diese eine Funktion meine ich dann natürlich
Also kann ich diese Berechnung in die Sub packen, die ich hier gepostet habe?
Denn monthInfos hält doch alle benötigten Daten. (ich glaube ich raffe den Code so allmählich)
Also würde ich noch eine Tuple erstellen:Public PCSRecord As Tuple(Of Date, Double)
und dem Code folgendes hinzufügen (PCS = per capita sales / eine andere Übersetzung für "pro Kopf Umsatz" habe ich nicht gefunden)
VB.NET-Quellcode
- Dim PCS As Double
- For Each row In monthInfos
- Dim TempPcs = row.IncomeSum / 100 / row.CustomerSum
- If TempPcs > PCS Then
- PCS = TempPcs
- PCSRecord = Tuple.Create(row.dtMonth, PCS)
- End If
- Next
- MessageBox.Show("Der höchste pro Kopf Umsatz war Monat " & PCSRecord.Item1.ToString("MMMM yyyy") & Environment.NewLine & "Es waren " & PCSRecord.Item2.ToString & "€")
Die msgbox ist natürlich sinnfrei (und gehört hier auch nicht hin, weils ja eine Anzeige ist - keine Berechnung) - dient nur der eindeutigen Anzeige, was ich möchste, dass die Funktion tut.
passt das so? -
DerSmurf schrieb:
Ein Durchlauf des Dts sollte ja doppelt so schnell sein, wie zwei Durchläufe.
Das Durchlaufen selbst kost eiglich keine Zeit - Es kommt drauf an, was im Durchlauf gemacht wird.
Würde in einem Durchlauf genau das hintereinander getan, was in zwei Durchläufen einzeln gemacht würde, dann wäre es zeitlich gehopft wie gesprungen.
Oft sind getrennte Durchläufe auch vonne Code-Verstehbarkeit besser (trotz des bischen mehr Codes).
Aber hier spart man wohl tatsächlich was, indem die Gruppierung nur einmal stattfindet.
Ansonsten ist das auch egal: Das eine ist sauschnell, und das anneres ist bischen sauschneller - der Unterschied wird sich nie auswirken.
Jo - deine Zusatz-Auswertung der MonthInfos finde ich sehr gut. Die stellen sich somit als dreifach wiederverwendbar dar: MonatsUmsatzRekord, MonatsKundenRekord, MonatsProkopfUmsatzRekord.
Da schmeichel ich mich immer, wenn ich eine Lösung programmiert hab, wo sich später herausstellt, dass sie so gut ist, dass sie auch Probleme löst, die noch garnet bekannt waren.
Interessanterweise wars ja nicht ganz meine Idee, Kunden und Einkommen in dieselbe monthInfos-Linq-query zu stopfen, sondern entstand ja aus einem Missverständnis dessen, was du gefragt hattest.
Aber dieses Zusammenstopfen eröffnete die Option nun auch der dritten Art der Auswertung.
Das issn Konzept, das werde ich mir merken.
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()
-
ErfinderDesRades schrieb:
Das issn Konzept, das werde ich mir merken.
Na, dann hast du ja sogar was von deiner Hilfe :o)
ErfinderDesRades schrieb:
Da schmeichel ich mich immer, wenn ich eine Lösung programmiert hab, wo sich später herausstellt, dass sie so gut ist, dass sie auch Probleme löst, die noch garnet bekannt waren.
und es zeigt, dass du als "Lehrer" taugst, denn der ProKopfUmsatz Code ist ja das erste was ich sinnvolles zur Lösung beitragen konnt, weil ich so allmählich etwas raffe...
Soho - die Anzeige zum Code aus Post 84 ist fertig. Die Anzeige für den ProKopfUmsatz stellt ja ebenfalls kein Problem dar.
Also kommen wir zu den letzten Fragen (ich glaube gecodet werden muss - von dir - nix mehr).
Frage1: wäre noch im zuletzt hochgeladenen Demoprojekt nachvollziehbar
Ich habe nun sämtliche Rekorde wunderschön in Properties des DataSet. Also kann ich beim speichern einer Tageseinnahme prüfen, ob ein Wert höher ist als der Rekord, es also einen neuen Rekord gibt, damit ich dies dem User mitteilen kann.
Ich habe mal ein Bildchen der Form zur Eingabe angehangen. Diese findet sich, falls du brauchst im letzten Demoprojekt hinter dem Button "Umsätze eintragen".
Die Controls oberhalb des schwarzen Striches DateTimePicker, Textbox unter Kunden und Buttons sind dabei im Designer erstellt, die Controls unten (Textboxen Warengruppen) sind per Code erstellt.
Damit ich gescheit auf diese Textboxen zugreifen kann, werden sie als List(Of Control) gespeichert, die wie folgt gefüllt wird:
VB.NET-Quellcode
- for i = 1 to groupcount
- Dim newTB = New System.Windows.Forms.TextBox
- With newTB
- .Location = New System.Drawing.Point(xPosition, yPosition + 23)
- .Font = New Font("Microsoft Sans Serif", 12)
- .Name = "TB " & GroupName
- .Size = New System.Drawing.Size(200, 26)
- .TabIndex = i + 1
- .Visible = True
- AddHandler .TextChanged, AddressOf TextChangeEvent
- AddHandler .Enter, AddressOf TextBox_Enter
- End With
- TBList.Add(newTB)
- next
Nun ist meine Frage wie ich die suche nach neuen Rekorden und ggf. auch das speichern des neuen Rekordes in der entsprechenden Property veranstalte.
- wo gehört der Code hin? Ich würde ihn ins DataSet schmeißen, damit ich nicht "von außen" an dessen Properties rumfummeln muss, und nach Abschluss der speichern Sub - also nachdem alle Tageseinnahmewerte im DataSet sind ausführen. Hierfür würde ich dann die Werte aus den Textboxen nehmen und nicht die aus dem DataSet. Das ganze mache ich als einfache Sub. An diese Übergebe ich den Inhalt sämtlicher Textboxen (Warengruppen als Array), sowie das Datum.
In der Sub wird dann jeder Wert mit den Properties abgeglichen. Ist ein übergebener Wert höher, ändere ich den Wert der Property und öffne eine messagebox.
Hier bin ich mir an zwei Stellen unsicher. 1. Ich verwende den Inhalt der Textboxen, nicht den ausm DataSet. Ist das ok?
2. Ich vermische Businesslogik mit Anzeigelogik. (Die Sub die prüft ob es einen neuen Rekord gibt, gibt Rückmeldung in Form einer messagebox. Ist das ok?
Frage2: bezieht sich auf das Hauptprogramm
Die zweite Frage bezieht sich auf die Sub, welche das befüllen der Properties im DataSet bewirkt. Wo muss die hin?
Hierzu musst du wissen, dass es im Hauptprogramm eine Form zur Anzeige der Umsatzrekorde gibt. Diese kann nix außer anzeigen - enthält nur die Codegenerierten Label (genau wie hier im Demoprojekt).
Und es gibt eine Form zum Eintragen eines neuen Tagesumsatzes (exakt die gleiche wie hier hinter dem Button "Umsatz eintragen")
Das Programm macht aber natürlich noch einiges mehr. (Ich hoffe das ist soweit vorstellbar - sonst mache ich mal Bilder).
Nun ist es so, dass ich die Statistik nicht jeden Tag brauche. Ich trage so alle 3 bis 4 Tage die fehlenden Tageseinnahmen ein. Die Rekorde schaue ich mir nur sehr sehr selten an.
Sollte ich dann das setzen der Properties im DataSet im Form Load Event anstoßen? Denn dann würde ich diese recht oft füllen, ohne sie dann zu nutzen.
Alternativ könnte ich diese ja auch füllen, wenn ich den Button zum starten der Tageseinnahmeform, bzw. zum starten der RekordeForm drücke. -
also das mit den Monats-Rekorden wird mühsam, wenn du das bei jeder Eingabe prüfen willst.
Da muss man ja jedesmal den aktuellen Monat neu zusammenrechnen.
Ja gut, kann man machen.
Ansonsten würde ich mit den DistributionRows arbeiten, die du zufügst, wenn du ein TagesErgebnis eingibst.
Die kann man sich ja merken, und der BL zur Überprüfung übergeben.
Oder du änderst das Konzept, und berechnest die Rekorde nicht beim Einlesen, sondern beim Abspeichern.
Dann haste immer alles frisch und aktuell durchgerechnet.
Dann kannste noch INotifyPropertyChanged implementieren - das ist der Standard, wie über Property-Änderungen benachrichtigt wird - Databinding arbeitet auch damit.
Dann brauchste der BL garnix extra zu übergeben, sondern die meldet sich automatisch, wenn eine der Rekord-Properties sich ändert.
Hmm - dann muss man aber noch was erfinden, wie man die Rekorde auch ins Dataset abspeichert...
Nee, ich glaub, beste ist, du rechnest wie gehabt beim Einlesen, und beim Abspeichern rechneste eben alles nochmal.
Zum Einlesen blockierste dann das PropertyChanged-Event, aber anschliessend aktivierste es, sodass beim Abspeicher-Berechnen die Messageboxes aufpoppen können.
Ist zwar nicht suuper-performant, aber so brauchste keinerlei erneuten Rekorde-BerechnungsCode schreiben - nichtmal, wenn du dir weitere Rekorde ausdenken tätest.
Ist aber trotzdem einiges an Arbeit, und besonders deine laufzeit-generierten Labels werden da nicht einfach zu händeln sein.
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()
-
-
Da ist ja einiges auszurechnen, welche Rekorde durch was übertroffen werden: UmsatzRekord, KundenRekord, KategorieUmsatzRekord (und von welcher Kategorie), Umsatz/Kunden-Rekord-Monat auch?
Also bei einer Methode wird das nicht bleiben.
Und im Dataset muss die Rekord-Property geändert werden - also da müssen neueDailyIncomeRow
/Distribution_TableRow
eingetragen werden, weil die nächste Berechnung muss ja die neuesten Rekorde zum Vergleich heranziehen können.
Wo aber willste dieDailyIncomeRow
/Distribution_TableRow
hernehmen, wenn du nur 2 Werte (Datum, Zahl) aus iwelchen Textboxen an die Methode übergibst?
Naja, vielleicht auch nicht. Es kann ja nur eine Eingabe pro Tag geben, also wird es auch nur eine NeuRekord-Berechnung geben - also kann unsaubererweise evtl. drauf verzichtet werden, die Dataset-Rekord-Properties zu updaten.
Aber wie siehts eiglich mit Korrigieren von Fehleingaben aus? Was soll passieren, wenn eine nachträgliche Korrektur die Rekord-Werte in Mitleidenschaft zieht?
Also ich glaub, das mitte Rekord-Meldung wird viel Arbeit machen (kann mich natürlich irren) für vergleichsweise geringen Nutzen. Ich fände es eiglich wichtiger, die Benamungsgeschichte anzugehen.
-
Huhu
Sorry, einmal etwas länger halbwegs Offtopic:
Also ich gebe dir absolut Recht, dass die Anzeige dieser "Hurra neuer Rekord Messageboxen" für das Programm absolut sinnfrei sind.
Zumal der Umsatz eines einzelnen Tages ja irgendwie auch total egal ist. Wenn ich in einem Monat 30% Kunden weniger, und 20% Umsatz weniger habe, läuft gewaltig etwas schief.
Wenn ich dann in diesem Monat in zwei Kategorien den höchsten Tagesumsatz fahre, interessiert das auch keinen.
Aber von solchen Szenarien ist meine Firma weit weg - dit lööft! und ich freu mich immer wie ein Kullerkeks, wenn das Fenster aufploppt und wieder ein Umsatzrekord gebrochen wurde.
Daher ist diese - wie gesagt, vollkommen sinnbefreite Funktion - für mich gar nicht so sinnbefreit - denn sie liefern mir ein - 2 Sekunden andauerndes - Glücksgefühl.
Generell sollte dann natürlich trotzdem das wichtigere zuerst erledigt werden, und dass die Umbenennung wichtiger ist glaube ich dir. (für eine eigene Meinung dazu fehlt mir die Erfahrung).
Außerdem habe ich ja auch noch die Datenredundanz in DtsSettings.DailyIncome.DailyIncome - wo ich den Tagesumsatz per Code errechne und eintrage und eben (noch) keine errechnete Spalte habe.
Dann ist da noch DtsSettings.DailyIncome._Date welches umbenannt werden muss.
Und ich habe auch noch das Thema String Interpolation auf dem Schirm. Das hatten wir beide mal in einem anderen Thread.
Damit würde ich meinen Code deutlich lesbarer machen.
Aber! Ich gebe dir wie gesagt recht (oder glaube dir), dass diese Dinge wichtiger sind, als mein UmsatzrekordBenachrichtgungNonsense, dennoch ist es so, dass ich bei all dem oben aufgezählten ja keine Hilfe brauche.
DataSet umbennen ist unheimlich ätzend. Ich werde ein paar Anläufe brauchen, ich werd den Mist verfluchen, ich werd neu Anfangen, wieder alles verfluchen und irgendwann passts.
Da kann (braucht) mir keiner bei helfen.
Das gleiche bei Stringinterpolation - da sollten ein paar Seiten im Netz alles klären, damit ich meinen Code abändern kann.
Wenn ich früher in meinem VisualBasic Forum in einem Thread geholfen habe, dann habe ich mich mit anspruchsvolleren Themen als Helfer auch tagsüber damit beschäftigt, bis es erledigt war.
Oder eventuelle Verbesserungen für geposteten Code oder Lösungswege gesucht.
Erst wenn das Thema erledigt war, habe ich als Helfer damit "abgeschlossen".
Deswegen habe ich das Thema mit der Rekordbenachrichtigung nun zu ende gedacht und gemacht. Es kann dann nämlich das Thema hier abgehakt werden, weil wir fertig sind.
Bzw. weil ich beim Rest den ich zum fertigstellen brauche, keine Hilfe mehr brauche (glaube ich zumindest).
Also zurück zum Thema:
Ich habe einfach mal den Code fertiggestellt. Zu deinen Bedenken habe ich mir das folgende gedacht:
ErfinderDesRades schrieb:
Da ist ja einiges auszurechnen, welche Rekorde durch was übertroffen werden
ErfinderDesRades schrieb:
Und im Dataset muss die Rekord-Property geändert werden (...) weil die nächse Berechnung muss ja die neuesten Rekorde zum Vergleich (...)
Das löse ich beides indem ich, nachdem ein neuer Rekord gefunden wurde, einfach die Rekorde neu berechnen lasse.
Die Funktionen dafür sind ja im DataSet und laufen in windeseile durch.
ErfinderDesRades schrieb:
Aber wie siehts eiglich mit Korrigieren von Fehleingaben aus? Was soll passieren, wenn eine nachträgliche Korrektur die Rekord-Werte in Mitleidenschaft zieht?
Wenn ich durch das ändern einer Tageseinnahme einen Rekord entferne, stimmt die ganze Geschichte nach einem Neustart des Programmes wieder, denn da werden die Rekorde ja neu eingelesen.
Da dies nicht so häufig vorkommt, kann ich damit glaube ich leben.
Alternativ könnte ich, da meine TageseinnahmeSpeichernSub einen Unterschied zwischen "neuer Einnahme" und "Einnahme ändern" kennt, - beim ändern müssen ja keine neuen Rows hinzugefügt werden, bei einer neuen schon - im Falle einer Änderung auch die Rekorde neu berechnen lassen.
Das gleiche gilt beim löschen einer Einnahme. Nach dem löschen der Rows - einfach Rekorde neu berechnen und gut ist.
Soho, hier der Code - ich habe ihn grob getestet, schein zu tun was er soll. Ob das ganze so gut - oder wenigestens "in Ordnung" gelöst ist, weiß ich natürlich nicht.
Ich spare mir mal das hochladen einer Demo, denn ich denke die brauchst du zum beurteilen des Code nicht.
Wenn doch, mach ich das aber natürlich gerne:
VB.NET-Quellcode
- 'der Aufruf aus der speichern Sub
- 'neue Rekorde suchen
- Dim ArrDailyGroupincome(0 To TBList.Count - 1) As Double
- Dim i = -1
- For Each TB As Control In TBList
- i = i + 1
- ArrDailyGroupincome(i) = Double.Parse(TB.Text)
- Next
- DtsSettings.FindNewStatisticRecordsDaily(Integer.Parse(TBCustomers.Text), Double.Parse(LBLDailyIncome.Text), ArrDailyGroupincome, IncomeDate)
- DtsSettings.FindNewStatisticRecordsMonthly(IncomeDate)
- 'und die DataSet Klasse. Den alten Code habe ich mal hinterm Spoiler versteckt:
- Partial Class DtsSettings
- Public IncomeRecord As DailyIncomeRow
- Public CustomersRecord As DailyIncomeRow
- Public ReadOnly ProductGroupRecords As New List(Of Distribution_TableRow)
- Public ReadOnly MonthlyGroupRecords As New List(Of Tuple(Of Date, ProductGroupRow, Integer))
- Public MonthlyIncomeRecord As Tuple(Of Date, Integer)
- Public MonthlyCustomersRecord As Tuple(Of Date, Integer)
- Public PCSRecord As Tuple(Of Date, Double)
Spoiler anzeigen
VB.NET-Quellcode
- Public Sub LoadDailyRecords()
- IncomeRecord = DailyIncome.MaxBy(Function(x) x.DailyIncome)
- CustomersRecord = DailyIncome.MaxBy(Function(x) x.CustomerCount)
- Dim rekorde = From dist In Distribution_Table Group By dist.ProductGroupID Into rekord = MaxBy(dist.ProductGroupIncome) Select rekord
- ProductGroupRecords.Clear() : ProductGroupRecords.AddRange(rekorde)
- End Sub
- Private Sub CalculateMonthlyGroupRecords()
- Dim tuples = New List(Of Tuple(Of Date, ProductGroupRow, Integer))
- Dim kategorieInfos = Aggregate dist In Distribution_Table Group By kategorie = dist.ProductGroupRow Into distsOfKategorie = Group Into ToArray
- For Each info In kategorieInfos
- Dim monthInfos = Aggregate dist In info.distsOfKategorie
- Let dt = dist.DailyIncomeRow._Date, dtMonth = New Date(dt.Year, dt.Month, 1)
- Group By dtMonth Into monthlyDists = ToArray(dist)
- Select dtMonth, monthSum = monthlyDists.Sum(Function(x) x.ProductGroupIncome) Into ToArray
- Dim maxMonthPerKategorie = monthInfos.MaxBy(Function(x) x.monthSum)
- tuples.Add(Tuple.Create(maxMonthPerKategorie.dtMonth, info.kategorie, maxMonthPerKategorie.monthSum))
- Next
- Dim orderedTuples = From kat In ProductGroup Join tpl In tuples On kat Equals tpl.Item2 Select tpl 'Reihenfolge der Tuples in ProductGroup-Reihenfolge bringen
- Me.MonthlyGroupRecords.Clear()
- Me.MonthlyGroupRecords.AddRange(orderedTuples)
- End Sub
- Public Sub CalculateMonthlyRecords()
- Dim monthInfos = Aggregate tag In DailyIncome
- Let dt = tag._Date, dtMonth = New Date(dt.Year, dt.Month, 1)
- Group By dtMonth Into Income = [Select](tag.DailyIncome), customers = [Select](tag.CustomerCount)
- Select dtMonth, IncomeSum = Income.Sum, CustomerSum = customers.Sum Into ToArray
- Dim maxIncomeMonth = monthInfos.MaxBy(Function(x) x.IncomeSum)
- Dim maxCustomerMonth = monthInfos.MaxBy(Function(x) x.CustomerSum)
- MonthlyIncomeRecord = Tuple.Create(maxIncomeMonth.dtMonth, maxIncomeMonth.IncomeSum)
- MonthlyCustomersRecord = Tuple.Create(maxCustomerMonth.dtMonth, maxCustomerMonth.CustomerSum)
- Dim PCS As Double
- For Each row In monthInfos
- Dim TempPcs = row.IncomeSum / 100 / row.CustomerSum
- If TempPcs > PCS Then
- PCS = TempPcs
- PCSRecord = Tuple.Create(row.dtMonth, PCS)
- End If
- Next
- 'MessageBox.Show("Der höchste pro Kopf Umsatz war Monat " & PCSRecord.Item1.ToString("MMMM yyyy") & Environment.NewLine & "Es waren " & PCSRecord.Item2.ToString & "€")
- CalculateMonthlyGroupRecords()
- End Sub
VB.NET-Quellcode
- 'Sub zum finden neuer Tagesrekorde nach Eingabe
- Public Sub FindNewStatisticRecordsDaily(Customers As Integer, DailyIncome As Double, ArrDailyGroupIncome() As Double, Datum As Date)
- 'TODO Sub in Function umwandeln: Rückgabewert = List(of string)
- 'statt Showmessagebox: List of String mit entsprechenden Werten erstellen
- 'Ausgabe der Daten erledigt dann Aufrufsub
- Dim NewRecordFound = False
- 'Kunden
- If Customers > CustomersRecord.CustomerCount Then
- ShowMessageBox("Tageskundenrekord", CustomersRecord.CustomerCount.ToString("#,###"), Customers.ToString("#,###"), Datum.ToShortDateString)
- NewRecordFound = True
- End If
- 'Tageseinnahme
- If DailyIncome > IncomeRecord.DailyIncome Then
- ShowMessageBox("Tageseinnahmerekord", IncomeRecord.DailyIncome.ToString("#,##0.00 €"), DailyIncome.ToString("#,##0.00 €"), Datum.ToShortDateString)
- NewRecordFound = True
- End If
- 'Warengruppeneinnahme
- Dim i = -1
- For Each record In ProductGroupRecords
- i += 1
- 'Daten aus DataSet sammeln
- Dim ParentProductGroupRow = record.ProductGroupRow
- Dim ParentDailyIncomeRow = record.DailyIncomeRow
- Dim RecordDate = ParentDailyIncomeRow._Date
- Dim ProductGroupName = ParentProductGroupRow.Name
- Dim ProductGroupIncome = record.ProductGroupIncome / 100
- 'die Prüfung auf neuen Rekord
- If ArrDailyGroupIncome(i) > ProductGroupIncome Then
- ShowMessageBox("Tagesumsatzrekord " & ProductGroupName, ProductGroupIncome.ToString("#,##0.00 €"), ArrDailyGroupIncome(i).ToString("#,##0.00 €"), RecordDate.ToShortDateString)
- NewRecordFound = True
- End If
- Next
- If NewRecordFound Then LoadDailyRecords()
- End Sub
- Private Sub ShowMessageBox(NameOfRecord As String, OldRecord As String, NewRecord As String, OldDatum As String)
- MessageBox.Show("Herzlichen Glückwunsch! Es gibt einen neuen " & NameOfRecord & Environment.NewLine &
- "alt: " & OldRecord & " vom " & OldDatum & Environment.NewLine &
- "neu: " & NewRecord)
- End Sub
- 'Sub zum finden neuer Monatsrekorde nach Eingabe
- Public Sub FindNewStatisticRecordsMonthly(Datum As Date)
- 'TODO Sub in Function umwandeln: Rückgabewert = List(of string)
- 'statt Showmessagebox: List of String mit entsprechenden Werten erstellen
- 'Ausgabe der Daten erledigt dann Aufrufsub
- Dim NewRecordFound = False
- '1. Schritt
- 'Monatsumsätze des aktuellen Monats zusammenrechnen (enthalten ja die neue Tageseinnahme bereits)
- Dim Startdate = New Date(Datum.Year, Datum.Month, 1)
- Dim MonthIncome As Double
- Dim MonthCustomers As Integer
- Dim productGroups = ProductGroup.ToArray
- Dim groupcount = productGroups.Length
- Dim ArrProductGroupIncome(0 To groupcount - 1) As Double
- Dim dt1 = Startdate.AddMonths(1) ' Zeit-Bereich
- 'Produktgruppen
- Dim distributionTables = From distTbl In productGroups.SelectMany(Function(x) x.GetDistribution_TableRows)
- Let dt = distTbl.DailyIncomeRow._Date
- Where dt >= Startdate AndAlso dt < dt1 Select distTbl
- For Each rwDistrTbl In distributionTables
- Dim rwPG = rwDistrTbl.ProductGroupRow
- Dim day = rwDistrTbl.DailyIncomeRow._Date.Day
- Dim iProdGroup = Array.IndexOf(productGroups, rwPG)
- Dim income = rwDistrTbl.ProductGroupIncome / 100
- ArrProductGroupIncome(iProdGroup) += income
- Next
- 'Gesamtumsatz
- For Each ProductGroupIncomeIncome In ArrProductGroupIncome
- MonthIncome += ProductGroupIncomeIncome
- Next
- 'Monatskunden
- For Each rwIncome In DailyIncome.Where(Function(x) x._Date >= Startdate AndAlso x._Date < dt1)
- MonthCustomers += rwIncome.CustomerCount
- Next
- '2. Schritt
- 'Monatsumsätze mit Rekorden vergleichen
- 'Monatsumsatz
- If MonthIncome > MonthlyIncomeRecord.Item2 / 100 Then
- ShowMessageBox("Monatsumsatzrekord", MonthlyIncomeRecord.Item2.ToString("#,##0.00 €"), MonthIncome.ToString("#,##0.00 €"), Datum.ToString("MMMM yyyy"))
- NewRecordFound = True
- End If
- 'Monatskunden
- If MonthCustomers > MonthlyCustomersRecord.Item2 Then
- ShowMessageBox("Monatskundenrekord", MonthlyCustomersRecord.Item2.ToString("#,###"), MonthCustomers.ToString("#,###"), Datum.ToString("MMMM yyyy"))
- NewRecordFound = True
- End If
- 'Monatsumsatz für jede Warengruppe
- Dim i = -1
- For Each tpl In MonthlyGroupRecords
- i += 1
- If ArrProductGroupIncome(i) > tpl.Item3 / 100 Then
- ShowMessageBox("Umsatzrekord " & tpl.Item2.Name, (tpl.Item3 / 100).ToString("#,##0.00 €"), ArrProductGroupIncome(i).ToString("#,##0.00 €"), Datum.ToString("MMMM yyyy"))
- NewRecordFound = True
- End If
- Next
- If NewRecordFound Then CalculateMonthlyRecords()
- End Sub
- End Class
Und noch eine Frage:
Welche Methode ist geeigneter, wenn ich eine Boolean Variable mit dem Wert False erstellen möchte?
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „DerSmurf“ ()
-
zur Frage: Annähernd schnurz. Der zweiten Variante kann man einen kleinen Vorzug geben, weil da ausdrücklich steht, dass die Variable False sein wird. Bei der ersten Variante weiss man das halt, dass ein uninitialisierter Boolean auf False steht.
Für mich ists auch eine Gewohnheitssache - ich nehme die zweite Variante, wo immer es geht. -
-
Ich kann dir nicht gut raten, alles zu löschen. V.a. nicht, wenns tut, was es soll (inklusive Geschwindigkeit) - und davon geh ich mal aus.
Ich blick den Code auch nicht durch - ist viel zu viel.
Ich wäre wie gesagt ganz anderen Weg gegangen. Weil die Berechnung der Rekorde aus dem Dataset gibts ja schon - da würde ich nicht eine neue Berechnung aus Textboxen heraus implementieren.
Sondern ich würde mir die alten Rekorde merken, den neuen Datensatz einspeichern, und die Rekorde neu rechnen lassen - und dann gucken, was sich geändert hat.
Wirklich elegant könnte das werden, wenn du Databinding implementieren tätest - weil dann brauchste den Umstand mit "vorher merken - hinterher nachgucken" nicht, sondern kriegst ein Event gefeuert, wenn ein Rekord übertroffen wird.
Ich setze eh immer komplett auf Databinding - aber Controls zur Laufzeit generieren passt da nicht zu.
Wenn wolle guckst du WinForms Projektentwicklung bzgl. der Rolle des Formulars da hat im Kapitel "MVP" Vaporized ein Databinding-Sample gemacht, und in meiner Antwort ich auch eines. -
Holla
Den verlinkten Thread von Vaporized finde ich mega interessant.
Allerdings hebe ich mir diesen für später auf - denn nach dem intensiven lesen und verstehen, habe ich deutlich mehr Code zu ändern, als nur das Beispiel hier...
Also mache ich mich zunächst an die von dir angesprochenen Dinge.
ErfinderDesRades schrieb:
Weil die Berechnung der Rekorde aus dem Dataset gibts ja schon - da würde ich nicht eine neue Berechnung aus Textboxen heraus implementieren.
Aber das zu ich doch garnicht!?
Bei Tagesrekorden prüfe ich, ob der Eingegebene Wert (Textbox) größer ist als der Rekord. Wenn ja - messagebox und Rekorde neu rechnen (Methode im DataSet aufrufen).
Bei den Monatsumsätzen übergebe ich nur das Datum der Eingabe und rechne den Monatsumsatz zusammen (so wie bei der Anzeige der Monatsumsätze - ohne das in diesem Fall unnötige drumherum) und vergleiche die Werte dann entsprechend mit den RekordProperties. Hier auch, wenn Rekord gefunden Messagebox und Prozedur im DataSet aufrufen, um Rekorde neu zu rechnen.
Es ist zeitlich nicht zu merken, dass der Code ausgeführt wird. Ich klicke auf speichern und fertig ist.
Also lass ichs dann erstmal so und mach mich ans umbenennen.
Aber hierzu eine Frage.
Ich entwickle das Programm auf PC1 (EntwicklerPC) und führe es auf PC2(AusführPC) aus - mittels nUpdate.
Auf dem AusführPC befinden sich bereits Daten im DataSet.
Wenn ich nun die Tables im DataSet auf dem EntwicklerPC ändere und das ganze mittels nUpdate auf den AusführPC bringe, ist doch meine dortige xml nicht mehr einlesbar.
Ich muss also die xml des AusführPC auf den EntwicklerPC bringen. Dann irgendwie eine Prozedur schreiben, die mir eine neue xml - mit aktualisieren Table Namen erstellt, damit diese nicht unbrauchbar wird.
Gibt es hier einen Trick? Denn ich weiß nicht so genau, wie ich das anstellen soll. -
DerSmurf schrieb:
Wenn ich nun die Tables im DataSet auf dem EntwicklerPC ändere und das ganze mittels nUpdate auf den AusführPC bringe, ist doch meine dortige xml nicht mehr einlesbar.
Ich muss also die xml des AusführPC auf den EntwicklerPC bringen. Dann irgendwie eine Prozedur schreiben, die mir eine neue xml - mit aktualisieren Table Namen erstellt, damit diese nicht unbrauchbar wird.
Gibt es hier einen Trick? Denn ich weiß nicht so genau, wie ich das anstellen soll.
Da muss wirklich die Datendatei zurück auf den Entwicklungsrechner, damit man Textersatz über den ganzen Ordner drüberlaufen lassen kann. -
Na toll... noch mehr Try and Error xD
Nagut ich mach mich mal ran.
Musste erstmal die neue Helpers Datei pimpen. In der die ich auf dem PC habe, hatte ich mir damals eine Funktion aus der "großen" Helpers eingebaut, um EditNew eine Tuple zu übergeben, damit ich einen Datensatz "kopieren" kann. Aber das müsste hingehauen haben. Bin dann daran gescheitert, dass ich das Framework meiner Programmes von 4.5 auf 4.6.1 anheben muss, weil die Helpers darauf entwickelt wurde.
Hat ewig gedauert, bis ich den Fehler gefunden habe.
Oder hast du da mittlerweile etwas anderes implementiert? So siehts bei mir aus, nach Änderung der entsprechenden Prozeduren in den Helpers.
Spoiler anzeigen VB.NET-Quellcode
- Dim SelectedOrder = DirectCast(DirectCast(CustomerOrderBindingSource.Current, DataRowView).Row, DtsSettings.CustomerOrderRow)
- 'Daten des Artikels an bearbeitenForm übergeben
- Dim tpls As New List(Of Tuple(Of DataColumn, Object))
- With DtsSettings.CustomerOrder
- 'Werte die belegt sein müssen übergeben
- tpls.Add(Tuple.Create(Of DataColumn, Object)(.OrderDateColumn, System.DateTime.Now))
- tpls.Add(Tuple.Create(Of DataColumn, Object)(.CustomerNameColumn, SelectedOrder.CustomerName))
- tpls.Add(Tuple.Create(Of DataColumn, Object)(.ArtNrColumn, SelectedOrder.ArtNr))
- tpls.Add(Tuple.Create(Of DataColumn, Object)(.AmountColumn, SelectedOrder.Amount))
- CustomerOrderBindingSource.EditNew(Of frmEditCustomerOrder)(tpls)
- End With
Dafür habe ich die Helpers wie folgt geändert. Habe Sorge zuviel (oder was falsch) geändert zu haben:
Die MoveToRow habe ich eingefügt, den Rest habe ich ersetzt:
VB.NET-Quellcode
- Public Module BindingSourceX
- ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
- <Extension()>
- Public Function EditNew(Of T As {Form, New})(bs As BindingSource, defaultValues As List(Of Tuple(Of DataColumn, Object)), Optional owner As Form = Nothing, Optional onErrorRetry As Boolean = False) As DialogResult
- Dim drv = DirectCast(bs.AddNew, DataRowView)
- defaultValues.ForEach(Sub(tpl) drv(tpl.Item1.ColumnName) = tpl.Item2)
- Return EditItem(Of T)(bs, drv, onErrorRetry, owner)
- End Function
- ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
- <Extension()>
- Public Function EditNew(Of TEditForm As {Form, New})(bs As BindingSource, Optional onErrorRetry As Boolean = False, Optional owner As Form = Nothing) As DialogResult
- Return EditItem(Of TEditForm)(bs, bs.AddNew, onErrorRetry, owner)
- End Function
- ''' <summary> editiert bs.AddNew im angegebenen Dialog-Form. </summary>
- <Extension()>
- Public Function EditCurrent(Of TEditForm As {Form, New})(bs As BindingSource, Optional onErrorRetry As Boolean = False, Optional owner As Form = Nothing) As DialogResult
- Return EditItem(Of TEditForm)(bs, bs.Current, onErrorRetry, owner)
- End Function
- Private Function EditItem(Of T As {Form, New})(bs As BindingSource, item As Object, onErrorRetry As Boolean, owner As Form) As DialogResult
- EditItem = DialogResult.Retry
- Dim tb = DirectCast(item, DataRowView).Row.Table
- While EditItem = DialogResult.Retry
- Using frm = New T
- tb.DataSet.Register(frm, False)
- Dim allCtls = New GetChilds(Of Control)(Function(ctl) ctl.Controls).AllAsList(frm)
- Dim bindFlags As BindingFlags = BindingFlags.Instance Or BindingFlags.Public _
- Or BindingFlags.NonPublic Or BindingFlags.GetField
- For Each ctl In allCtls.Where(Function(c) TypeOf c Is ContainerControl AndAlso TypeOf c Is Form OrElse TypeOf c Is UserControl)
- For Each fld In ctl.GetType.GetFields(bindFlags).Where(Function(f) f.FieldType Is GetType(BindingSource))
- Dim bs2 = DirectCast(fld.GetValue(ctl), BindingSource)
- If bs2 Is Nothing Then Continue For
- If tb Is bs2.DataTable Then
- bs2.DataSource = item
- Try
- EditItem = frm.ShowDialog(owner)
- If EditItem <> DialogResult.Cancel Then
- Try
- bs2.EndEdit()
- ' BusyDelay.SetCallback(Sub() bs2.MoveToRow(DirectCast(item, DataRowView).Row))
- Catch ex As Exception
- If Not onErrorRetry Then Throw
- EditItem = MessageBox.Show(ex.Message, ex.GetType.Name, MessageBoxButtons.RetryCancel)
- Continue While
- End Try
- End If
- Finally
- If EditItem = DialogResult.Cancel Then
- bs.CancelEdit()
- Else
- If DirectCast(bs.List, DataView).Sort <> "" Then bs.MoveToRow(DirectCast(item, DataRowView).Row)
- bs.ResetCurrentItem() 'den von der anneren BS geänderten Datensatz neu einlesen.
- End If
- End Try
- Exit Function
- End If
- Next
- Next
- Throw New Exception("could not find a proper BindingSource.")
- End Using
- End While
- End Function
- <Extension()>
- Public Function MoveToRow(bs As BindingSource, row As DataRow) As Boolean
- If row Is bs.At Then Return True
- If bs.IsBindingSuspended Then Return False
- Dim dv = DirectCast(bs.List, DataView)
- For i As Integer = 0 To bs.Count - 1
- If dv(i).Row Is row Then
- bs.CancelEdit() 'ansonsten setzter u.U. beim Moven die vorherige Row auf Rowstate.Changed
- bs.Position = i
- Return True
- End If
- Next
- Return False
- End Function
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „DerSmurf“ ()
-
Benutzer online 1
1 Besucher
-
Ähnliche Themen
-
SAR-71 - - Sonstige Problemstellungen
-
mathysjp - - Sonstige Problemstellungen
-
8 Benutzer haben hier geschrieben
- DerSmurf (46)
- ErfinderDesRades (42)
- VaporiZed (5)
- Kasi (2)
- petaod (1)
- mrMo (1)
- KingLM97 (1)
- EaranMaleasi (1)