Laden...

Linq to SQL Performanceproblem

Erstellt von sra vor 14 Jahren Letzter Beitrag vor 14 Jahren 1.204 Views
S
sra Themenstarter:in
230 Beiträge seit 2004
vor 14 Jahren
Linq to SQL Performanceproblem

Hallo Allerseits

Ich mache für ein aktuelles Projekt eine statistische Auswertung der Anzahl verkaufter Produkte (Geschenkartikel) pro Koffer (die Artikel sind an Koffer "gebunden" und es intressiert mit welchem Koffer welche Artikel am ehesten weggehen).

Auf der DB Seite habe ich folgende Tabellen:

  • BestellKopf
  • BestellDetail (fk zu BestellKopf, fk zu GeschenkArtikel)
  • GeschenkArtikel
  • FakturaKopf (fk zu BestellKopf)

Die Abfrage sieht so aus:

From p In db.BestellungsDetails() _
Where p.Bestellung.Datum >= DatumVon _
And p.Bestellung.Datum <= DatumBis _
Group p By p.GeschenkArtikel.Gruppe Into Group _
Select _
    Produkt = Gruppe, _
    Braut = Group.Where(Function(g) g.Bestellung.Quelle = 1 And g.Menge > 0).Sum(Function(g) g.Menge), _
    Baby = Group.Where(Function(g) g.Bestellung.Quelle = 2 And g.Menge > 0).Sum(Function(g) g.Menge), _
    JuFa = Group.Where(Function(g) g.Bestellung.Quelle = 3 And g.Menge > 0).Sum(Function(g) g.Menge), _
    FTop = Group.Where(Function(g) g.Bestellung.Quelle = 5 And g.Menge > 0).Sum(Function(g) g.Menge), _
    Post = Group.Where(Function(g) g.Bestellung.Quelle = 6 And g.Menge > 0).Sum(Function(g) g.Menge), _
    INet = Group.Where(Function(g) g.Bestellung.Quelle = 7 And g.Menge > 0).Sum(Function(g) g.Menge), _
    ADM = Group.Where(Function(g) g.Bestellung.Quelle = 0 Or g.Bestellung.Quelle Is Nothing And g.Menge > 0).Sum(Function(g) g.Menge), _
    Total = Group.Sum(Function(g) g.Menge), _
    Ret = Group.Where(Function(g) g.Menge < 0).Sum(Function(g) g.Menge) * -1()

Wenn ich die so ausführe, dann dauert es ganz schön lange. Allerdings kann ich den generierten SQL Query anhand von Loadoptions schon einigermassen optimieren.


lo.LoadWith(Of Data.BestellungsDetail)(Function(p) p.Bestellung)
lo.LoadWith(Of Data.Bestellung)(Function(p) p.FakturaKopfs)
lo.LoadWith(Of Data.BestellungsDetail)(Function(p) p.GeschenkArtikel)

Trotzdem dauert die Abfrage noch sehr lange (x Sekunden), sobald ich statt des Rechnungsdatums (wie im query oben) das FakturaDatum nehme (also statt Where p.Bestellung.Datum ≥ DatumVon gibt es eben Where p.Bestellung.FakturaKopfs.FirstOrDefault.Datum ≥ DatumVon). Weiss jemand weiter? Wie kann ich Loadoptions über zwei Verknüpfungen hinweg definieren?

Ausserdem sollte ich in diesem Query noch irgendwie nachträglich ein WHERE auf die Sprache (im Bestellkopf) setzen können. Allerdings ist er natürlich zu dem Zeitpunkt schon gruppiert und hat das Sprachefeld nicht im Angebot. Zwei verschiedene Queries (für beide Sprachen) will ich aber vermeiden. Wie würded ihr da vorgehen?

Wäre sehr khul wenn sich jemand mit mehr Ahnung kurz Zeit nimmt.

Danke und gruss
sra

Wenn Zeit in Geschichte übergeht und keine Blüten trägt werden Zukunftsbilder blass //Clueso

3.003 Beiträge seit 2006
vor 14 Jahren

Heilige Sch...ich mein, ach, du liebe Güte, was für ein Select.
Zuerst einmal versuchst du Reporting per Linq To Sql - klingt ein bisschen wie das Hammer-Syndrom (wenn man als Werkzeug einen Hammer hat, sieht alles wie ein Nagel aus).

Zum zweiten dürfte das generierte SQL schon sehr abartig sein. Schau's dir mal im Profiler an, ist sicherlich unterhaltsam.

Zum dritten bist du mit den LoadOptions schon gut auf der Spur einer Optimierung, da das nunmal so ziemlich die einzige Kontrolle ist, die du bei LinqToSql darüber hast, welche Tabellen inkludiert werden. Dass die Abfrage bei Änderung auf Faktura-Datumsprüfung deutlich länger dauert, liegt, denke ich, auf der Hand, denn du joinst an jede Zeile die gesamte Fakturakopftabelle, sortierst sie, und selektierst dann eine Spalte in der ersten Zeile des Fakturakopfs. Wir reden hier von exponentiell steigender Datenmenge.

Das hilft dir jetzt alles nicht sonderlich weiter, ich weiß. Spalte die Abfrage auf - im Extremfall mach die Auswertung komplett im Speicher (Linq auf Collections), statt eine derartige SQL-Abfrage zu generieren. Oder benutz ein richtiges Reporting 😃.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

S
sra Themenstarter:in
230 Beiträge seit 2004
vor 14 Jahren

Hallo Latino

Der generierte Query war echt eine Augenweide ^^
Zwar kann unser alter SQL Server hier noch kein Profiling (glaube ich), aber man kann sich ja den Query mit .toString in ein LogFile schreiben lassen.

Ich habe deinen Rat befolgt und den Linq Query in zwei Teile gesplittet. Der erste liest mir die Daten aus der DB in eine Liste und der zweite gruppiert diese dann so wie ich das will. Dauert zwar noch immer 2 Sekunden, aber damit kann ich leben.

Kleine Frage am Rande noch dazu:
Da ich für jede Woche ein Grid binde (innerhalb eines Repeaters), habe ich den ersten Query (bringt mir gleich die Daten für das ganze Jahr) nicht in der selben Funktion wie den zweiten der diesem noch das Wochenkriterium hinzufügt und gruppiert.
Da aber das Resultat der ersten Abfrage einen "neuen" anonymen Datentyp hervorbringt, kennt die zweite Funktion dessen Members nicht, auch wenn ich natürlich Zugriff habe. Ich habe mir deshalb eine Klasse gemacht, und habe das Ergebnis des ersten Queries per foreach in eine list<meinerKlasse> geadded. Gäbe es da eine elegantere Möglichkeit?

Wenn Zeit in Geschichte übergeht und keine Blüten trägt werden Zukunftsbilder blass //Clueso

3.003 Beiträge seit 2006
vor 14 Jahren

Hallo, sra,

Da ich für jede Woche ein Grid binde (innerhalb eines Repeaters), habe ich den ersten Query (bringt mir gleich die Daten für das ganze Jahr) nicht in der selben Funktion wie den zweiten der diesem noch das Wochenkriterium hinzufügt und gruppiert.
Da aber das Resultat der ersten Abfrage einen "neuen" anonymen Datentyp hervorbringt, kennt die zweite Funktion dessen Members nicht, auch wenn ich natürlich Zugriff habe. Ich habe mir deshalb eine Klasse gemacht, und habe das Ergebnis des ersten Queries per foreach in eine list<meinerKlasse> geadded. Gäbe es da eine elegantere Möglichkeit?

Ich bin mir noch nicht ganz sicher, dass ich verstehe, was du machst. Ich vermute folgendes Szenario:

  • erste Linq-Anweisung bringt eine IQueryable<Klasse1>, wo du eigentlich IQueryable<Klasse2> brauchst. Klasse 1 und Klasse 2 unterscheiden sich jedoch kaum, oder gar nicht.

Wenn das der Fall ist:


var result = (from p in foo 
                  where ...
                  group p by p.bar into ...
                   select new Klasse2() 
                  {
                        Property1 = p.Property,
                        Property2 = p.Name
                   }).ToList();

Debug.Assert(result is List<Klasse2>, "Whoops. Wrong type.");

So ähnlich halt. Also select new MyType() { }, anstatt mit anonymen Klassen zu hantieren.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

S
sra Themenstarter:in
230 Beiträge seit 2004
vor 14 Jahren

Hallo Latino

Danke übrigens zunächst mal für deinen Support hier.

Wenn ich die Klasse direkt als new angebe, kann ich mir schon mal den foreach sparen, der die Resultate aus dem anonymen iqueryable in die Liste meiner klasse umgeschichtet hat.

Trotzdem habe ich noch eine Klasse erstellt, nur um die Daten anständig "zwischen"speichern zu können. Die Datenstruktur dieser Klasse (Datum, ArtikelName, Anzahl, KofferCode) brauche ich sonst nirgends. Wenn mir einen ganz normalen Linq Query erstelle, welcher mir die Daten in dieser Form zurückgibt (ohne Angabe einer Klasse), dann erstellt er mir ja automatisch einen anonymen Typen, auf welchen ich im folgenden zugreifen kann.
Speichere ich das Resultat des Query jedoch auf Klassenebene um von einer anderen Methode zugreifen zu können, dann weiss der Compiler die einzelnen Member nicht - ich kann diese also nicht weiterverwenden (zumal ich ja auch gar nicht weiss in welchen Typen ich casten müsste).

Gibt es eine Möglichkeit wie ich z.B. definiere, dass der Rückgabetyp eine aut. erstellte Klasse sein soll, die der Compiler dann auch kennt. Naja - wär vielleicht nicht so logisch, also gibt es das wahrscheinlich nicht, aber ich frage trotzdem mal ganz leise nach 😃

Wenn Zeit in Geschichte übergeht und keine Blüten trägt werden Zukunftsbilder blass //Clueso

3.003 Beiträge seit 2006
vor 14 Jahren

Gibt es eine Möglichkeit wie ich z.B. definiere, dass der Rückgabetyp eine aut. erstellte Klasse sein soll, die der Compiler dann auch kennt. Naja - wär vielleicht nicht so logisch, also gibt es das wahrscheinlich nicht, aber ich frage trotzdem mal ganz leise nach 😃

Naja, nach meinem Verständnis, das falsch sein kann, ist die Anonymität der Klasse ja darin begründet, dass ihre Schnittstellen nicht bekannt - also anonym - sind. Außerhalb des Kontexts, in der sie erstellt wurde, würde ich die Frage, ob man auf ihre Properties zugreifen kann, deshalb verneinen. Also auf deutsch - wenn du die anonyme Klasse ausserhalb der Methode, in der sie erstellt wurde, verwenden willst, wird das wohl nicht gehen. Denk ich mal so.
Vielleicht mit Reflection, wobei: wenn ich mit einer anonymen Klasse arbeite, weiß ich ja, dass das Ding eben nach außen unbekannt ist. Möchte ich, dass es nach außen bekannt ist, darf ich eben keine anonyme Klasse benutzen.

Wie gesagt, ich kann auch falsch liegen. Nachteil von Anonymen ist ja überall, dass sie einem in den Hintern treten können, und man nichtmal sagen kann, wer's war. Sobald ich das Ergebnis weiterreiche, benutze ich sie nicht.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)