Laden...

[Anders gelöst] Linq where Abfrage aus einer Liste

Erstellt von Reggi vor 6 Jahren Letzter Beitrag vor 6 Jahren 2.705 Views
R
Reggi Themenstarter:in
25 Beiträge seit 2016
vor 6 Jahren
[Anders gelöst] Linq where Abfrage aus einer Liste

verwendetes Datenbanksystem: MS SQL 2012, Entity Framework 6, .NET Framework 4.0

Hallo zusammen,

ich versuche gerade die Performance zu verbessern für eine Datenintegration, die User später einmal am Tag ausführen. Bisher dauert das bei ca. 60.000 Zeilen um die 3,5 Minuten, was noch zu lang ist.
Immerhin bin ich Dank Indexierung schon von 5 Minuten weggekommen.
Nachdem ich den Profiler genutzt habe, ist mir aufgefallen, dass ich sehr viel Zeit verschwende für An- und Abmelden auf der Datenbank durch das EF.
Das habe ich wiederum nur gemacht, weil ich pro 1.000 Datensätze, jeweils pro Datensatz geprüft habe, ob ein Datensatz mit diesem Schlüssel schon existiert.
Heißt ich habe eine Liste der zu prüfenden Objekte als Parameter der Funktion übergeben, die inserten soll, was noch nicht vorhanden ist. Bisher habe ich für jeden Datensatz die Nummer an diese Funktion übergeben:


public bool CheckExistingGHVReceiver(string no)
        {
            bool result = false;

            try
            {
                using (var ctx = GetDbContext())
                {
                    GHV_Receiver_intern check = (from receiver in ctx.GHV_Receiver_intern
                                 where receiver.No_ == no
                                 select receiver).First();
                    result = true;
                }
            }
            catch (InvalidOperationException)
            {
                result = false;
            }

            return result;
        }

So viel zur Vorgeschichte und zum Reindenken...
Um zu verhindern, dass tausende Male auf die Datenbank verbunden wird, will ich meine Datensätze, die übrigens aus einer anderen Datenbank kommen, nach einer Liste filtern.
Dafür habe ich mir nun eine Sicht erstellt, die nur noch die Nummern liest, statt der kompletten Daten. Zurück kommt eine Liste mit Strings.
Wäre ich in SQL, würde ich diese einfach mit where....in oder noch besser exists überprüfen, da ich ja nur wissen muss, ob es diese Nummer schon gibt und wenn nicht, eben hinzufügen kann zu der Liste, die nachher mit SaveChanges() in die DB geschrieben wird.

Nun ist die Frage, kann ich das sinnvoll in Linq abbilden? Bisher finde ich zwar die ganzen Erklärungen zu Find, Exists und wie sie alle heißen, aber alle haben gemeinsam, dass sie nach bestimmten Werten filtern.
Also Liste.Exists(x => x.No == 15) mal so als Beispiel.
Ich will aber meine Liste prüfen Liste.Exists(x => x.No in NoListe). Das wäre soooooo perfekt. Ich will natürlich auch in meiner Funktion eine foreach-Schleife möglichst verhindern und jede Nummer einzeln prüfen.

Ich hab auch schon versucht einfach die vorhandenen Nummern auszulesen (Hab jetzt eine Sicht, die mir nur die Nummern ausgibt als List<String>) und einfach aus der Liste zu löschen, die ich prüfe, aber dann muss ich durch die restlichen Einträge ja auch wieder komplett durch.... Arrg, zum Mäuse melken.

Und wenn es da nichts gescheites gibt, ist der letzte Ausweg wohl eine SP zu erstellen und den SQL Server das machen zu lassen.

Ich hoffe Jemand von euch weiß einen Rat.

Liebe Grüße
Reggi

2.079 Beiträge seit 2012
vor 6 Jahren

Also wenn ich mich nicht ganz doll irre, kann LINQ to SQL so spannende Methoden wie Contains auch parsen 😉 Da kommt dann "x IN (1, 2, 3)" raus

Du kannst also einfach myList.Contains(column) schreiben.

Das würde ich aber nicht machen, je nachdem wie viele Nummern Du prüfen willst.
Es gibt nämlich eine Obergrenze, was maximal an Parametern übergeben wird und deine Liste wird in viele Parameter aufgelöst.

Daher würde ich schon weiter versuchen, alle vorhandenen Nummern zu downloaden und darin zu suchen.
Da brauchst Du aber auch keine Sicht, es reicht, wenn Du schreibst:

var myNumbers = ctx.GHV_Receiver_intern.Select(receiver => receiver.No_).ToList();

Die Query-Syntax geht zwar auch, aber dann müsstest Du danach nochmal ToList aufrufen, um alle in den RAM zu holen.
Für so kleine Querys bevorzuge ich daher die Erweiterungsmethoden direkt.

Ob nun das vorherige Downloaden alles Nummern oder das Contains die schnellere Variante ist, musst Du messen.
Aber ich hab dich gewarnt: Es kann sein, dass Du deine Nummern nicht alle auf einmal übertragen kannst.
Maximal 2100 (siehe hier) kannst Du übertragen, Du musst deine Nummern also in 2100 (oder besser etwas kleiner) große Gruppen durchsuchen.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

R
Reggi Themenstarter:in
25 Beiträge seit 2016
vor 6 Jahren

Also erstmal natürlich danke.
Diese kurze Schreibweise ist mir sehr sympatisch 😉.

Und das mit dem Contains will bei mir einfach nicht, so wie ich das will.
Hier mal die Funktion, wie sie bisher aussieht:


public bool InsertGHVReceiverList(List<GHV_Receiver_intern> receiverList)
        {
            bool result = false;
            int counter = 0;
            GHV_Receiver_intern tmpReceiver = new GHV_Receiver_intern();

            using (var ctx = GetDbContext())
            {
                try
                {
                    // Liste bereits vorhandener Receiver
                    var ReceiverNoList = ctx.GHV_Receiver_intern.Select(receiver => receiver.No_).ToList();
                    tmpReceiver = receiverList.Contains(ReceiverNoList.No_);

                    // tmpReceiver zum Context hinzufügen
                    ....
                    ....

                    ctx.SaveChanges();
                    result = true;
                }
.
.
.

Egal was ich beim Contains anstelle, ich habe immer die Fehlermeldung, dass ich eine Liste nicht in GHV_Receiver_intern konvertieren kann.
Und wenn ich in so einem Contains wirklich nur 2100 Nummern vergleichen könnte, dann habe ich ein Problem.
Oder geht das in meinem Fall nicht, weil ich mit LINQ to Entity arbeite? Ich muss gestehen, ich habe mich mit den Unterschieden nicht wirklich befasst.

Und wenn ich mir so ansehe, dass ich die ganze Zeit Nummern suche, obwohl ich eine Liste brauche, wo die NICHT drin stehen, würde ich sagen, muss ich nochmal die Logik überdenken.

T
461 Beiträge seit 2013
vor 6 Jahren

Hallo,

ich denk du hast hier einen Denkfehler:


// So wirds eindeutlicher, WENN .No_ ein int ist, weiß ich nicht:
//var ReceiverNoList = ctx.GHV_Receiver_intern.Select(receiver => receiver.No_).ToList();
List<int> ReceiverNoList = ctx.GHV_Receiver_intern.Select(receiver => receiver.No_).ToList();
// Hier versuchst du mit einer Liste auf .No_ zuzugreifen, wie geht das? 
// Zudem vergleichst du hier eine Objektliste vom Typ 'GHV_Receiver_intern' mit dem was auch immer Rückgabewert...
tmpReceiver = receiverList.Contains(ReceiverNoList.No_);

Müßte vorerst entschlüsselt werden bevor es hier weitergeht 😁

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

R
Reggi Themenstarter:in
25 Beiträge seit 2016
vor 6 Jahren

Die Liste besteht aus Strings, da die Nummern einen Buchstaben-Präfix haben, ergo --> List<String>.

Und sorry, ich hatte vergessen das ".No_" weg zu nehmen. Der Fehler tritt natürlich auf, wenn ich die Stringliste dort als Filter nutzen will.

Ich versuche irgendwie eine Möglichkeit zu finden und ich würde eher sagen, dass da generell ein Denkfehler besteht. Sagt mir zumindest mein Bauchgefühl, wenn ich Schritt für Schritt diese Dinge in meinem Kopf durchgehe, aber ich bin jetzt so festgefahren, dass mir partout nichts anderes einfallen will. X(

T
461 Beiträge seit 2013
vor 6 Jahren

Aha, also so?:


List<string> ReceiverNoList = ctx.GHV_Receiver_intern.Select(receiver => receiver.No_).ToList();
tmpReceiver = receiverList.Contains(ReceiverNoList);

Wenn das so ist, dann versuchst du in der 2ten Zeile eine List<string> in einer List<GHV_Receiver_intern> zufinden, das geht natürlich nicht...

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

C
2.121 Beiträge seit 2010
vor 6 Jahren

Ich muss das einfach fragen...

Wäre ich in SQL

Und warum bist du dann nicht in SQL?

Datenverarbeitungen mit Abgleich usw. mache ich immer in SQL. Das ist zwar aus Sicht mancher längst nicht mehr cool und in und was auch immer. Aber schnell ist es. Also irgendwie vielleicht doch noch cool?

ist der letzte Ausweg wohl eine SP zu erstellen und den SQL Server das machen zu lassen.

Warum der letzte? Das wäre für mich der erste Ansatz, denn für sowas ist der SQL Server da.

R
Reggi Themenstarter:in
25 Beiträge seit 2016
vor 6 Jahren

Hey chilic,

naja cool sind SP's glaube ich immer noch 😉. Also zumindest machen sie nach dem Insert, den ich hier brauche auch die ganze Arbeit mit Triggern und noch vielen anderen SP's.

Und ja ich muss dir da Recht geben, nichts könnte effektiver vergleichen als der SQL Server selbst...
Ich schätze ich habe es nur so versucht zu lösen, weil es vorher auch so war und funktioniert hat.

Dann würde ich sagen, kann das Thema abgeschlossen werden.

2.079 Beiträge seit 2012
vor 6 Jahren

Naja, Du suchst doch nach einer string-Liste in einer string-Liste.
Die eine Liste ist die, die vom Server kommt, die Andere die, die Du lokal hast.

Du müsstest eigentlich in einer Schleife über die Liste der lokalen Nummern iterieren und pro Durchlauf prüfen ob diese Nummer in der Liste vom Server existiert.
Je nach Ergebnis handelst Du dann.

Ist das vielleicht dein Denkfehler?

Und was Stored Procedures angeht:
Die sind auch toll und vor allem kann man da ganz hervorragend optimieren.
Aber sie sind grausam zu testen und sie können nicht direkt von einem Source-Verwaltungssystem verwaltet werden.
Wenn Du unbedingt plain SQL schreiben willst, dann würde ich eher in C# das Script schreiben und dann so raus schicken.
Dann ist es zwar immer noch grausam zu testen, aber wenigstens hat ein Source-Verwaltungssystem die Hoheit drüber.

Ich würde immer erst einmal mit C# anfangen und versuchen, es dort zu lösen.
In den aller aller meisten Fällen reicht das auch völlig aus und in vielen Fällen ist es da sogar einfacher, weil Du einfachere Schleifen und automatische JOINs (bei ORMs wie EF) hast.
In diesen paar wenigen Fällen, wo eine SP z.B. aus Performance-Gründen wichtig ist, dann kann man da ja immer noch drüber nach denken.

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

R
Reggi Themenstarter:in
25 Beiträge seit 2016
vor 6 Jahren

Palladin007,

ja genau das war der Denkfehler, den hatte ich gerade aufgedeckt, als ich die SP angefangen habe.
Also für alle, die es interessiert:


                    List<String> ReceiverNoList = ctx.GHV_Receiver_intern.Select(receiver => receiver.No_).ToList();

                    foreach (GHV_Receiver_intern receiverListItem in receiverList)
                    {
                        if (!ReceiverNoList.Contains(receiverListItem.No_))
                            ctx.GHV_Receiver_intern.Add(receiverListItem);
                    }

SP's sind in der Tat nicht so einfach zu testen, aber wenn man sich da einmal durchgeschlängelt hat, dann geht es irgendwann. Die Fehlermeldungen sind teilweise echt "toll".
Aber wie du schon sagst, aus Performance Gründen kann man drüber nachdenken. Und genau das ist hier das Stichwort. Ich habe gerade mal die Zeit gemessen...
Ich muss natürlich in erster Linie wieder splitten, denn die Auswertung aller Daten hat kompakt jetzt mal 27 Minuten gedauert. (Vorher 3,5)

Ich bin dann mal meine SP erstellen 8).

Danke, und schönes WE an euch.