.NET 7 Preview 7: Mehr Eingriffspunkte in den OR-Mapper, aber unvollständig
Preview 7 bietet Interzeptoren für OR-Mapping in Entity Framework Core und Blazor führt Datenbindungsfunktionen ein. Einige versprochene Funktionen fehlen noch.
- Dr. Holger Schwichtenberg
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.
Unvollständige Verbesserungen bei LINQ
Bei LINQ-Abfragen ist das Einbeziehen verbundener Datensätze nun auch mit Filtern möglich (Filtered Includes), wenn die Navigationseigenschaft privat ist. Laut Blogeintrag sollen die .NET-Methoden String.Join() und String.Concat() in LINQ-Abfragen in SQL übersetzt 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]
Erneut fehlen manche zuvor angepriesene Funktionen
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 sowie die in der Dokumentation erwähnen neuen 128-Bit langen Ganzzahltypen System.Int128 und System.UInt128.
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.
Unix-Dateirechte-APIs angekündigt, aber noch nicht vorhanden
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, 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 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 hat Microsoft bisher noch nicht reagiert.
Fehlerhaftes Codebeispiel im Blogeintrag von Microsoft
Auch beim nächsten Feature im Blogeintrag gibt es einen Fehler: 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.
Am 22. November 2022 richten heise Developer und dpunkt.verlag in Kooperation mit www.IT-Visions.de mit der betterCode() .NET 7 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.
Ladeanimation für Blazor WebAssembly
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.