Abrufen von Aufzählungsdaten in einer Automapper-Perspektive

Ein bisschen Bildungsprogramm


Ich mag Automapper sehr, besonders die QueryableExtensions und die ProjectTo <> -Methode . Kurz gesagt, diese Methode ermöglicht die Projektion von Typen direkt in die SQL-Abfrage. Es erlaubte dto tatsächlich von einer Datenbank zu empfangen. Das heißt, Es ist nicht erforderlich, alle Entitäten aus der Datenbank abzurufen, sie in den Speicher zu laden und Automapper.Map<> , was zu einem hohen Verbrauch und Speicherverkehr führte.


Projektionstyp


Um eine Projektion in linq zu erhalten, mussten Sie Folgendes schreiben:


  from user in dbContext.Users where user.IsActive select new { Name = user.Name, Status = user.IsConnected ? "Connected" : "Disconnected" } 

Mit QueryableExtensions kann dieser Code durch den folgenden ersetzt werden (vorausgesetzt, die Konvertierungsregeln User -> UserInfo sind bereits beschrieben).


 dbContext.Users.Where(x => x.IsActive).ProjectTo<UserInfo>(); 

Aufzählung und Probleme damit


Die Projektion hat einen Nachteil, der berücksichtigt werden muss. Dies ist eine Einschränkung der ausgeführten Operationen. Nicht alles kann in eine SQL-Abfrage übersetzt werden . Insbesondere ist es nicht möglich, Informationen nach Aufzählungstyp zu erhalten. Zum Beispiel gibt es die folgende Aufzählung


  public enum FooEnum { [Display(Name = "")] Any, [Display(Name = "")] Open, [Display(Name = "")] Closed } 

Es gibt eine Entität, in der eine Eigenschaft vom Typ FooEnum deklariert ist. In dto müssen Sie nicht Enum selbst abrufen, sondern den Wert der Name-Eigenschaft des DisplayAttribute-Attributs. Dies durch die Projektion zu realisieren, funktioniert nicht, weil Um den Attributwert zu erhalten, ist Reflection erforderlich, von der SQL einfach „nichts weiß“.


Daher müssen Sie entweder die übliche Map<> , alle Entitäten in den Speicher laden oder eine zusätzliche Tabelle mit Enum-Werten und Fremdschlüsseln starten.


Es gibt eine Lösung - Ausdrücke


Aber "es wird einen Slammer gegen die alte Frau geben." Immerhin sind alle Werte von Enum im Voraus bekannt. SQL verfügt über eine switch Implementierung, die Sie beim Erstellen einer Projektion einfügen können. Es bleibt zu verstehen, wie das geht. HashTag: "Ausdrucksbäume - unser Alles."


Automapper kann beim Projizieren von Typen einen Ausdruck in einen Ausdruck konvertieren, der nach dem Entity Framework in die entsprechende SQL-Abfrage konvertiert wird.


Auf den ersten Blick ist die Syntax zum Erstellen von Ausdrucksbäumen zur Laufzeit äußerst unpraktisch. Aber nach ein paar kleinen gelösten Problemen wird alles offensichtlich. Um das Problem mit Enum zu lösen, müssen Sie einen verschachtelten Baum von bedingten Ausdrücken erstellen, die abhängig von den Quelldaten Werte zurückgeben. So etwas in der Art


 IF enum=Any THEN RETURN "" ELSE IF enum=Open THEN RETURN "" ELSE enum=Closed THEN RETURN "" ELSE RETURN "" 

Entscheiden Sie sich für die Methodensignatur.


  public class FooEntity { public int Id { get; set; } public FooEnum Enum { get; set; } } public class FooDto { public int Id { get; set; } public string Name { get; set; } } //  Automapper CreateMap<FooEntity, FooDto>() .ForMember(x => x.Enum, options => options.MapFrom(GetExpression())); private Expression<Func<FooEntity, string>> GetExpression() { } 

Die GetExpression() -Methode sollte einen Ausdruck generieren, der eine Instanz von FooEntity empfängt und eine Zeichenfolgendarstellung für die Enum Eigenschaft zurückgibt.
Definieren Sie zunächst den Eingabeparameter und rufen Sie den Eigenschaftswert selbst ab


 ParameterExpression value = Expression.Parameter(typeof(FooEntity), "x"); var propertyExpression = Expression.Property(value, "Enum"); 

Anstelle der Eigenschaftsnamenzeichenfolge können Sie die nameof(FooEntity.Enum) Compilersyntax nameof(FooEntity.Enum) oder sogar Daten zur Eigenschaft System.Reflection.PropertyInfo oder zum Getter System.Reflection.MethodInfo . Zum Beispiel reicht es jedoch aus, den Eigenschaftsnamen explizit festzulegen.


Um einen bestimmten Wert zurückzugeben, verwenden wir die Expression.Constant Methode. Wir bilden den Standardwert


  Expression resultExpression = Expression.Constant(string.Empty); 

Danach "verpacken" wir das Ergebnis nacheinander in einen Zustand.


  resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Any)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Any)), resultExpression); resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Open)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Open)), resultExpression); resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(FooEnum.Closed)), Expression.Constant(EnumHelper.GetShortName(FooEnum.Closed)), resultExpression); 

  public static class EnumHelper { public static string GetShortName(this Enum enumeration) { return (enumeration .GetType() .GetMember(enumeration.ToString())? .FirstOrDefault()? .GetCustomAttributes(typeof(DisplayAttribute), false)? .FirstOrDefault() as DisplayAttribute)? .ShortName ?? enumeration.ToString(); } } 

Alles was bleibt ist, das Ergebnis zu erstellen


  return Expression.Lambda<Func<TEntity, string>>(resultExpression, value); 

Ein bisschen mehr Nachdenken


Das Kopieren aller Enum-Werte ist äußerst unpraktisch. Lass es uns reparieren


  var enumValues = Enum.GetValues(typeof(FooEnum)).Cast<Enum>(); Expression resultExpression = Expression.Constant(string.Empty); foreach (var enumValue in enumValues) { resultExpression = Expression.Condition( Expression.Equal(propertyExpression, Expression.Constant(enumValue)), Expression.Constant(EnumHelper.GetShortName(enumValue)), resultExpression); } 

Lassen Sie uns den Immobilienwert verbessern


Der Nachteil des obigen Codes ist die enge Bindung des verwendeten Entitätstyps. Wenn ein ähnliches Problem in Bezug auf eine andere Klasse gelöst werden muss, müssen Sie einen Weg finden, um den Wert einer Eigenschaft vom Typ Aufzählung zu erhalten. Also lass es den Ausdruck für uns tun. Als Parameter der Methode übergeben wir einen Ausdruck, der den Wert der Eigenschaft und den Code selbst empfängt. Wir bilden einfach eine Reihe von Ergebnissen für diese Eigenschaft. Vorlagen, die uns helfen


  public static Expression<Func<TEntity, string>> CreateEnumShortNameExpression<TEntity, TEnum>(Expression<Func<TEntity, TEnum>> propertyExpression) where TEntity : class where TEnum : struct { var enumValues = Enum.GetValues(typeof(TEnum)).Cast<Enum>(); Expression resultExpression = Expression.Constant(string.Empty); foreach (var enumValue in enumValues) { resultExpression = Expression.Condition( Expression.Equal(propertyExpression.Body, Expression.Constant(enumValue)), Expression.Constant(EnumHelper.GetShortName(enumValue)), resultExpression); } return Expression.Lambda<Func<TEntity, string>>(resultExpression, propertyExpression.Parameters); } 

Ein paar Klarstellungen. Weil Wir erhalten den Eingabewert über einen anderen Ausdruck, dann müssen wir den Parameter nicht über Expression.Parameter deklarieren. Wir nehmen diesen Parameter aus der Eigenschaft des Eingabeausdrucks und verwenden den Hauptteil des Ausdrucks, um den Wert der Eigenschaft abzurufen.
Dann können Sie die neue Methode folgendermaßen anwenden:


  CreateMap<FooEntity, FooDto>() .ForMember(x => x.Enum, options => options.MapFrom(GetExpression<FooEntity, FooEnum>(x => x.Enum))); 



Alle erfolgreiche Entwicklung von Expressionsbäumen.


Ich empfehle dringend, die Artikel von Maxim Arshinov zu lesen. Insbesondere über Expression Trees in der Unternehmensentwicklung .

Source: https://habr.com/ru/post/de439484/


All Articles