none
Einsatz von Entity-Framework im Webservice mit unterschiedlichen Datenbanken RRS feed

  • Frage

  • Hallo zusammen,

    unser Webservice wächst und gedeiht und nun kommt man auf die Idee, das EF einzusetzen, um die Entwicklung zu beschleunigen. Dabei stellt sich mir eine Frage:

    • Viele Beispiel mit dem EF sind auf die Verwendung mit einer Datenbank ausgelegt. D.h., irgendwo in einer Datei stehen die Verbindungsparameter und damit wird nun gearbeitet.
    • Unser Webservice arbeitet jedoch mit unterschiedlichen Datenbanken (alle auf dem gleichen SQL-Server freilich). D.h., jeder Aufruf aus einem Endpunkt verwendet die E-Mailadresse des Aufrufers um die passende Datenbank zu finden. Der Name der Datenbank wird dann an die weiter hinten liegenden Schichten durchgereicht, welche nun eine "Connection" eröffnen und diverse SQL-Befehle ausführen.
    • Läßt sich so eine dynamische Datenbankverwendung auch mit dem EF einrichten? Gibt es dafür vielleicht auch Beispiele? Meine Erfahrung mit dem EF ist leider noch sehr dünn.

    Viele Grüße, Karsten.

    Samstag, 3. September 2022 09:52

Antworten

  • Ok dann ist es einfach. Man muss das Nuget-Paket Microsoft.EntityFrameworkCore.SqlServer zum Projekt hinzufügen und die Datenbankstruktur in .NET abbilden. Schau dir dazu diese Doku an Datenbank zuerst oder suche nach "entity framework db first"

    Der DbContext könnte so aussehen

    internal class MyTestDb : DbContext
    {
        private readonly string _email;
    
        public MyTestDb(string email)
        {
            _email = email;
        }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string dbName = string.Empty;
    
            //Anhang der Mail den DB Namen rausfinden
    
            //Der Connection String kann auch anders aussehen ist nur ein beispiel
            optionsBuilder.UseSqlServer($"server=.;database={dbName};trusted_connection=true;");
        }
    
        public DbSet<Address> Addresses { get; set; }
        //Weitere Tabellen
    }
    Man könnte auch den Connection String an die Klasse übergeben


    Gruß Thomas
    Github

    Samstag, 3. September 2022 11:11
  • Den größten Fehler den man mit EF machen kann ist nach irgendwelchen "haken" oder Komplexität zu suchen. Diese gibt es aus der Anwendersicht nicht. 

    Der DbContext ist eine Klasse mit Listen und folgt den selben regeln wie jede andere Klassen mit Listen. Man kann diese Listen mit Linq abfragen, neue Objekt hinzufügen, ändern oder löschen. Änderungen an den Listen müssen aber mit DbContext.SaveChanges abgeschlossen werden. Erst dann werden die Änderungen an die DB übermittelt.

    Man sollte sich den Aufruf von SaveChanges aber immer gut überlegen. Mal 2 Beispiele

    //1000 Adreesen in die Db Schreiben
    for (int i = 0; i < 1000; i++)
    {
        db.Addresses.Add(new Address() { Street = $"S - {1}" });
        db.SaveChanges();
        //Schreiben in die DB würde ewig dauer da nach jedem Add eine Verbindung zur DB aufgebaut werden muss
    }
    
    //1000 Personen mit Adresse in die Db schreiben
    //Beziehung der Tabellen 1:N
    //Der Primary Key von Tabelle Person ist PersonId mit auto increment
    for (int i = 0; i < 1000; i++)
    {
        var p = new Person() { Name = $"N - {1}" };
        db.People.Add(p);
        db.SaveChanges();
    
        //Hier muss SaveChanges aufgerufen werden bevor die Addresse angelegt werden kann
        //Da die Datenbank für diese Person einen Primary Key vergeben muss
    
        db.Addresses.Add(new Address() { PersonId = p.PersonId, Street = $"S- {i}" });
    }


    Gruß Thomas
    Github

    Samstag, 3. September 2022 12:24

Alle Antworten

  • Hallo Karsten,

    ja sowas lässt sich erreichen. Sind die Datenbanken alle gleich oder unterscheiden sich die Tabellen? 


    Gruß Thomas
    Github

    Samstag, 3. September 2022 10:23
  • Hallo Thomas, schön von dir zu hören.

    Ja, die Datenbanken sind alle identisch, sie gehören nur verschiedenen Kunden. Es gibt auch bereits ein Tool, um die Datenbanken alle auf den gleichen Stand zu bringen (Tabellenstruktur + ausgelieferte Inhalte wie Bankleitzahlen, Postleitzahlen). Das Thema der Migration entfällt also, der Code kann davon ausgehen, dass die angesprochenen Spalten vorhanden sind.

    Gruß, Karsten.

    Samstag, 3. September 2022 10:53
  • Ok dann ist es einfach. Man muss das Nuget-Paket Microsoft.EntityFrameworkCore.SqlServer zum Projekt hinzufügen und die Datenbankstruktur in .NET abbilden. Schau dir dazu diese Doku an Datenbank zuerst oder suche nach "entity framework db first"

    Der DbContext könnte so aussehen

    internal class MyTestDb : DbContext
    {
        private readonly string _email;
    
        public MyTestDb(string email)
        {
            _email = email;
        }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            string dbName = string.Empty;
    
            //Anhang der Mail den DB Namen rausfinden
    
            //Der Connection String kann auch anders aussehen ist nur ein beispiel
            optionsBuilder.UseSqlServer($"server=.;database={dbName};trusted_connection=true;");
        }
    
        public DbSet<Address> Addresses { get; set; }
        //Weitere Tabellen
    }
    Man könnte auch den Connection String an die Klasse übergeben


    Gruß Thomas
    Github

    Samstag, 3. September 2022 11:11
  • Hallo Thomas,

    das sieht schon mal sehr gut aus. Ich nehme an, dass es auch ein passendes Paket für den PostgreSQL gibt?

    Nochmal fürs Verständnis: Die Klasse "MyTestDb" gibt es nur einmal und sie stellt praktisch die Verbindung von den Fachklassen wie "Address" zur Datenbank her, oder?

    Gruß, Karsten.

    Samstag, 3. September 2022 11:18
  • Ja es gibt auch Pakete für PostgreSQL aber nicht von MS selbst. Das macht aber nichts musst halt selbst mal schauen welche gut funktionieren.

    Genau die Klasse gibt es nur einmal. In dieser Klasse werden alle Tabellen der Datenbank angegeben die Abgefragt werden sollen. Es müssen aber nicht alle Tabellen der Datenbank aufgeführt sein. Es können mehrere Instanzen zeitgleich dieser Klasse zur gleichen oder unterschiedlichen Datenbanken vorhanden sein.

    EF ist ein OR Mapper. Beim erstem zugriff auf die Datenbank wird OnConfiguring Aufgerufen und eine SQLConnectien zur DB aufgebaut. Danach werden alle Anfrage und Ergebnisse zwischen SQL und .NET übersetzt.


    Gruß Thomas
    Github

    Samstag, 3. September 2022 11:51
  • Ah, sehr schön, klingt gut.

    Ich habe dir eine Mail geschrieben, schau mal rein.

    Viele Grüße, Karsten.

    Samstag, 3. September 2022 12:00
  • Den größten Fehler den man mit EF machen kann ist nach irgendwelchen "haken" oder Komplexität zu suchen. Diese gibt es aus der Anwendersicht nicht. 

    Der DbContext ist eine Klasse mit Listen und folgt den selben regeln wie jede andere Klassen mit Listen. Man kann diese Listen mit Linq abfragen, neue Objekt hinzufügen, ändern oder löschen. Änderungen an den Listen müssen aber mit DbContext.SaveChanges abgeschlossen werden. Erst dann werden die Änderungen an die DB übermittelt.

    Man sollte sich den Aufruf von SaveChanges aber immer gut überlegen. Mal 2 Beispiele

    //1000 Adreesen in die Db Schreiben
    for (int i = 0; i < 1000; i++)
    {
        db.Addresses.Add(new Address() { Street = $"S - {1}" });
        db.SaveChanges();
        //Schreiben in die DB würde ewig dauer da nach jedem Add eine Verbindung zur DB aufgebaut werden muss
    }
    
    //1000 Personen mit Adresse in die Db schreiben
    //Beziehung der Tabellen 1:N
    //Der Primary Key von Tabelle Person ist PersonId mit auto increment
    for (int i = 0; i < 1000; i++)
    {
        var p = new Person() { Name = $"N - {1}" };
        db.People.Add(p);
        db.SaveChanges();
    
        //Hier muss SaveChanges aufgerufen werden bevor die Addresse angelegt werden kann
        //Da die Datenbank für diese Person einen Primary Key vergeben muss
    
        db.Addresses.Add(new Address() { PersonId = p.PersonId, Street = $"S- {i}" });
    }


    Gruß Thomas
    Github

    Samstag, 3. September 2022 12:24
  • Hallo zusammen,

    ich muss dieses Thema nochmal aufleben lassen. Zunächst geht mal mein aufrichtiger Dank an Thomas, welcher mir sehr kompetent bei diesem Thema geholfen hat.

    Jetzt hätte ich noch ein Problem:

    • Bei bestimmten Tabellen gibt es noch verknüpfte Tabellen, deren Spalten dynamisch sind, also vom User bestimmt werden. Es gibt lediglich eine feste Spalte "KEY_ID", welche auf die Spalte "ID" der dazugehörenden Tabelle verweist.
    • Ich würde nun gerne die dynamische Tabelle im gleichen Schritt laden wie die Haupttabelle, so dass kein zusätzlicher SQL-Befehl nötig wird, welcher die Laufzeit verlängert. Die Haupttabelle ist via EF abgebildet, die dynamische Tabelle nicht, weil ich da die Spalten nicht kenne (außer eben "KEY_ID").

    Im Internet finde ich nur Beispiel, wie ich die dynamische Tabelle per SQL lade, aber das ist ja wieder ein einzelnes SQL-Statement.

    Ich hoffe, ich konnte mich verständlich ausdrücken.

    Gruß und danke, Karsten Heimer.

    Donnerstag, 27. Oktober 2022 08:18
  • Hallo Karsten,

    wie ich oben schon geschrieben habe ist EF ein OR Mapper der Daten/Abfragen zwischen SQL und .NET übersetzt. Übersetzen kann EF nur wenn EF die Struktur der Daten kennt.

    Um das Thema besser zu verstehen kannst Du dir den Code von sqlite-net anschauen. Das ist auch ein OR Mapper.

    Man kann auch SQL Abfragen mit EF gegen eine Tabelle senden aber das Problem bleibt das die Daten gemappt werden müssen und so wie ich Dich verstehe kann KEY_ID auf eine Tabelle mit vielen Spalten führen. Die Eigenschaft der Haupttabelle im DB-Context ist aber wahrscheinlich wiederum ein int.

    Wie kann also die Klasse im DB-Context "Haupttabelle" die Daten der dynamischen Tabellen abbilden? Oder anders gefragt welches .NET Objekt kann die Struktur der dynamischen Tabellen darstellen? Wenn Du das rausgefunden hast, könnten Beziehungen dass sein was Du suchst.


    Gruß Thomas
    Github

    Donnerstag, 27. Oktober 2022 13:23
  • Hallo Thomas,

    ich werde mir die Themen zu Gemüte führen.

    Aber schon plagt mich das nächste Problem:

    • Ich wollte das Performanceproblem durch Einsatz von serverseitigem Paging entschärfen.
    • Du hattest ja erwähnt, dass man die gewünschte Page und Pagesize direkt angeben kann. Das funzt auch wunderbar - nur bei PostGres.
    • Als Ergebnis kommt ein "PageResult" zurück, welches ein "IQueryable"-Objekt der Suchergebnisse enthält. Dieses IQueryable-Objekt enthält Objekte des angefragten Typs. Unter Postgres kann ich die Ergebnisliste wunderbar durchlaufen, bei MSSQL kommt:

    Falsche Syntax in der Nähe von 'OFFSET'.
    Ungültige Verwendung der Option NEXT in der FETCH-Anweisung.
    Falsche Syntax in der Nähe des AS-Schlüsselworts.

    Kann das ein Fehler in der zugrunde liegenden Bibliothek sein? System.Linq.Dynamic.Core?

    Gruß, Karsten.

    Donnerstag, 27. Oktober 2022 14:17
  • Ich arbeite nicht mit IQueryable denn hier wird keine Abfrage durchgeführt sondern nur die Anweisung gespeichert. Erst beim Zugriff auf die Liste wird eine Abfrage durchgeführt z.B. mit einer foreach Schleife

    var data = _db.Roles.Skip(100).Take(100); //Hier wird nur die Anweisung gespeichert aber keine Abfrage durchgeführt.
    var d1 = data.ToArray(); //Erst hier wird die Abfrage gegen die Datenbank ausgeführt

    Ich nutze immer ToArray oder ToList am Ende meiner Abfragen je nach dem was ich brauche. Es ist dennoch wichtig das MS das genauso umgesetzt hat, denn so kann man Abfragen verketten und EF einen optimalen SQL Statement erstellen.

    Vielleicht liegt hier der Fehler?  

     

    Gruß Thomas
    Github

    Donnerstag, 27. Oktober 2022 14:56
  • Hallo Thomas,

    auch das .ToList()-Statement funktioniert nicht. Ich vermute hier einen Fehler in der Bibliothek, denn der gleiche Code funktioniert mit dem PostGre. Ich habe mal ein Issue in deren Github eingestellt, mal sehen, ob eine Antwort kommt.

    Die Begriffe "OFFSET", "NEXT" und "FETCH" lassen mich vermuten, dass hier dem SQL-Server fehlerhafte Kommandos untergejubelt werden. Der Stacktrace kommt auch vom SQL-Server-Client aus dem OnError-Event...

    Gruß, Karsten.

    Donnerstag, 27. Oktober 2022 15:09
  • Hallo Karsten,

    bzgl. der Fehler bei OFFSET, NEXT, ... gäbe es mehrere mögliche Ursachen.

    OFFSET ... FETCH ... ist nur verwendbar, wenn auch ORDER BY angegeben wurde, Du musst also sicherstellen, dass auch eine Sortierung angewendet wird.

    Deine SQL Server Version ist zu alt. OFFSET ... FETCH ... gibt es erst seit SQL Server 2012. Bei früheren Versionen wird ein Fehler ausgegeben.

    ---

    P.S.: Es wäre prima, wenn Du für neue Fragestellungen wie die mit den dynamischen Spalten oder die aktuelle bzgl. des Paging neue Threads erstellen könntest. Das würde die Übersicht immens erhöhen und einzelne Threads, die abgeschlossen sind, schaut man oft nicht mehr an.


    Gruß, Stefan
    Microsoft MVP - Visual Developer ASP/ASP.NET (2001-2018)
    https://www.asp-solutions.de/ - IT Beratung, Softwareentwicklung, Remotesupport


    • Bearbeitet Stefan Falz Freitag, 28. Oktober 2022 07:45
    Freitag, 28. Oktober 2022 07:43
  • Hallo Stefan,

    die Serverversion könnte das Problem sein... das ist hier in der Tat 10.50.1600.1, also ein SQL-Server 2008, gut zu wissen!

    Das Thema habe ich mittlerweile leicht entschärft, denn ich verwende einfach statt "PageResult" aus dem Core-Paket die Standardmethoden "Skip" und "Take". Die haben zwar den Nachteil, dass erstmal alle Daten geladen werden, aber das ist hier nicht weiter schlimm, denn der Load aus der Datenbank ist noch sehr schnell. Die Probleme fangen weiter hinten an, beim Aufbereiten der Daten. Damit kann ich jetzt mal für den Moment leben.

    Hinweis wegen neuer Threads angenommen, ist wirklich besser so.

    Danke und Gruß, Karsten.

    Freitag, 28. Oktober 2022 08:51