Für die meisten Entwickler ist die Verwendung des Ausdrucksbaums auf Lambda-Ausdrücke in LINQ beschränkt. Oft legen wir keinen Wert darauf, wie die Technologie „unter der Haube“ funktioniert.
In diesem Artikel zeige ich Ihnen fortgeschrittene Techniken für die Arbeit mit Ausdrucksbäumen: Eliminieren von Codeduplizierungen in LINQ, Codegenerierung, Metaprogrammierung, Transpilation und Testautomatisierung.
Sie lernen, wie Sie den Ausdrucksbaum direkt verwenden, welche Fallstricke die Technologie vorbereitet hat und wie Sie sie umgehen können.

Unter dem Schnitt - Video- und Textprotokoll
meines Berichts mit DotNext 2018 Piter.
Mein Name ist Maxim Arshinov, ich bin Mitbegründer des Outsourcing-Unternehmens der Hi-Tech Group. Wir entwickeln Software für Unternehmen und heute werde ich darüber sprechen, wie die Ausdrucksbaumtechnologie in der täglichen Arbeit verwendet wurde und wie sie uns zu helfen begann.
Ich wollte nie speziell die interne Struktur von Ausdrucksbäumen untersuchen. Es schien, dass dies eine Art interne Technologie für das .NET-Team war, damit LINQ funktioniert, und Anwendungsprogrammierer mussten die API nicht kennen. Es stellte sich heraus, dass einige angewandte Probleme gelöst werden mussten. Damit mir die Lösung gefiel, musste ich in den Darm klettern.
Diese ganze Geschichte ist zeitlich gespannt, es gab verschiedene Projekte, verschiedene Fälle. Es kam etwas heraus, und ich beendete es, aber ich werde mir erlauben, die historische Wahrhaftigkeit zugunsten einer künstlerischeren Präsentation zu opfern, sodass alle Beispiele auf demselben Themenmodell basieren - einem Online-Shop.

Stellen Sie sich vor, wir schreiben alle einen Online-Shop. Es hat Produkte und ein Häkchen "Zum Verkauf" im Admin-Bereich. Wir zeigen nur Produkte an, bei denen dieses Häkchen im öffentlichen Teil markiert ist.

Wir nehmen etwas DbContext oder NHibernate, wir schreiben Where (), wir geben IsForSale aus.
Alles ist in Ordnung, aber die Geschäftsregeln sind nicht die gleichen, so dass wir sie ein für alle Mal schreiben. Sie entwickeln sich im Laufe der Zeit. Ein Manager kommt und sagt, dass wir immer noch den Kontostand überwachen und nur Waren anzeigen müssen, die einen Kontostand im öffentlichen Teil haben, ohne das Häkchen zu vergessen.

Wir fügen leicht eine solche Eigenschaft hinzu. Jetzt sind unsere Geschäftsregeln gekapselt und können wiederverwendet werden.

Versuchen wir, LINQ zu bearbeiten. Ist hier alles in Ordnung?
Nein, dies funktioniert nicht, da IsAvailable nicht der Datenbank zugeordnet ist. Dies ist unser Code, und der Abfrageanbieter weiß nicht, wie er analysiert werden soll.

Wir können ihm sagen, dass unser Eigentum eine solche Geschichte hat. Aber jetzt wird dieses Lambda sowohl im linq-Ausdruck als auch in der Eigenschaft dupliziert.
Where(x => x.IsForSale && x.InStock > 0) IsAvailable => IsForSale && InStock > 0;
Wenn sich dieses Lambda das nächste Mal ändert, müssen wir für das Projekt Strg + Umschalt + F drücken. Natürlich werden wir alle nicht finden - Fehler und Zeit. Ich möchte das vermeiden.

Wir können von dieser Seite gehen und eine weitere ToList () vor Where () setzen. Dies ist eine schlechte Entscheidung, denn wenn sich eine Million Waren in der Datenbank befinden, steigt jeder in den Arbeitsspeicher und filtert dort.

Wenn Sie drei Produkte in einem Geschäft haben, ist die Lösung gut, aber im E-Commerce gibt es normalerweise mehr. Dies funktionierte nur, weil Lambdas trotz der Ähnlichkeit miteinander einen völlig anderen Typ haben. Im ersten Fall ist dies ein Func-Delegat und im zweiten ein Ausdrucksbaum. Es sieht gleich aus, die Typen sind unterschiedlich, der Bytecode ist völlig unterschiedlich.

Um vom Ausdruck zum Delegaten zu wechseln, müssen Sie nur die Compile () -Methode aufrufen. Diese API bietet .NET: Es gibt Ausdruck kompiliert, einen Delegaten erhalten.
Aber wie geht es zurück? Gibt es in .NET etwas, um von einem Delegaten zu Ausdrucksbäumen zu wechseln? Wenn Sie beispielsweise mit LISP vertraut sind, gibt es einen Zitiermechanismus, mit dem der Code als Datenstruktur interpretiert werden kann, in .NET jedoch nicht.
Express oder Delegierte?
Wenn man bedenkt, dass wir zwei Arten von Lambdas haben, können wir philosophieren, was primär ist: Ausdrucksbaum oder Delegierte.
Auf den ersten Blick ist die Antwort offensichtlich: Da es eine wunderbare Compile () -Methode gibt, ist der Ausdrucksbaum primär. Und wir sollten den Delegaten empfangen, indem wir den Ausdruck kompilieren. Das Kompilieren ist jedoch ein langsamer Prozess, und wenn wir dies überall tun, kommt es zu Leistungseinbußen. Darüber hinaus erhalten wir es an zufälligen Stellen, an denen der Ausdruck zu einem Delegaten kompiliert werden musste. Es kommt zu einem Leistungsabfall. Sie können diese Orte finden, sie wirken sich jedoch zufällig auf die Antwortzeit des Servers aus.

Daher müssen sie irgendwie zwischengespeichert werden. Wenn Sie einen Vortrag über gleichzeitige Datenstrukturen gehört haben, kennen Sie ConcurrentDictionary (oder wissen es einfach). Ich werde Details zu Caching-Methoden weglassen (mit Sperren, nicht mit Sperren). Es ist nur so, dass ConcurrentDictionary eine einfache GetOrAdd () -Methode hat. Die einfachste Implementierung besteht darin, sie in ConcurrentDictionary einzufügen und zwischenzuspeichern. Das erste Mal bekommen wir die Kompilierung, aber dann wird alles schnell gehen, da der Delegat bereits kompiliert wurde.

Dann können Sie eine solche Erweiterungsmethode verwenden, unseren Code mit IsAvailable () verwenden und umgestalten, den Ausdruck beschreiben, die Eigenschaften von IsAvailable () kompilieren und ihn relativ zum aktuellen Objekt aufrufen.
Es gibt mindestens zwei Pakete, die dies implementieren:
Microsoft.Linq.Translations und
Signum Framework (Open Source Framework, das von einem kommerziellen Unternehmen geschrieben wurde). Sowohl dort als auch dort gibt es ungefähr die gleiche Geschichte mit der Zusammenstellung von Delegierten. Eine etwas andere API, aber alles, wie ich auf der vorherigen Folie gezeigt habe.
Dies ist jedoch nicht der einzige Ansatz, und Sie können von Delegaten zu Ausdrücken wechseln. Seit langem gibt es
einen Artikel über den Habre über den Delegate Decompiler, in dem der Autor behauptet, dass alle Zusammenstellungen schlecht sind, weil für eine lange Zeit.
Im Allgemeinen standen Delegaten vor Ausdrücken, und Sie können von Delegaten zu ihnen wechseln. Zu diesem Zweck verwendet der Autor die MethodeBody.GetILAsByteArray (). from Reflection, das wirklich den gesamten IL-Code der Methode als Array von Bytes zurückgibt. Wenn Sie es weiter in Reflection einfügen, können Sie eine Objektdarstellung dieses Falls abrufen, mit einer Schleife durchgehen und einen Ausdrucksbaum erstellen. Somit ist auch ein umgekehrter Übergang möglich, der jedoch von Hand erfolgen muss.

Um nicht alle Eigenschaften zu durchlaufen, schlägt der Autor vor, das Attribut "Berechnet" aufzuhängen, um anzuzeigen, dass diese Eigenschaft inline sein muss. Vor der Anforderung klettern wir in IsAvailable (), ziehen den IL-Code heraus, konvertieren ihn in den Ausdrucksbaum und ersetzen den Aufruf von IsAvailable () durch das, was in diesem Getter geschrieben ist. Es stellt sich heraus, dass ein solches manuelles Inlining vorliegt.

Damit dies funktioniert, rufen wir die spezielle Methode Decompile () auf, bevor wir alles an ToList () übergeben. Es bietet einen Dekorator für das Original abfragbar und Inlining. Erst danach übergeben wir alles an den Abfrageanbieter, und bei uns ist alles in Ordnung.

Das einzige Problem bei diesem Ansatz ist, dass Delegate Decompiler 0.23.0 nicht weiterentwickelt wird, es keine Core-Unterstützung gibt und der Autor selbst sagt, dass dies Deep Alpha ist, es gibt viele unvollendete, sodass Sie es nicht in der Produktion verwenden können. Obwohl wir auf dieses Thema zurückkommen werden.
Boolesche Operationen
Es stellt sich heraus, dass wir das Problem der Verdoppelung bestimmter Bedingungen gelöst haben.

Bedingungen müssen jedoch häufig mithilfe der Booleschen Logik kombiniert werden. Wir hatten IsForSale (), InStock ()> 0 und dazwischen die Bedingung "AND". Wenn eine andere Bedingung vorliegt oder eine ODER-Bedingung erforderlich ist.

Im Fall von "Und" können Sie die gesamte Arbeit des Abfrageanbieters betrügen und ausgeben, dh viel Where () hintereinander schreiben, er weiß, wie es geht.

Wenn "ODER" erforderlich ist, funktioniert dies nicht, da WhereOr () nicht in LINQ enthalten ist und der Operator | | nicht mit Ausdrücken überladen ist.
Technische Daten
Wenn Sie mit Evans 'DDD-Buch vertraut sind oder nur etwas über das Spezifikationsmuster wissen, dh ein speziell dafür entwickeltes Entwurfsmuster. Es gibt mehrere Geschäftsregeln, und Sie möchten Operationen in der Booleschen Logik kombinieren - implementieren Sie die Spezifikation.

Eine Spezifikation ist ein solcher Begriff, ein altes Muster aus Java. Und in Java, insbesondere in der alten, gab es kein LINQ, so dass es dort nur in Form der isSatisfiedBy () -Methode implementiert wurde, dh nur in Form von Delegaten, aber es wurde nicht über Ausdrücke gesprochen. Im Internet gibt es eine Implementierung namens
LinqSpecs . Auf der Folie sehen Sie sie. Ich habe es ein bisschen für mich selbst abgelegt, aber die Idee gehört zur Bibliothek.
Hier sind alle Booleschen Operatoren überladen, die wahren und falschen Operatoren sind überladen, so dass die beiden Operatoren "&&" und "||" funktionieren, ohne die nur ein einziges kaufmännisches Und funktioniert.

Als Nächstes fügen wir implizite Anweisungen hinzu, die den Compiler davon ausgehen lassen, dass die Spezifikation sowohl Ausdrücke als auch Delegaten ist. An jeder Stelle, an der Expression <> oder Func <> in die Funktion aufgenommen werden soll, können Sie die Spezifikation übergeben. Da der implizite Operator überladen ist, analysiert und ersetzt der Compiler entweder die Expression- oder die IsSatisfiedBy-Eigenschaften.

IsSatisfiedBy () kann implementiert werden, indem der kommende Ausdruck zwischengespeichert wird. In jedem Fall stellt sich heraus, dass wir von Expression kommen, der Delegat entspricht ihm, wir haben Unterstützung für Boolesche Operatoren hinzugefügt. Jetzt kann das alles arrangiert werden. Geschäftsregeln können in statische Spezifikationen aufgenommen, deklariert und kombiniert werden.
public static readonly Spec<Product> IsForSaleSpec = new Spec<Product>(x => x.IsForSale); public static readonly Spec<Product> IsInStockSpec = new Spec<Product>(x => x.InStock > 0);

Jede Geschäftsregel wird nur einmal geschrieben, sie geht nirgendwo verloren, sie wird nicht dupliziert, sie kann kombiniert werden. Leute, die zum Projekt kommen, können sehen, was Sie haben, welche Bedingungen, das Themenmodell verstehen.

Es gibt ein kleines Problem: Expression verfügt nicht über die Methoden And (), Or () und Not (). Dies sind Erweiterungsmethoden, die unabhängig voneinander implementiert werden müssen.

Der erste Umsetzungsversuch war dieser. Über den Ausdrucksbaum gibt es im Internet eine ganze Menge Dokumentation, und alles ist nicht detailliert. Deshalb habe ich versucht, nur Expression zu nehmen, Strg + Leertaste gedrückt, OrElse () gesehen und darüber gelesen. Zwei Ausdrücke bestanden, um Lambda zu kompilieren und zu erhalten. Dies wird nicht funktionieren.

Tatsache ist, dass dieser Ausdruck aus zwei Teilen besteht: Parameter und Körper. Die zweite besteht ebenfalls aus einem Parameter und einem Körper. In OrElse () müssen Sie die Ausdruckskörper übergeben, dh es ist sinnlos, Lambdas mit "AND" und "OR" zu vergleichen. Dies funktioniert nicht. Wir beheben, aber es wird nicht wieder funktionieren.
Aber wenn es das letzte Mal eine NotSupportedException gab, dass das Lambda nicht unterstützt wurde, gibt es jetzt eine seltsame Geschichte über Parameter 1, Parameter 2, "etwas stimmt nicht, ich werde nicht arbeiten".
C # 7.0 auf den Punkt gebracht
Dann dachte ich, dass die wissenschaftliche Poke-Methode nicht funktionieren würde, ich muss es herausfinden. Er begann zu googeln und fand die Seite von Albaharis Buch "
C # 7.0 in a Nutshell ".

Joseph Albahari, der auch Entwickler der beliebten LINQKit-Bibliothek und des LINQPad ist, beschreibt dieses Problem nur. dass Sie nicht nur Expression nehmen und kombinieren können, und wenn Sie den magischen Expression.Invoke () nehmen, wird es funktionieren.
Frage: Was ist Expression.Invoke ()? Gehen Sie erneut zu Google. Es wird ein InvocationExpression erstellt, der einen Delegaten- oder Lambda-Ausdruck auf die Argumentliste anwendet.

Wenn ich Ihnen diesen Code jetzt vorlese, wo wir Expression.Invoke () verwenden, übergeben wir die Parameter, dann wird dasselbe in Englisch geschrieben. Es wird nicht klarer. Es gibt einige magische Expression.Invoke (), die aus irgendeinem Grund dieses Problem mit Parametern lösen. Wir müssen glauben, es ist nicht notwendig zu verstehen.

Wenn Sie versuchen, EFs wie diesen kombinierten Ausdruck zu füttern, wird er erneut fallen und sagen, dass Expression.Invoke () nicht unterstützt wird. Übrigens begann EF Core zu unterstützen, aber EF 6 hält nicht. Aber Albahari bietet nur an, AsExpandable () zu schreiben, und alles funktioniert.

Und Sie können in Ausdrucksunterabfragen ersetzen, wenn wir einen Delegaten benötigen. Damit sie übereinstimmen, schreiben wir Compile (). Wenn wir jedoch AsExpandable () schreiben, wie Albahari vorschlägt, wird Compile () nicht tatsächlich ausgeführt, aber alles wird auf magische Weise korrekt ausgeführt.

Ich glaubte kein Wort und stieg in die Quelle. Was ist die AsExpandable () -Methode? Es hat Abfrage und QueryOptimizer. Wir werden das zweite aus Klammern herauslassen, da es uninteressant ist, aber einfach klebt. Ausdruck: Wenn es 3 + 5 gibt, wird es 8 setzen.

Es ist interessant, dass die Expand () -Methode später aufgerufen wird, danach der queryOptimizer, und dann alles an den Abfrageanbieter übergeben wird, der nach der Expand () -Methode irgendwie überarbeitet wurde.

Wir öffnen es, es ist Besucher, im Inneren sehen wir das nicht originale Compile (), das etwas anderes kompiliert. Ich werde Ihnen nicht genau sagen, obwohl dies eine bestimmte Bedeutung hat, aber wir entfernen eine Zusammenstellung und ersetzen sie durch eine andere. Großartig, aber es riecht nach Level 80-Marketing, weil die Auswirkungen auf die Leistung nirgendwo hin gehen.
Auf der Suche nach einer Alternative
Ich dachte, dass dies nicht funktionieren würde und suchte nach einer anderen Lösung. Und fand es. Es gibt solchen Pete Montgomery, der auch über dieses Problem
schreibt und behauptet, Albahari habe vorgetäuscht.

Pete sprach mit den Entwicklern von EF und sie lehrten ihn, alles ohne Expression.Evoke () zu kombinieren. Die Idee ist sehr einfach: Der Hinterhalt war mit den Parametern. Tatsache ist, dass es bei der Kombination Ausdruck einen Parameter des ersten Ausdrucks und einen Parameter des zweiten gibt. Sie stimmen nicht überein. Die Körper wurden zusammengeklebt, aber die Parameter blieben in der Luft hängen. Sie müssen richtig verbunden werden.
Dazu müssen Sie ein Wörterbuch kompilieren, indem Sie die Parameter der Ausdrücke betrachten, wenn das Lambda nicht aus einem Parameter stammt. Wir erstellen ein Wörterbuch und binden alle Parameter des zweiten wieder an die Parameter des ersten, so dass die Anfangsparameter in Expression eingegeben werden. Fahren Sie durch den gesamten Körper, den wir zusammengeklebt haben.

Mit einer so einfachen Methode können Sie mit Expression.Invoke () alle Hinterhalte beseitigen. Darüber hinaus wird dies bei der Implementierung von Pete Montgomery noch cooler. Es verfügt über eine Compose () -Methode, mit der Sie einen beliebigen Ausdruck kombinieren können.

Wir nehmen Ausdruck und durch AndAlso verbinden wir uns, arbeiten ohne Expandable (). Diese Implementierung wird in Booleschen Operationen verwendet.
Technische Daten und Einheiten
Alles war in Ordnung, bis klar wurde, dass Aggregate in der Natur existieren. Für diejenigen, die nicht vertraut sind, erkläre ich: Wenn Sie ein Domänenmodell haben und alle miteinander in Beziehung stehenden Entitäten in Form von Bäumen darstellen, ist ein separat hängender Baum ein Aggregat. Die Bestellung wird zusammen mit den Bestellpositionen als Aggregat bezeichnet, und die Bestellessenz ist die Aggregationswurzel.

Wenn es neben Produkten noch Kategorien gibt, für die eine Geschäftsregel in Form einer Spezifikation deklariert ist, dass es eine bestimmte Bewertung gibt, die mehr als 50 betragen sollte, wie Vermarkter sagten, und wir sie auf diese Weise verwenden möchten, dann ist dies gut.

Aber wenn wir die Waren aus einer guten Kategorie herausziehen wollen, ist es wiederum schlecht, weil unsere Typen nicht übereinstimmten. Spezifikation für die Kategorie, aber Produkte werden benötigt.

Wieder müssen wir das Problem irgendwie lösen. Die erste Option: Ersetzen Sie Select () durch SelectMany (). Zwei Dinge mag ich hier nicht. Erstens weiß ich nicht, wie die SelectMany () - Unterstützung in allen gängigen Abfrageanbietern implementiert ist. Zweitens, wenn jemand einen Abfrageanbieter schreibt, schreibt er als erstes eine nicht implementierte Ausnahme und SelectMany (). Und der dritte Punkt: Die Leute denken, dass SelectMany () entweder Funktionalität oder Joins ist, die normalerweise nicht mit einer SELECT-Abfrage verknüpft sind.
Zusammensetzung
Ich möchte Select () verwenden, nicht SelectMany ().

Etwa zur gleichen Zeit las ich über Kategorietheorie, über funktionale Zusammensetzung und dachte, wenn es Spezifikationen vom Produkt im Bool unten gibt, gibt es eine Funktion, die vom Produkt zur Kategorie gehen kann, es gibt eine Spezifikation bezüglich der Kategorie, die dann die erste ersetzt Als Argument der zweiten erhalten wir, was wir brauchen, eine Spezifikation bezüglich des Produkts. Absolut das Gleiche wie funktionale Komposition, jedoch für Ausdrucksbäume.

Dann wäre es möglich, eine solche Where () -Methode zu schreiben, dass es notwendig ist, von Produkten zu Kategorien zu wechseln und die Spezifikation auf diese verwandte Entität anzuwenden. Eine solche Syntax für meinen subjektiven Geschmack sieht ziemlich verständlich aus.
public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable, Expression<Func<T, TParam>> prop, Expression<Func<TParam, bool>> where) { return queryable.Where(prop.Compose(where)); }
Mit der Compose () -Methode kann dies auch einfach durchgeführt werden. Wir nehmen den eingegebenen Ausdruck aus den Produkten und kombinieren ihn mit den Spezifikationen für das Produkt und das wars.

Jetzt können Sie ein solches Where () schreiben. Dies funktioniert, wenn Sie eine Maschine beliebiger Länge haben. Kategorie hat eine Superkategorie und eine beliebige Anzahl weiterer Eigenschaften, die ersetzt werden können.
„Da wir ein Werkzeug für die funktionale Komposition haben, es kompilieren und dynamisch zusammenstellen können, riecht es nach Metaprogrammierung!“, Dachte ich.
Projektionen
Wo können wir Metaprogrammierung anwenden, damit wir weniger Code schreiben müssen?

Die erste Option ist die Projektion. Das Herausziehen eines ganzen Unternehmens ist oft zu teuer. Meistens geben wir es an die Front weiter, serialisieren JSON. Aber es braucht nicht die ganze Essenz zusammen mit dem Aggregat. Sie können dies mit LINQ so effizient wie möglich tun, indem Sie Select () manuell schreiben. Nicht schwer, aber langweilig.

Stattdessen schlage ich vor, dass jeder ProjectToType () verwendet. Zumindest gibt es zwei Bibliotheken, die dies tun können: Automapper und Mapster. Aus irgendeinem Grund wissen sehr viele Leute, dass AutoMapper Mapping im Speicher durchführen kann, aber nicht jeder weiß, dass es abfragbare Erweiterungen hat, es hat auch Ausdruck und es kann einen SQL-Ausdruck erstellen. Wenn Sie immer noch manuelle Abfragen schreiben und LINQ verwenden, macht es keinen Sinn, dies mit Ihren Händen zu tun, da dies keine besonders schwerwiegenden Leistungsbeschränkungen sind. Dies ist die Arbeit der Maschine, nicht der Person.
Filtern
Wenn wir dies mit Projektionen tun können, warum nicht zum Filtern.

Hier ist auch der Code. Ein Filter kommt herein. Viele Geschäftsanwendungen sehen folgendermaßen aus: Ein Filter kam, füge Where () hinzu, ein anderer Filter kam, füge Where () hinzu. Wie viele Filter gibt es, so viele und wiederholen. Nichts kompliziertes, aber viel Kopieren und Einfügen.

Wenn wir als AutoMapper AutoFilter, Project und Filter schreiben, damit er alles selbst macht, wäre das cool - weniger Code.

Das ist nichts kompliziertes. Nehmen Sie Expression.Property, gehen Sie durch das DTO und im Wesentlichen. Wir finden gemeinsame Eigenschaften, die identisch genannt werden. Wenn sie gleich heißen, sieht es aus wie ein Filter.
Als Nächstes müssen Sie nach null suchen, eine Konstante verwenden, um den Wert von DTO abzurufen, ihn im Ausdruck zu ersetzen und die Konvertierung hinzuzufügen, falls Sie Int und NullableInt oder eine andere Nullable haben, damit die Typen übereinstimmen. Und setzen Sie zum Beispiel Equals (), einen Filter, der auf Gleichheit prüft.

Sammeln Sie dann Lambda und gehen Sie für jede Eigenschaft durch: Wenn es viele davon gibt, sammeln Sie entweder durch „UND“ oder „ODER“, je nachdem, wie der Filter für Sie funktioniert.

Dasselbe kann für das Sortieren getan werden, ist jedoch etwas komplizierter, da die OrderBy () -Methode zwei Generika enthält. Sie müssen sie also mit Ihren Händen ausfüllen, mithilfe von Reflections die OrderBy () -Methode aus zwei Generika erstellen und den Typ der Entität einfügen, die wir verwenden, den Typ der Sortierbarkeit Eigentum. Im Allgemeinen können Sie dies auch tun, es ist nicht schwierig.
Es stellt sich die Frage: Wo soll Where () platziert werden - auf Unternehmensebene, wie die Spezifikationen angekündigt wurden, oder nach der Projektion, und dort und dort wird es funktionieren.

Es ist beides wahr, und so, weil die Spezifikationen per Definition Geschäftsregeln sind und wir sie schätzen und schätzen müssen und keinen Fehler mit ihnen machen müssen. Dies ist eine eindimensionale Schicht. Bei Filtern geht es mehr um die Benutzeroberfläche, dh sie filtern nach DTO. Daher können Sie zwei Where () einfügen. Es gibt wahrscheinlichere Fragen, wie gut der Abfrageanbieter damit umgehen wird, aber ich glaube, dass ORM-Lösungen sowieso schlechtes SQL schreiben, und es wird nicht viel schlimmer sein. Wenn Ihnen das sehr wichtig ist, dann handelt diese Geschichte überhaupt nicht von Ihnen.

Wie sie sagen, ist es besser, einmal zu sehen als hundertmal zu hören.
Jetzt hat der Laden drei Produkte: Snickers, Subaru Impreza und Mars. Seltsamer Laden. Versuchen wir, Snickers zu finden. Es gibt. Mal sehen, was hundert Rubel. Auch Snickers. Und für 500? Vergrößern, da ist nichts. Und für den 100500 Subaru Impreza. Großartig, das gleiche gilt für das Sortieren.
Alphabetisch und nach Preis sortieren. Der Code dort ist genauso geschrieben wie er war. Diese Filter funktionieren für alle Klassen. Wenn Sie versuchen, nach Namen zu suchen, ist auch Subaru vorhanden. Und in meiner Präsentation war Equals (). Wie so? Tatsache ist, dass der Code hier und in der Präsentation etwas anders ist. Ich habe die Zeile über Equals () auskommentiert und eine spezielle Straßenmagie hinzugefügt. Wenn wir den String-Typ haben, brauchen wir nicht Equals (), sondern rufen StartWith () auf, das ich auch erhalten habe. Daher wird für die Zeilen ein anderer Filter erstellt.

Dies bedeutet, dass Sie hier Strg + Umschalt + R drücken, die Methode auswählen und nicht wenn schreiben, sondern wechseln können, oder sogar das Muster „Strategie“ implementieren und dann verrückt werden können. Sie können alle Wünsche bezüglich des Betriebs von Filtern verwirklichen. Es hängt alles von den Typen ab, mit denen Sie arbeiten. Am wichtigsten ist, dass Filter gleich funktionieren.
Sie können zustimmen, dass Filter in allen UI-Elementen folgendermaßen funktionieren sollten: Zeichenfolgen werden auf eine Weise durchsucht, Geld wird auf eine andere Weise durchsucht. Koordinieren Sie dies alles, schreiben Sie einmal, alles wird in verschiedenen Schnittstellen korrekt ausgeführt, und kein anderer Entwickler wird es beschädigen, da sich dieser Code nicht auf Anwendungsebene befindet, sondern irgendwo entweder in einer externen Bibliothek oder in Ihrem Kernel.
Validierung
Zusätzlich zum Filtern und Projizieren können Sie eine Validierung durchführen. Die JS-Bibliothek
TComb.validation hatte diese Idee. TComb ist eine Abkürzung für Typkombinatoren und basiert auf einem Typsystem und so weiter. refinement', .
, JS, nill, undefined, .
. . , x >= 0 , Positive. . , , -.

. refinement, C#, IsValid(), Expression , . .
public class RefinementAttribute: ValidationAttribute { public IValidator<object> Refinement { get; } public RefinementAttribute(Type refinmentType) { Refinement = (IValidator<object>) Activator.CreateInstance(refinmentType); } public override bool IsValid(object value) => Refinement.Validate(value).IsValid(); }
DataAnnotations ASP.NET MVC, . RefinementAttribute(), . , RefinementAttribute , , .NET, .

. AdultRefinement, 18.

, . NoJS JS , . , C# , JS. JSX, ES6 JavaScript. ? Visitor, , JavaScript.

— , . regexp, StringBuilder, regexp. , JS — , bool , . , .
{ predicate: “x=> (x >= 18)”, errorMessage: “For adults only» }
, , , JS errorMessage «For adults only». . . , .
React, UserRefinment() Expression errorMessage, refinment number, eval, . , number, JS. , . , , false .

alert. onSubmit, alert , . .

Ok(ModelState.IsValid), User, JavaScript. Refinement.
using … namespace DemoApp.Core { public class User: HasNameBase { [Refinement(typeof(AdultRefinement))] public int Age { get; set; } } }
, . JavaScript. , - C#, , . NoJS, .
Testen

., -, Moq. mock - — moq, fluent-. , .
moq — Expression, . , Castle.DynamicProxy. . .

, Core - WCF. , WebAPI. WebAPI, WCF WSDL . WebAPI swagger. swagger — , , API . WCF, WSDL, API, .
, , . moq GetResponse<>() ProductController, , , . , , Ctrl+Space , , , , dll . Intellisense, , .
, Moq, , , , API . , - , , , POST- GET-, , , Intellisense expression tree . , , Web-.
Reflection
-, Reflection.

, Reflection , . Expression. — CreateInstance. , Expression.New(), , .

. - . Activator, , . Constructor_Invoke, . — New compiled-. , , , , .

.

. - Fast Memember Fast Reflect, , . , , - . , , .

, , , . , , . , , - , , , , DSL, Little Languages -, .
, - , . , , , . DLR, , IronPython, IronRuby. Expression tree CLR. -, .
Zusammenfassung
, . , . , , - , .
— . 100 , . , , , . Expression Trees, - .
, , , , , .
, , . , .
, , Expression, Reflection, , , . , , API, , Expression . .
Expression.Compile() . , Expression' , Dictionary . - , , , , , Compile() . , .
— C#-, , , Where(), - implicit- . MSDN, . , , , , , , StackOverflow , - .
, , . , , ,
. , — .
22-23 DotNext 2018 Moscow . , ( ).