Feinheiten des abfragbaren Anbieters
Der abfragbare Anbieter kann dies nicht verarbeiten:
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ToList();
Es wird keinen Ausdruck verarbeiten, der die interpolierte Zeichenfolge verwendet, aber es wird dies ohne Schwierigkeiten analysieren:
var result = _context.Humans .Select(x => "Name " + x.Name + " Age " + x.Age) .Where(x => x != "") .ToList();
Es ist besonders schmerzhaft, Fehler nach dem Aktivieren von ClientEvaluation zu korrigieren (eine Ausnahme bei der Berechnung auf dem Client). Alle Profile des Auto-Mappers sollten einer strengen Analyse unterzogen werden, um diese Interpolation zu finden. Lassen Sie uns herausfinden, worum es geht, und unsere Lösung für das Problem anbieten.
Wir korrigieren
Die Interpolation im Ausdrucksbaum wird folgendermaßen übersetzt (dies ist das Ergebnis der ExpressionStringBuilder.ExpressionToString-Methode, bei der einige Knoten weggelassen wurden, für uns jedoch
nicht tödlich):
Zumindest wenn es mehr als 3 Argumente gibt
Format("Name:{0} Age:{1}", new [] {x.Name, Convert(x.Age, Object)))
Wir können daraus schließen, dass der Anbieter einfach nicht gelernt hat, mit solchen Fällen umzugehen, aber er könnte ihm beibringen, diese Fälle auf das gute alte ToString () zu reduzieren, das wie folgt sortiert ist:
((("Name: " + x.Name) + " Age: ") + Convert(x.Age, Object)))
Ich möchte einen Besucher schreiben, der den Ausdrucksbaum durchläuft, nämlich die Knoten der MethodCallExpression, und die Format-Methode durch Verkettung ersetzen. Wenn Sie mit Expression Trees vertraut sind, wissen Sie, dass C # uns seinen Besucher zum Durchlaufen des Baums anbietet - ExpressionVisitor, für diejenigen, die nicht vertraut sind, wird es interessant sein .
Es reicht aus, nur die VisitMethodCall-Methode zu überschreiben und ihren Rückgabewert geringfügig zu ändern. Der Methodenparameter ist vom Typ MethodCallExpression und enthält Informationen zur Methode selbst und zu den Argumenten, die an sie übergeben werden.
Teilen wir die Aufgabe in mehrere Teile auf:
- Stellen Sie fest, dass es sich um die Format-Methode handelt, die zu VisitMethodCall kam
- Ersetzen Sie diese Methode durch Zeichenfolgenverkettung
- Verarbeiten Sie alle Überladungen der Format-Methode, die empfangen werden können
- Schreiben Sie eine Erweiterungsmethode, mit der unser Besucher anruft
Der erste Teil ist recht einfach: Bei der Format 4-Methode müssen Überladungen erstellt werden
im Ausdrucksbaum
public static string Format(string format, object arg0) public static string Format(string format, object arg0,object arg1) public static string Format(string format, object arg0,object arg1,object arg2) public static string Format(string format, params object[] args)
Wir verwenden die Reflexion ihrer MethodInfo
private IEnumerable<MethodInfo> FormatMethods => typeof(string).GetMethods().Where(x => x.Name.Contains("Format"))
Klasse, jetzt können wir feststellen, dass die Format-Methode zu MethodCallExpression "gekommen" ist.
Beim Durchlaufen eines Baums kann VisitMethodCall "kommen":
- Formatmethode mit Objektargumenten
- Formatmethode mit dem Argument object []
- Überhaupt nicht die Format-Methode
Ein bisschen benutzerdefiniertes Pattern Maching
Bisher können nur drei Bedingungen mit Hilfe von if gelöst werden. Unter der Annahme, dass wir diese Methode in Zukunft erweitern müssen, werden wir jedoch alle Fälle in eine solche Datenstruktur einfügen:
public class PatternMachingStructure { public Func<MethodInfo, bool> FilterPredicate { get; set; } public Func<MethodCallExpression, IEnumerable<Expression>> SelectorArgumentsFunc { get; set; } public Func<MethodCallExpression, IEnumerable<Expression>, Expression> ReturnFunc { get; set; } } var patternMatchingList = new List<PatternMachingStructure>()
Mit FilterPredicate bestimmen wir, um welchen der drei Fälle es sich handelt. Mit SelectorArgumentFunc werden die Argumente der Format-Methode in eine einheitliche Form gebracht, die ReturnFunc-Methode, die den neuen Ausdruck an uns zurückgibt.
Versuchen wir nun, die Interpolationsdarstellung durch Verkettung zu ersetzen. Dazu verwenden wir die folgende Methode:
private Expression InterpolationToStringConcat(MethodCallExpression node, IEnumerable<Expression> formatArguments) {
InterpolationToStringConcat wird von Visitor aufgerufen und ist hinter ReturnFunc versteckt
(wenn node.Method == string.Format)
protected override Expression VisitMethodCall(MethodCallExpression node) { var pattern = patternMatchingList.First(x => x.FilterPredicate(node.Method)); var arguments = pattern.SelectorArgumentsFunc(node); var expression = pattern.ReturnFunc(node, arguments); return expression; }
Jetzt müssen wir Logik schreiben, um verschiedene Überladungen der Format-Methode zu behandeln. Sie ist ziemlich trivial und befindet sich in patternMachingList
patternMatchingList = new List<PatternMachingStructure> {
Dementsprechend werden wir in der VisitMethodCall-Methode dieses Blatt bis zum ersten positiven FilterPredicate durchgehen, dann die Argumente (SelectorArgumentFunc) konvertieren und ReturnFunc ausführen.
Schreiben wir Extention und rufen wir auf, dass wir die Interpolation ersetzen können.
Wir können Expression abrufen, an unseren Besucher übergeben und dann die Schnittstellenmethode IQuryableProvider CreateQuery aufrufen, die den ursprünglichen Ausdrucksbaum durch unseren ersetzt:
public static IQueryable<T> ReWrite<T>(this IQueryable<T> qu) { var result = new InterpolationStringReplacer<T>().Visit(qu.Expression); var s = (IQueryable<T>) qu.Provider.CreateQuery(result); return s; }
Achten Sie auf Cast qu.Provider.CreateQuery (Ergebnis) vom Typ IQueryable in IQueryable <T>. Dies ist im Allgemeinen die Standardpraxis für c # (siehe IEnumerable <T>). Sie entstand aufgrund der Notwendigkeit, alle generischen Schnittstellen in einer Klasse zu verarbeiten. Wer möchte IQueryable / IEnumerable akzeptieren und es mit gängigen Schnittstellenmethoden verarbeiten.
Dies hätte vermieden werden können, indem T in die Basisklasse umgewandelt wurde. Dies ist mithilfe der Kovarianz möglich, führt jedoch auch zu Einschränkungen bei den Schnittstellenmethoden (mehr dazu im nächsten Artikel).
Zusammenfassung
Wenden Sie ReWrite auf den Ausdruck am Anfang des Artikels an
var result = _context.Humans .Select(x => $"Name: {x.Name} Age: {x.Age}") .Where(x => x != "") .ReWrite() .ToList();
Github