Entity Framework 2.1 Core Play Ground

Dotnet Core wird als Nachfolger des .NET Framework gehandelt. Dabei ist es keine entweder oder Entscheidung. Am Beispiel von DOTNET Core Entity Framework 2.1 und ASP.NET Webforms werden die Möglichkeiten erforscht. Achja und das ganze auch noch mit VB.NET. Einfach weil es geht oder auch nicht.

Ich verwende gerne Entity Framework, stoße aber auch häufig an Grenzen. Als Beispiel dient eine komplexere View auf die Northwind Datenbank. Die Rückgabe soll als generische Liste verarbeitet werden um z.B. eine Listview per Modelbinding zu verbinden.

Auf dem Weg dorthin nutze ich gern das SQL Management Studio und klicke mir die Sicht einfach zusammen. Ich kann zwar ganz gut SQL, aber bei Joins aber zig Tabellen mit Subquerys und so …

image

Dazu braucht man eine Model Klasse, die man leider per Hand codieren muss.

   1:  Public Class MeineSicht
   2:      Public Property CompanyName As String
   3:      Public Property OrderDate As Date
   4:      Public Property LastName As String
   5:      Public Property Summe As Decimal
   6:  End Class

Das ist genau einer der stärken von Entity Framework, die Generierung von Klassen aus einem Tabellenschema.

Rein aus Neugier, lassen wir auch noch die Zeiten messen. Folgende SQL Abfrage liefert ca 2000 Datensätze zurück. Die SQL Zeichenketten wird dank des Visual Basic 14 Sprachfeatures Multiline String Literals einfach per copy paste eingefügt.

   1:  Dim sql = "SELECT        dbo.Customers.CompanyName, dbo.Orders.OrderDate, 
dbo.Employees.LastName, dbo.[Order Details].Quantity * dbo.Products.UnitPrice As summe
   2:  From dbo.Customers INNER Join
   3:                dbo.Orders ON dbo.Customers.CustomerID = dbo.Orders.CustomerID INNER Join
   4:                dbo.Employees ON dbo.Orders.EmployeeID = dbo.Employees.EmployeeID INNER Join
   5:                dbo.[Order Details] ON dbo.Orders.OrderID = dbo.[Order Details].OrderID
INNER Join
   6:               dbo.Products ON dbo.[Order Details].ProductID = dbo.Products.ProductID"
   7:   
   8:  Dim ConnString As String = ConfigurationManager.ConnectionStrings("nwModel").ConnectionString
   9:  Dim sw = New Stopwatch()
  10:          sw.Start()

Als nächstes wird zum DataReader aus ADO.NET gegriffen. Jeder Datensatz wird als neues Objekt per Hand angelegt und einer generischen Liste hinzugefügt. Das ist reichlich Tipparbeit im Source Code. Problematisch sind die Feldbezeichner als String, wir “CompanyName”.

   1:  Dim liste As New List(Of MeineSicht)
   2:  Using Conn As New SqlConnection(ConnString)
   3:      Dim Cmd As New SqlCommand(sql, Conn)
   4:      Conn.Open()
   5:      Dim dtr As SqlDataReader = Cmd.ExecuteReader()
   6:      While dtr.Read
   7:          liste.Add(New MeineSicht With {
   8:          .CompanyName = dtr("CompanyName"),
   9:          .LastName = dtr("LastName"),
  10:          .OrderDate = dtr("OrderDate"),
  11:          .Summe = dtr("Summe")
  12:           })
  13:       End While
  14:  End Using

Allerdings ist das schnell. Meine Stopwatch zeigt Zeiten von 14ms, Die Zeiten variieren, liegen aber fast immer unter 20ms. Es soll nicht unerwähnt bleiben, das das Testszenarion, nur eine Tendenz zeigt und keine absolut Aussagen. Es sind einfach zu viele Störfaktoren, wenn Datenbank Server, Webserver, Debugger und mehr auf einer Maschine betrieben wird.

Um Entity Framework 6 zu nutzen, wird das Nuget Paket installiert. Dann wird aus der Datenbank (Code First) das Database Model generiert. Also die DBContext Klasse  und die Modelklassen zu den Tabellen.

Ein Blick in die von DBContext erzeugte Klasse. Man erkennt im Konstruktor den Namen des Connection Strings aus der web.config. Pro Tabelle wird ein Property DbSet angelegt. Im ModelCreating Event ist auscodiert wie die Tabellen angelegt werden sollten, wenn dies per Code geschieht.

   1:  Public Sub New()
   2:     MyBase.New("name=nwModel")
   3:  End Sub
   4:  ....
   5:  Public Overridable Property Customers As DbSet(Of Customers)
   6:  ....
   7:  Protected Overrides Sub OnModelCreating(
ByVal modelBuilder As DbModelBuilder)
   8:          modelBuilder.Entity(Of CustomerDemographics)() _
   9:              .Property(Function(e) e.CustomerTypeID) _
  10:              .IsFixedLength()

Wir wollen ja spielen und so sind die Unterschiede von Entity Framework 6 und Entity Framework core 2.1 eben auch noch im Fokus. Aber eben auch wie man nun an die Daten kommt. Obwohl es sich um einen OR Mapper handelt wird eben nicht 1:1 gemappt. Die SQL Abfrage ist einfach zu komplex, als das man mit LINQ sinnvoll arbeiten könnte. Also einfach weiter verwenden.

   1:  Dim ef = New nwModel
   2:  Dim liste = ef.Database.SqlQuery(Of MeineSicht)(sql).ToList

Einfacher geht eigentlich nicht. Das ist der coole Part von Entity Framework. Aber die Performance leidet. In diesem Fall vernachlässigbar, weil die Kontrolle über das erzeugte SQL nicht LINQ in die Hände gegeben wurde. Die schnellste Messung war sogar 15ms. Der Wertebereich war allerdings bis 50ms. Datareader scheint also schneller zu sein.

Nun nehmen wir uns das neueste Kind der Redmonder zur Brust, EF Core 2.1. Installiert wird das Nuget Paket

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.1.4

Leider fehlt ein visueller Designer so braucht man für Scaffolding

Install-Package Microsoft.EntityFrameworkCore.Tools -Version 2.1.4

In der Nuget Pagage Manager Konsole von Visual Studio wird dann das generieren des Models angestossen:

Scaffold-DbContext "Server=localhost;Database=northwnd;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models2

Geht nicht, Microsoft hat wohl den Code Generator für VB nicht implementiert.

.image

Ein Lösungsansatz lautet: eine C# Klassenbibliothek in die Solution einfügen. Typ .NET Standard 2. Diese kann dann als Verweis in das ASP.NET Webprojekt referenziert werden. Aber bevor es soweit ist, müssen auch in die Library die dotnet Framework Core per Nuget hinzugefügt werden  (siehe vorher) und das Scaffolding angeworfen werden.

Der erzeugte C# Code, also das Entity Model core, sieht doch ein wenig anders aus. Connection String im Code und anderer Model Creator.

   1:  public northwndContext()
   2:          {
   3:          }
   4:   
   5:  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
   6:          {
   7:              if (!optionsBuilder.IsConfigured)
   8:              {
   9:                  optionsBuilder.UseSqlServer(
"Server=localhost;Database=northwnd;Trusted_Connection=True;");
  10:              }
  11:          }
  12:   
  13:   protected override void OnModelCreating(ModelBuilder modelBuilder)
  14:          {
  15:              modelBuilder.Entity<Categories>(entity =>
  16:              {
  17:                  entity.HasKey(e => e.CategoryId);
  18:   

Die referenzierten Objekte werden nach wie vor per DbSet eingebettet. Dabei sind allerdings die Modelklassen durchaus unterschiedlich generiert worden. Das sprengt aber den Rahmen des Blog Artikels. Für die SQL Abfrage muss aber nun eine DbQuery eingebunden werden. Das ist neu in EF Core.

   1:   public virtual DbQuery<MeineSicht> MeineSicht { get; set; }
   2:  ....
   3:   
   4:   public virtual DbSet<Categories> Categories { get; set; }

Nun erst lässt sich EF Core Code, im Kontext von .NET 4.7 ausführen. Wieder reichen 2 Zeilen, Visual Basic Code.  Nun wird das SQL Kommando auf dem Zieltyp, also Meinsicht ausgeführt.

   1:  Dim ef = New northwndContext
   2:  Dim list = ef.MeineSicht.FromSql(sql).ToList

Die Stopuhr zeigt als schnellste Zeit 19 ms und als maximal 34ms. Es scheint also tatsächlich etwas schneller zu gehen. Wobei ganz wichtig, vermutlich es noch schneller wird, wenn man das ganze auch im Kontext von dotnet core ausführt. Verschiedene Artikel im Web meinen dazu, das es dann ident schnell ausgeführt wird zum Datareader.

Kommentare sind geschlossen