zurück zum Artikel

.NET 7 Preview 7: Mehr Eingriffspunkte in den OR-Mapper, aber unvollständig

Dr. Holger Schwichtenberg

(Bild: Ken Wolter/Shutterstock.com)

Preview 7 bietet Interzeptoren für OR-Mapping in Entity Framework Core und Blazor führt Datenbindungsfunktionen ein. Einige versprochene Funktionen fehlen noch.

In der siebten Preview zu .NET 7 bietet Entity Framework Core neue und verbesserte Interzeptoren für zahlreiche Abläufe beim OR-Mapping. Dazu gehören die Modifikation von LINQ vor der Umwandlung nach SQL (IQueryExpressionInterceptor), der Verbindungsaufbau zum Datenbankmanagementsystem (ConnectionCreating/ConnectionCreated in DbConnectionInterceptor), die Materialisierung von Objekten nach Eingang der Resultatsets (IMaterializationInterceptor) und die Behandlung von Änderungskonflikten (ISaveChangesInterceptor). Folgendes Listing zeigt ein aussagekräftiges Beispiel:

using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using AutoBogus;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
 
namespace EFC_Console.EFCore7News;
 
/// <summary>
/// Eigene Schnittstelle mit einem DateTime-Property
/// </summary>
public interface ILoadTime
{
 DateTime LoadTime { get; set; }
}
 
/// <summary>
/// Entitaetsklassen, die ILoadTime realisiert
/// </summary>
public class Person : ILoadTime
{
 public int ID { get; set; }
 public string Name { get; set; }
 public string Country { get; set; }
 
 [NotMapped]
 public DateTime LoadTime { get; set; }
}
 
/// <summary>
/// Eigener Interceptor für die Materialisierung
/// </summary>
public class LoadedTimeInterceptor : IMaterializationInterceptor
{
 public object InitializedInstance(
   MaterializationInterceptionData materializationData, 
   object instance)
 {
  if (instance is ILoadTime obj)
  {
   obj.LoadTime = DateTime.UtcNow;
  }
  return instance;
 }
}
 
/// <summary>
/// Kontextklasse: SQLite-DB mit einem Interceptor
/// </summary>
public class DemoContext : DbContext
{
 private static readonly 
   LoadedTimeInterceptor _setRetrievedInterceptor = new();
 
 public DbSet<Person> PersonSet => Set<Person>();
 
 protected override void 
   OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     => optionsBuilder
         .AddInterceptors(_setRetrievedInterceptor)
         .UseSqlite("Data Source = personset.db");
}
 
 
/// <summary>
/// Beispiel-Client, der Datensaetze anlegt und abfragt
/// </summary>
public class InterceptorDemo
{
 public static void Run()
 {
  using (var context = new PersonContext())
  {
   context.Database.EnsureCreated();
   if (context.PersonSet.Count() == 0)
   {
    for (int i = 0; i < 100; i++)
    {
     context.PersonSet.Add(new AutoFaker<Person>().Generate());
    }
   }
   context.SaveChanges();
 
   var p = 
     context.PersonSet.FirstOrDefault(e => 
                                      e.Name.StartsWith("A"));
   Console.WriteLine(
  $"Person '{p.Name}' was loaded at '{p.LoadTime.ToLocalTime()}'");
  }
 }
}

Der Interzeptor für die Materialisierung merkt sich im RAM, wann ein Objekt aus der Datenbank geladen wurde.

Weitere in Preview 7 implementierte Verbesserungen gibt es beim Mapping selbst. So sind n:m-Beziehungen jetzt auch unidirektional möglich, das heißt, zwischen zwei Entitäten mit n:m-Kardinalität muss es keine wechselseitigen Navigationsbeziehungen mehr geben. Beim Table-per-Concrete-Type-Mapping (TPC) können die einzelnen beteiligten Tabellen nun verschiedene Schrittweiten bei den ID-Spalten verwenden [1].

Bei LINQ-Abfragen ist das Einbeziehen verbundener Datensätze nun auch mit Filtern möglich (Filtered Includes) [2], wenn die Navigationseigenschaft privat ist. Laut Blogeintrag sollen die .NET-Methoden String.Join() und String.Concat() in LINQ-Abfragen in SQL übersetzt [3] werden. Ein Schnelltest des Verfassers mit Entity Framework Core 7.0 Preview 7 ergab allerdings, dass bei SQL Server aus folgender LINQ-Abfrage

var q = from f in ctx.FlightSet
        select string.Join("->", f.Departure, f.Destination);
  foreach (var route in q.Take(10).ToList())
  {
   Console.WriteLine(route);
  }

weiterhin das gleiche SQL entsteht wie bei Entity Framework Core 6.0:

SELECT TOP(@__p_0) [f].[Departure], [f].[Destination]
FROM [WWWings].[Flight] AS [f]

Es ist schon häufiger vorgekommen, dass im Blogeintrag zu neuen .NET-Versionen Funktionen angepriesen wurden, die noch gar nicht enthalten sind. Auch in Preview 7 fehlen noch die im Blogeintrag zu Preview 4 in Aussicht gestellten Eigenschaften Microseconds und Nanoseconds [4] sowie die in der Dokumentation erwähnen neuen 128-Bit langen Ganzzahltypen System.Int128 und System.UInt128 [5].

Eine weitere Schwäche findet man bei den neu eingeführten LINQ-Operatoren Order() und OrderDescending(), die es seit Preview 7 als Ergänzung zu OrderBy() und OrderByDescending() gibt. Sie erlauben eine verkürzte Syntax zum Sortieren von Listen mit elementaren Datentypen. So kann der Entwickler nun

var sortedDesc = data.OrderDescending();

statt

var sortedDesc = data.OrderByDescending(e => e);

schreiben. Das klappt mit LINQ-to-Objects, allerdings versagt Entity Framework Core beim Versuch, dies auf eine Datenbank anzuwenden. So führt

var q1 = (from f in ctx.FlightSet
          select f.Departure).OrderDescending().ToList();

zum Laufzeitfehler "The LINQ expression DbSet<Flight>() .Select(f => f.Departure).OrderDescending() could not be translated". Dieser Effekt ist schon seit .NET 6.0 bekannt: Auch dort hatte Microsoft LINQ-Operatoren ergänzt (unter anderem MinBy(, MaxBy(), DistinctBy() und Chunk()), die Entity Framework Core bis heute nicht unterstützt.

Auch wenn .NET seit .NET Core 1.0 plattformneutral sein will, gab es für Dateisystemrechte bisher nur ein API für das Windows-Betriebssystem. In einem weiteren Blogeintrag zeigt Microsoft ein Dateisystemrechte-APIs für Unix [6], beispielsweise

// Create a new directory with specific permissions
Directory.CreateDirectory("myDirectory", 
                          UnixFileMode.UserRead | 
                          UnixFileMode.UserWrite | 
                          UnixFileMode.UserExecute);

und

// Get the mode of an existing file
UnixFileMode mode = File.GetUnixFileMode("myFile")

Der Schnelltest dieser Meldung ergab allerdings, dass weder die Enumeration UnixFileMode noch die neuen Methoden GetUnixFileMode() und SetUnixFileMode() vom Compiler im Namensraum System.IO gefunden werden konnten, obwohl laut GitHub-Check-In [7] diese Erweiterungen in der Assembly System.Runtime.dll liegen, also im .NET SDK ohne weitere NuGet-Installation enthalten sein sollten. Auf eine entsprechende Nachfrage des Verfassers [8] hat Microsoft bisher noch nicht reagiert.

Auch beim nächsten Feature im Blogeintrag gibt es einen Fehler [9]: Wenn man das in der Ankündigung zu Preview 7 im Abschnitt "LibraryImport P/Invoke source generator" dargestellte Beispiel

public static class Native
{
    [LibraryImport(nameof(Native), 
     StringMarshalling = StringMarshalling.Utf16)]
    public static partial string ToLower(string str);
}

in Visual Studio kopiert, meckert der Compiler: "Error SYSLIB1050 Method ToLower is contained in a type NativeNew that is not marked partial. P/Invoke source generation will ignore method ToLower." Auf einen Blog-Kommentar des Autors dieser Nachricht hat Microsoft bisher nicht reagiert [10].

Heise-Konferenz zu .NET 7

Am 22. November 2022 richten heise Developer und dpunkt.verlag in Kooperation mit www.IT-Visions.de mit der betterCode() .NET 7 [11] eine Online-Konferenz zum kommenden Release von Microsofts Entwicklungsplattform .NET aus. Diese soll im November erscheinen, Sie ist zwar kein Long-Term Release wie das letztes Jahr erschienene und gerade aktuelle.NET 6. In das neue Release fließen aber viele Neuerungen und Arbeiten zur Verbesserung der Codequalität, die dann die Migration auf.NET 7 rechtfertigen können.

LibraryImport basiert im Gegensatz zu dem schon seit .NET Framework 1.0 vorhandenen DllImport nicht auf Laufzeitcodegenerierung, sondern auf einem Source Code Generator. Der zur Entwicklungszeit generierte Code ist sichtbar unter /Dependencies/Analyzers/ Microsoft.Interop.LibraryImportGenerator.

In ASP.NET Core 7.0 Preview 7 gibt es vor allem Neuerungen für Blazor. Die Projektvorlage für Blazor WebAssembly enthält nun eine prozentuale Ladeanimation. Die SVG-Animation ist über CSS-Klassen, die in der Style-Sheet-Datei /wwwroot/css/app.css liegen, anpassbar. Abbildung 1 zeigt aber, dass die Prozentanzeige nicht dem Kreisanteil entspricht.

Ladeanimation für eine Blazor WebAssembly-Anwendung in Version 7.0 Preview 7 (Abb. 1)

Ladeanimation für eine Blazor WebAssembly-Anwendung in Version 7.0 Preview 7 (Abb. 1)

Bei der Datenbindung in allen Blazor-Varianten (also Blazor WebAssembly, Blazor Server, Blazor Desktop und Blazor MAUI) gibt es nun die neuen Zusätze :get und :set, um das Auslesen von Werten und das Schreiben neuer Werte auf einfache Weise zu trennen:

<input @bind:get="currentCount" @bind:set="SetValue" />
 
@code {
 private int currentCount = 0;
 public void SetValue(int v)
 {
  if (v > 0) currentCount = v;
  else currentCount = 0;
 }
}

Zudem kann man mit :after eine Methode angeben, die automatisch nach Aktualisierung der Werte ausgeführt werden soll:

<input @bind="currentCount" @bind:after="Log" />

Die Mischung beider Erweiterungen ist aber nicht erlaubt. Folgende Zeile liefert den Laufzeitfehler "Error RZ10019 Attribute bind:after can not be used with bind:set. Invoke the code in bind:after inside bind:set instead":

<input @bind:get="currentCount" @bind:set="ValueChanged"
 @bind:after="Log" />

Bei der eingebauten Blazor-Komponente <Virtualize>, die .NET 5.0 einführte, ist es möglich, mit der neuen Eigenschaft SpacerElement das HTML-Tag, das die Komponente vor dem ersten und nach dem letzten Datensatz einfügt, selbst zu bestimmen:

<table>
<tbody>
  <Virtualize Items="@data" Context="d" SpacerElement="tr">
   <tr><td>@d</td></tr>
  </Virtualize>
</tbody>
</table>

Das Abstandselement war zuvor immer ein <div>-Tag, was aber in Verbindung mit einigen Tags wie <table> kein gültiges HTML ergab.

Der in Blazor seit der ersten Version enthaltene NavigationManager kann nun der Folgeseite eine Zeichenkette über den Browser History Stack übergeben:

navigationManager.NavigateTo("/Counter", 
                             new NavigationOptions {
                               HistoryEntryState = 
                                 "Startwert:10;Increment:2"});

Die Folgeseite kann diese Zeichenkette auswerten:

protected override void OnInitialized()
 {
  if (navigationManager.HistoryEntryState != null && 
      navigationManager.HistoryEntryState.Length > 0)
  {
   string data = navigationManager.HistoryEntryState;
   message = "Uebergebene Daten: " + 
     navigationManager.HistoryEntryState;
   currentCount = 
     Convert.ToInt32(data.Split(";")[0].Replace("Startwert:", ""));
   Increment = 
     Convert.ToInt32(data.Split(";")[1].Replace("Increment:", ""));
  }
  else
  {
   message = "Uebergebene Daten: KEINE";
  }
 }

Preview 7 erweitert die in Blazor WebAssembly 7.0 Preview 6 eingeführte Unterstützung der Hashing-Algorithmen (SHA1, SHA256, SHA364, SHA512 – jeweils inklusive HMAC-Variante) direkt über die Schnittstelle SubtleCrypto des Web Crypto API in modernen Browsern. Nun sind ohne Rückgriff auf .NET-APIs auch AES-CBC, Rfc2898DeriveBytes (PBKDF2) und HKDF möglich.

Das in früheren Preview-Version von .NET 7.0 eingeführte Transcoding zwischen gRPC und JSON hat Microsoft beschleunigt. Die in .NET 7.0 Preview 1 eingeführten Schnittstellen IFormFile und IFormFileCollection zum Dateiupload unterstützen nun auch eine Authentifizierung. Mit der Schnittstelle IProblemDetailsService lassen sich Problemdetails gemäß RFC 7807 in HTTP-Antworten zurückmelden [12]. Die in Preview 7 eingeführten HttpResults-Schnittstellen haben wie folgt Zuwachs erhalten:

Microsoft hat im Blogeintrag zur Preview angekündigt, dass Preview 7 die letzte als "Preview" bezeichnete Vorabversion war [13]. Falls der Hersteller dem Entwicklungsschema von .NET 6.0 folgt, sollten noch zwei Versionen im Status Release Candidate (RC) im September und Oktober bis zum geplanten Erscheinungstermin am 8. November 2022 folgen.

Auch in den RC-Versionen sind noch zahlreiche neue Features zu erwarten, denn insbesondere die Entwicklungsteams von ASP.NET Core [14] und das Team hinter Entity Framework Core haben noch zahlreiche geplante Neuerungen im Backlog [15].

Einige zu Beginn des Jahres angekündigte Funktionen wie das Hosting mehrerer Blazor-Anwendungen in einem Dokument [16], eine bessere Kontrolle über die Blazor Server-Circuits [17] und echtes Multi-Threading für Blazor WebAssembly wurden aber inzwischen vertagt. Das Multi-Threading soll aber immerhin als unfertige Vorab-Implementierung [18] in der einsatzreifen Fassung .NET 7.0 enthalten sein. Microsoft hatte schon in der Vergangenheit solche Previews mitunter als Teil von Releaseversionen ins Rennen geschickt.

Die fertige Version von .NET 7.0 ist für den 8. November 2022 angekündigt. Der Support für diese Version soll bis zum Mai 2024 laufen.

(sih [19])


URL dieses Artikels:
https://www.heise.de/-7216017

Links in diesem Artikel:
[1] https://github.com/dotnet/efcore/issues/28195
[2] https://github.com/dotnet/efcore/issues/27493
[3] https://devblogs.microsoft.com/dotnet/announcing-ef7-preview7/
[4] https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-4/#contributor-spotlight-maksym-koshovyi
[5] https://docs.microsoft.com/de-de/dotnet/api/system.int128?view=net-7.0
[6] https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-7/
[7] https://github.com/dotnet/runtime/pull/69980/files#diff-cec8e6f471b4193246bdc0107b0dd7cbe131fb7fd189b288b37269c333d1171d
[8] https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-7/#comment-16202
[9] https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-7/
[10] https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-7/#comment-16203
[11] https://net.bettercode.eu/
[12] https://www.rfc-editor.org/rfc/rfc7807
[13] https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-7/
[14] https://github.com/dotnet/aspnetcore/issues/39504
[15] https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/plan#raw-sql-queries-for-unmapped-types
[16] https://github.com/dotnet/aspnetcore/issues/38128
[17] https://github.com/dotnet/aspnetcore/issues/30344
[18] https://github.com/dotnet/aspnetcore/issues/17730#issuecomment-1183400232
[19] mailto:sih@ix.de