DBContext.Dispose() soll auch Datenmodell wegschmeißen

  • C#
  • .NET 5–6

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von ISliceUrPanties.

    DBContext.Dispose() soll auch Datenmodell wegschmeißen

    Hallo,

    ich habe ein Problem mit Dispose bei EF, d.h. DBContext.

    Mein Context:

    Spoiler anzeigen


    C#-Quellcode

    1. internal class Context :DbContext
    2. {
    3. private int index;
    4. public Context(int index)
    5. {
    6. this.index = index;
    7. }
    8. internal virtual DbSet<ZugEntity> Züge { get; set; }
    9. protected override void OnConfiguring(DbContextOptionsBuilder builder)
    10. {
    11. if(!builder.IsConfigured)
    12. {
    13. builder.UseSqlServer(connectionstringSQLServer)
    14. .LogTo(s => Debug.Print(s), Microsoft.Extensions.Logging.LogLevel.Information)
    15. ;
    16. }
    17. }
    18. protected override void OnModelCreating(ModelBuilder builder)
    19. {
    20. builder.Entity<ZugEntity>(
    21. xx =>
    22. {
    23. xx.ToTable($"Startindex02_{index:00}");
    24. xx.HasNoKey();
    25. });
    26. }
    27. public override void Dispose()
    28. {
    29. base.Dispose();
    30. }
    31. }


    und in der Hauptroutine gibt es sowas:

    Spoiler anzeigen

    C#-Quellcode

    1. for(int loop = maxzug; loop > 0; loop--)
    2. {
    3. using(var context = new Context(loop))
    4. {
    5. var tmp = context.Entities;
    6. }
    7. }



    Nach dem Ende von using wird der Context über Dispose weggeschmissen, das Model aber nicht. Beim zweiten Durchlauf der Schleife wird OnModelCreating nicht mehr aufgerufen. Das macht Sinn wenn ich ein umfangreiches Datenmodel erzeuge und ich tausende Zugriffe auf die DB mit jeweiles neuen Context-Instanzen mache. Da spare ich Aufwand.

    Bei mir müsste das Model aber auch beim Dispose gelöscht werden. Wie mache ich das denn?

    Gruß

    MQ
    Hallo,
    warum möchtest du denn die erzeugten Tabellen wieder loswerden? Werden die Daten nicht weiter benötigt? Ich sehe zwei Möglichkeiten. 1. Du nimmst keinen SQL Server als Provider, der die Daten persistiert, sondern verwendest einen In-Memory-Ansatz oder 2. Du rufst beim Beenden des Programms EnsureDeleted auf. Das löscht die komplette DB.
    Es ist übrigens sehr schlecht, den Context in der Schleife immer wieder zu erzeugen. Gängig ist es, den Context beim Programmstart zu erzeugen und ihn per Dependency Injection verfügbar zu machen.

    MasterQ schrieb:

    Nach dem Ende von using wird der Context über Dispose weggeschmissen, das Model aber nicht. Beim zweiten Durchlauf der Schleife wird OnModelCreating nicht mehr aufgerufen.
    Bist du ganz sicher?
    Das wäre ja ein phänomenaler Verstoss gegen so allerlei, was ich für selbstverständlich halte.
    Also wenn new ein zweites Mal aufgerufen wird, dann soll (in meiner Welt) all das ein zweites Mal durchlaufen werden, was beim ersten Mal auch durchlaufen wurde.
    new eben.


    ISliceUrPanties schrieb:

    Es ist übrigens sehr schlecht, den Context in der Schleife immer wieder zu erzeugen. Gängig ist es, den Context beim Programmstart zu erzeugen und ihn per Dependency Injection verfügbar zu machen.
    Echt? hast du da Background-Links zu?
    Weil ich hab (vor jahren) das genaue Gegenteil gehört: Ein DbContext ist nicht dafür vorgesehen, für längere Zeit zu existieren ("Unit of Work" hiess damals das Hype-Wort)

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ISliceUrPanties schrieb:

    1. Du nimmst keinen SQL Server als Provider, der die Daten persistiert, sondern verwendest einen In-Memory-Ansatz oder
    2. 2. Du rufst beim Beenden des Programms EnsureDeleted auf. Das löscht die komplette DB.




    Es handelt sich um statische Tabellen, deren Inhalte sich nie ändern. Die Anzahl der Datensätze beläuft sich auf knapp 1,5 Mrd, die im Arbeitsspeicher knappe 60GB benötigen. Auch wenn ich in jedem Schleifendurchgang den Context neu erzeuge, so dauert ein Schleifendurchgang teilweise mehrere Minuten und greift bis zu 300 Mio Datensätze an.

    Ich muss halt bei jedem Schleifendurchgang eine andere Tabelle ansprechen. Ich könnte alles zwar in eine einzige Tabelle stopfen, doch dann dauern die Abfragen (ein Schleifendurchgang) Stunden. Ich sortiere also in 31 Tabellen vor. Dann geht das schneller. So hoffe ich. Ich hänge gerade am zweiten Schleifendurchgang und am angesprochenen Sachverhalt.


    Es ist übrigens sehr schlecht, den Context in der Schleife immer wieder zu erzeugen. Gängig ist es, den Context beim Programmstart zu erzeugen und ihn per Dependency Injection verfügbar zu machen.


    Das hängt von verschiedenen Faktoren ab. So habe ich das auch gelesen wie das Rad unten ausführt.


    ErfinderDesRades schrieb:

    Bist du ganz sicher?
    Das wäre ja ein phänomenaler Verstoss gegen so allerlei, was ich für selbstverständlich halte.
    Also wenn new ein zweites Mal aufgerufen wird, dann soll (in meiner Welt) all das ein zweites Mal durchlaufen werden, was beim ersten Mal auch durchlaufen wurde.
    new eben.


    Ich bin mir sicher. Ich habe mich gewundert, warum trotz einer loop Variablen mit dem Inhalt 30 die falschen Daten kommen, nämlich die von loop=31 dem ersten Durchlauf. Dass OnModelCreating nur beim ersten mal aufgerufen wird, bestätigen mein Durchläufe mit dem Debugger und auch die Log-Ausgaben.

    Ich hätte das anders erwartet und bin etwas verwundert.


    -- EDIT --

    Im Anhang jetzt ein kleines Progrämmle zum Testen, das bestätigt, dass OnModelCreating nur einmal aufgerufen wird.

    C#-Quellcode

    1. for(int i = 1; i < 3; i++)
    2. {
    3. using(var context = new Context(i))
    4. {
    5. Console.WriteLine($"Schleife mit Index: {i}");
    6. var e = context.MyEntities.ToList();
    7. }
    8. }


    C#-Quellcode

    1. internal class Context :DbContext
    2. {
    3. private int index;
    4. public Context(int i)
    5. {
    6. index = i;
    7. }
    8. public DbSet<MyEntity> MyEntities { get; set; }
    9. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    10. {
    11. if(!optionsBuilder.IsConfigured)
    12. {
    13. optionsBuilder.UseSqlite("Data Source = Datenbank.sqlite")
    14. .LogTo(s => Debug.Print(s), Microsoft.Extensions.Logging.LogLevel.Information);
    15. }
    16. Console.WriteLine($"OnConfiguring mit Index: {index}");
    17. }
    18. protected override void OnModelCreating(ModelBuilder modelBuilder)
    19. {
    20. modelBuilder.Entity<MyEntity>(xx=>{ xx.ToTable($"Tabelle{index}").HasNoKey(); });
    21. Console.WriteLine($"OnModelCreating mit Index: {index}");
    22. }
    23. }


    C#-Quellcode

    1. internal class MyEntity
    2. {
    3. public int Inhalt { get; set; }
    4. }


    Ausgabe der Console:



    Debug-Fenster:



    Und nu?
    Dateien
    • VBP_Context.zip

      (2,9 kB, 41 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „MasterQ“ ()

    ErfinderDesRades schrieb:

    Ein DbContext ist nicht dafür vorgesehen, für längere Zeit zu existieren ("Unit of Work" hiess damals das Hype-Wort)

    Das gilt auch weiterhin. Ich meinte mit erstellen, dass der Context dem DI-Container bei Programmstart bekannt gemacht wird. Der DI-Container kümmert sich dann um die "Lifetime" des Contexts. (Ist kein muss, das so zu lösen, ich wollts nur erwähnt haben.)

    @MasterQ ich bin immer noch nicht schlauer, was du eigentlich erreichen möchtest. Wenn du auf weitere Tabellen in deiner DB zugreifen möchtest, musst du diese über den Context auch bekannt machen. MyEntity sollte dann also Eigenschaften haben, die deine Tabelle widerspiegeln. Wenn deine Datenbank also schon existiert, dann schau mal hier, hier wird der "Database first"-Anstatz mit EF erklärt. Du kannst mit EF außerdem auch weiterhin selbstgeschriebenes SQL verwenden.

    ISliceUrPanties schrieb:

    @MasterQ ich bin immer noch nicht schlauer, was du eigentlich erreichen möchtest. Wenn du auf weitere Tabellen in deiner DB zugreifen möchtest, musst du diese über den Context auch bekannt machen. MyEntity sollte dann also Eigenschaften haben, die deine Tabelle widerspiegeln. Wenn deine Datenbank also schon existiert, dann schau mal hier, hier wird der "Database first"-Anstatz mit EF erklärt. Du kannst mit EF außerdem auch weiterhin selbstgeschriebenes SQL verwenden.


    Ich habe 1,5Mrd statische Datensätze, die ich mehrfach durchsuchen muss. Die Daten existieren bereits und lassen sich in 32 Gruppen teilen. Diese 32 Gruppen werden mit einer for-Schleifen durchlaufen. In Tabelle 30 suche ich nach Elementen, die eine Eigenschaft mit Elementen in Tabelle 29 gemeinsam haben, dann nächster Schleifendurchgang und von den Elementen aus 29 suche ich dann in Tabelle 28 die was die mit denen aus der 29 gemeinsam haben. und so weiter!

    Im Prinzip kann ich alle Datensätze in eine einzige Tabelle hauen. Die Abfragen dauern dann aber ewig und ich muss schon an den TimeOut-Parametern schrauben. Es kann durchaus eine oder mehrere Stunden dauern bis eine einzige Abfrage fertig ist. Durch das Aufspalten in diese 32 Gruppen reduziere ich die Größe einer einzelnen Tabelle und erhoffe mir so, die langen Abfragezeiten deutlich zu verkürzen. In den Abfragen kommt ein Distinct vor, so dass alle Elemente miteinander verglichen werden müssen. Daher kommt die doch enorme Zeit, die eine solche Abfrage benötigt.

    Wenn ich das jetzt mit EF mache, müsste ich 32 mal identische Entitätsklassen (mit unterschiedlichen Namen) definieren und auch 32 (identische) Context-Objekte. Eine Entitätsklasse kann nur einer einzigen Tabelle/Context zugeordnet werden und der Context auch nur einer einzigen Tabelle. Ich dachte halt, wenn ich den Tabellenname in OnModelCreating dynamisch setze, dass ich mit einer Entitätsklasse und einem Context auskomme. Das hat sich jetzt zerschlagen, da sich irgendwas tief hinter dem Context-Objekt das Datenmodell merkt und beim zweiten und weiteren Instantiieren der Context-klasse OnModelCreating nicht mehr aufgerufen wird.

    Alle Tabellen haben die gleiche Struktur, sind bis auf die Inhalte absolut identisch und könnten alle (theoretisch) mit der gleichen Entitätsklasse angesprochen werden. Es macht doch keinen Sinn, 32 Entitätsklassen und 32 Contextklassen zu definieren, die alle identisch sind. Gut, in den Contextklassen wären die Tabellennamen anders, aber das wär auch alles.

    MasterQ schrieb:

    Ich habe 1,5Mrd statische Datensätze, die ich mehrfach durchsuchen muss. Die Daten existieren bereits und lassen sich in 32 Gruppen teilen.
    Da eröffnet sich vlt. die Möglichkeit, die Daten in einer Tabelle zu belasssen, aber die Tabelle mit einem Datenbank-Index ausszurüsten.
    So ein Index beschleunigt eine Db-Suche ungeheuerlich.
    Vielleicht sind noch weitere Suchkriterien alss Index formulierbar.
    Jetzt verstehe ich dein Problem. Aber was ich auf keinen Fall machen würde, ist die Tabelle aufzusplitten. Lass die Daten in einer Tabelle. Performanceprobleme bei der Abfrage deiner Daten kann auf viele Dinge zurückzuführen sein. Wie ErfinderDesRades schon schrieb, ist es vielleicht ein fehlender Index/Schlüssel.
    Bist du mit dem Microsoft SQL Server unterwegs? Evtl. ist auch deine eigentliche Abfrage nicht optimal. Das kannst du prüfen, wenn du dein SQL über das Management Studio ausführst und dir dazu einen Ausführungsplan generieren lässt. In dem Ausführungsplan siehst du, welcher Bereich deiner Abfrage die meiste Zeit kostet. Und du erkennst, ob wirklich ein Key/Index fehlt.
    Wenn die Tabelle auch noch viele Felder enthält, könnte es helfen, die Tabelle zu partitionieren.
    Oder der Server ist einfach Hardwareseitig nicht entsprechend potent, um die Masse an Daten zu verarbeiten.

    ISliceUrPanties schrieb:

    Das kannst du prüfen, wenn du dein SQL über das Management Studio ausführst und dir dazu einen Ausführungsplan generieren lässt. In dem Ausführungsplan siehst du, welcher Bereich deiner Abfrage die meiste Zeit kostet. Und du erkennst, ob wirklich ein Key/Index fehlt.


    Ich habe die Daten mal in zwei getrennte Dbs auf dem selben Server geschoben, einmal mit PrimaryKey (PK) und einmal ohne. Hier die Ablaufpläne.



    Ohne PK sind beide Abfragen deutlich schneller. Warum im zweiten Fall gemeckert wird, es wäre kein Index vorhanden, weiß ich nicht. Mit Indizes habe ich nicht wirklich Erfahrung

    Die Tabellen bestehen einmal (mit Index/PK) aus einem int und zwei long (bigint) und das andere mal aus nur den zwei long. Daher hilft der Index wohl nicht.


    Wenn die Tabelle auch noch viele Felder enthält, könnte es helfen, die Tabelle zu partitionieren.
    Oder der Server ist einfach Hardwareseitig nicht entsprechend potent, um die Masse an Daten zu verarbeiten.


    Der Server läuft auf einem Ryzen 7 5700X mit 64GB DDR4 und 1TB SSD. Es könnte sein, dass die SSD der Bottleneck ist. Schaufle ich die Daten von einer Tabelle in eine andere, erhalte ich "lediglich" Transferraten von 150MB/s und keine 3500MB/s wie es die Spezifikation verlauten lässt. Meine Untersuchungen dazu sind aber noch nicht abgeschlossen.
    Ok, das erste Statement kann man so nicht herannehmen. Ein Select COUNT(*) FROM... ohne Where muss immer alle Daten lesen. Da ist es fast egal, ob die Daten aus einem Index oder der Tabelle kommen. Aber generell gilt Table Scans sind schlecht und müssen vermieden werden! Bei einem Table Scan wird die komplette Tabelle gelesen, um die relevanten Daten zu finden. Index Scans sind besser, möchte man aber auch so gut es geht vermeiden. Wenn die Daten über ein Index Seek/Lookup ermittelt werden können, läuft es optimal.

    MasterQ schrieb:

    Warum im zweiten Fall gemeckert wird, es wäre kein Index vorhanden, weiß ich nicht.

    Das ist einfach zu erklären. Dein Statement hat eine Where-Clause mit dem Feld "Stellung". Dieses Feld ist nicht Teil deines Primärschlüssels (oder irgendeines Index) und er muss wieder die Tabelle/Schlüssel komplett durchkämmen, um die Daten zu ermitteln. Deshalb schlägt er dir vor, einen Index mit dem Feld "Stellung" zu erzeugen. Er sagt dir auch, dass durch diesen Index die Kosten der Abfrage um 99,9992% reduziert werden könnten. Leg den Index mal an und führe das Statement noch mal aus - du merkst den Unterschied sofort :)