Mendapatkan data enum dalam perspektif Automapper

Sedikit program pendidikan


Saya sangat suka Automapper, terutama QueryableExtensions dan metode ProjectTo . Singkatnya, metode ini memungkinkan proyeksi jenis secara langsung dalam permintaan SQL. Itu diperbolehkan untuk menerima dto sebenarnya dari database. Yaitu tidak perlu mendapatkan semua entitas dari database, memuatnya ke dalam memori, gunakan Automapper.Map<> , yang menyebabkan konsumsi besar dan lalu lintas memori.


Jenis proyeksi


Untuk mendapatkan proyeksi di LINQ, Anda perlu menulis sesuatu seperti ini:


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

Menggunakan QueryableExtensions, kode ini dapat diganti dengan yang berikut (tentu saja, asalkan aturan konversi Pengguna -> UserInfo sudah dijelaskan)


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

Enum dan masalah dengan itu


Proyeksi memiliki satu kelemahan yang perlu dipertimbangkan. Ini adalah pembatasan operasi yang dilakukan. Tidak semuanya bisa diterjemahkan ke dalam query SQL . Secara khusus, tidak mungkin untuk mendapatkan informasi berdasarkan tipe enumerasi. Misalnya, ada Enum berikut


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

Ada entitas di mana properti tipe FooEnum dideklarasikan. Di dto, Anda tidak perlu mendapatkan Enum itu sendiri, tetapi nilai properti Name dari atribut DisplayAttribute. Untuk mewujudkan ini melalui proyeksi tidak berhasil, karena mendapatkan nilai atribut memerlukan Refleksi, yang SQL "tidak tahu apa-apa".


Akibatnya, Anda harus menggunakan Map<> biasa Map<> , memuat semua entitas ke dalam memori, atau memulai tabel tambahan dengan nilai Enum dan kunci asing di atasnya.


Ada Solusi - Ekspresi


Tapi "akan ada hantaman pada wanita tua itu." Bagaimanapun, semua nilai Enum diketahui sebelumnya. SQL memiliki implementasi switch yang dapat Anda sisipkan saat membentuk proyeksi. Masih memahami bagaimana melakukan ini. HashTag: "Pohon Ekspresi-Semua-Kita."


Automapper, saat memproyeksikan tipe, dapat mengonversi ekspresi menjadi ekspresi yang, setelah Kerangka Entitas, mengonversi ke kueri SQL yang sesuai.


Pada pandangan pertama, sintaks untuk membuat pohon ekspresi dalam runtime sangat tidak nyaman. Tetapi setelah beberapa masalah kecil diselesaikan, semuanya menjadi jelas. Untuk menyelesaikan masalah dengan Enum, Anda perlu membuat pohon bersarang ekspresi bersyarat yang mengembalikan nilai, tergantung pada data sumber. Sesuatu seperti ini


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

Tentukan metode tanda tangan.


  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() { } 

Metode GetExpression() harus menghasilkan ekspresi yang menerima instance FooEntity dan mengembalikan representasi string untuk properti Enum .
Pertama, tentukan parameter input dan dapatkan nilai properti itu sendiri


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

Alih-alih string nama properti, Anda dapat menggunakan nameof(FooEntity.Enum) compiler nameof(FooEntity.Enum) atau bahkan mendapatkan data tentang properti System.Reflection.PropertyInfo atau pengambil System.Reflection.MethodInfo . Tetapi sebagai contoh, cukup bagi kami untuk secara eksplisit menetapkan nama properti.


Untuk mengembalikan nilai tertentu, kami menggunakan metode Expression.Constant . Kami membentuk nilai default


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

Setelah itu, kami berturut-turut "membungkus" hasilnya dalam suatu kondisi.


  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(); } } 

Yang tersisa hanyalah menyusun hasilnya


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

Sedikit lebih banyak refleksi


Menyalin semua nilai Enum sangat merepotkan. Mari kita perbaiki


  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); } 

Mari kita tingkatkan mendapatkan nilai properti


Kerugian dari kode di atas adalah pengikatan yang ketat dari jenis entitas yang digunakan. Jika masalah yang sama perlu dipecahkan terkait dengan kelas lain, Anda perlu menemukan cara untuk mendapatkan nilai properti enumerasi tipe. Jadi biarkan ekspresi melakukannya untuk kita. Sebagai parameter dari metode ini, kami akan meneruskan ekspresi yang menerima nilai properti, dan kode itu sendiri - kami hanya membentuk sekumpulan hasil untuk properti ini. Template untuk membantu kami


  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); } 

Beberapa klarifikasi. Karena kita mendapatkan nilai input melalui ekspresi lain, maka kita tidak perlu mendeklarasikan parameter melalui Expression.Parameter . Kami mengambil parameter ini dari properti ekspresi input, dan menggunakan tubuh ekspresi untuk mendapatkan nilai properti.
Kemudian Anda dapat menggunakan metode baru seperti ini:


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



Semua pengembangan pohon ekspresi berhasil.


Saya sangat merekomendasikan membaca artikel oleh Maxim Arshinov . Terutama tentang Pohon Ekspresi dalam pengembangan perusahaan .

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


All Articles