Kami mengadaptasi AutoMapper untuk diri kami sendiri

AutoMapper adalah salah satu alat utama yang digunakan dalam mengembangkan aplikasi Enterprise, jadi saya ingin menulis kode sesedikit mungkin sambil mendefinisikan pemetaan entitas.


Saya tidak suka duplikasi di MapFrom dengan proyeksi luas.


CreateMap<Pupil, PupilDto>() .ForMember(x => x.Name, s => s.MapFrom(x => x.Identity.Passport.Name)) .ForMember(x => x.Surname, s => s.MapFrom(x => x.Identity.Passport.Surname)) .ForMember(x => x.Age, s => s.MapFrom(x => x.Identity.Passport.Age)) .ForMember(x => x.Number, s => s.MapFrom(x => x.Identity.Passport.Number)) 

Saya ingin menulis ulang seperti ini:


 CreateMap<Pupil, PupilDto>() .From(x=>x.IdentityCard.Passport).To() 

Proyeksi


AutoMapper dapat membangun pemetaan dalam memori dan menerjemahkannya ke SQL, melengkapi Expression, memproyeksikan dalam DTO sesuai dengan aturan yang Anda jelaskan di profil.


 EntityQueryable.Select(dtoPupil => new PupilDto() { Name = dtoPupil.Identity.Passport, Surname = dtoPupil.Identity.Passport.Surname}) 

80% pemetaan yang harus saya tulis adalah pemetaan yang melengkapi Ekspresi dari IQueryble.


Ini sangat nyaman:


 public ActionResult<IEnumerable<PupilDto>> GetAdultPupils(){ var result = _context.Pupils .Where(x=>x.Identity.Passport.Age >= 18 && ...) .ProjectTo<PupilDto>().ToList(); return result; } 

Dalam gaya deklaratif, kami membentuk kueri di tabel Murid, menambahkan pemfilteran, diproyeksikan ke DTO yang diinginkan dan mengembalikannya ke klien, sehingga Anda dapat menulis semua metode baca antarmuka CRUD sederhana. Dan semua ini akan dilakukan pada tingkat basis data.


Benar, dalam aplikasi serius tindakan seperti itu tidak mungkin memuaskan pelanggan.


Kontra AutoMapper'a


1) Sangat verbose, dengan pemetaan "lebar" Anda harus menulis aturan yang tidak sesuai pada satu baris kode.


Profil tumbuh dan berubah menjadi arsip kode yang ditulis sekali dan berubah hanya ketika refactoring nama.


2) Jika Anda menggunakan pemetaan sesuai dengan konvensi, namanya hilang
properti di DTO:


 public class PupilDto { //  Pupil       IdentityCard // IdentityCard     Passport public string IdentityCardPassportName { get; set; } public string IdentityCardPassportSurname { get; set; } } 

3) Kurangnya keamanan jenis


1 dan 2 adalah momen yang tidak menyenangkan, tetapi Anda bisa bertahan dengan itu, tetapi dengan kurangnya keamanan tipe saat mendaftar, ini sudah lebih sulit untuk dipasang, ini tidak boleh dikompilasi:


 // Name - string // Age - int ForMember(x => x.Age, s => s.MapFrom(x => x.Identity.Passport.Name) 

Kami ingin menerima informasi tentang kesalahan tersebut pada tahap kompilasi, dan bukan pada saat run-time.


Menggunakan pembungkus ekstensi untuk menghilangkan titik-titik ini.


Menulis pembungkus


Mengapa pendaftaran harus ditulis dengan cara ini?


 CreateMap<Pupil, PupilDto>() .ForMember(x => x.Name, s => s.MapFrom(x => x.Identity.Passport.Name)) .ForMember(x => x.Surname, s => s.MapFrom(x => x.Identity.Passport.Surname)) .ForMember(x => x.Age, s => s.MapFrom(x => x.Identity.Passport.Age)) .ForMember(x => x.House, s => s.MapFrom(x => x.Address.House)) .ForMember(x => x.Street, s => s.MapFrom(x => x.Address.Street)) .ForMember(x => x.Country, s => s.MapFrom(x => x.Address.Country)) .ForMember(x => x.Surname, s => s.MapFrom(x => x.Identity.Passport.Age)) .ForMember(x => x.Group, s => s.MapFrom(x=>x.EducationCard.StudyGroup.Number)) 

Jauh lebih ringkas:


 CreateMap<Pupil,PupilDto>() //    // PassportName = Passport.Name, PassportSurname = Passport.Surname .From(x => x.IdentityCard.Passport).To() // House,Street,Country -   .From(x => x.Address).To() //    -  DTO,  -  .From(x => x.EducationCard.Group).To((x => x.Group,x => x.Number)); 

Metode Ke akan menerima tupel jika Anda perlu menentukan aturan pemetaan


IMapping <TSource, TDest> adalah antarmuka automaper di mana metode ForMember, ForAll () didefinisikan ... semua metode ini mengembalikan ini (Fluent Api).


Kami akan mengembalikan pembungkus untuk mengingat Ekspresi dari metode Dari


 public static MapperExpressionWrapper<TSource, TDest, TProjection> From<TSource, TDest, TProjection> (this IMappingExpression<TSource, TDest> mapping, Expression<Func<TSource, TProjection>> expression) => new MapperExpressionWrapper<TSource, TDest, TProjection>(mapping, expression); 

Sekarang programmer, setelah menulis metode Dari, akan segera melihat kelebihan metode To , dengan demikian kami akan memberitahunya API, dalam kasus seperti itu kita dapat menyadari semua pesona metode ekstensi, kami memperluas perilaku tanpa memiliki akses tulis ke sumber automapper


Kami melambangkan


Menerapkan metode diketik Ke lebih rumit.


Mari kita coba merancang metode ini, kita perlu memecahnya menjadi potongan-potongan sebanyak mungkin dan mengeluarkan semua logika dalam metode lain. Segera setuju bahwa kami akan membatasi jumlah parameter tuple hingga sepuluh.


Ketika tugas serupa dijumpai dalam praktik saya, saya segera melihat ke arah Roslyn, saya tidak merasa ingin menulis banyak jenis metode yang sama dan melakukan Copy Paste, lebih mudah untuk menghasilkannya.


Dalam generik ini akan membantu kita. Diperlukan untuk menghasilkan 10 metode dengan jumlah generik dan parameter yang berbeda


Pendekatan pertama pada proyektil itu sedikit berbeda, saya ingin membatasi jenis kembalinya (int, string, boolean, DateTime) dan tidak menggunakan tipe universal.


Kesulitannya adalah bahkan untuk 3 parameter kita harus menghasilkan 64 overload yang berbeda, dan ketika menggunakan generik hanya 1:


 IMappingExpression<TSource, TDest> To<TSource, TDest, TProjection,T,T1, T2, T3>( this MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) arg0, (Expression<Func<TDest, T1>>, Expression<Func<TProjection, T1>>) arg1, (Expression<Func<TDest, T2>>, Expression<Func<TProjection, T2>>) arg2, (Expression<Func<TDest, T3>>, Expression<Func<TProjection, T3>>) arg3) { ... } 

Tapi ini bukan masalah utama, kita menghasilkan kode, itu akan memakan waktu dan kita akan mendapatkan seluruh rangkaian metode yang diperlukan.


Masalahnya berbeda, ReSharper tidak akan mengambil begitu banyak kelebihan dan hanya menolak untuk bekerja, Anda akan kehilangan Intellisience dan memuat IDE.


Kami menerapkan metode yang membutuhkan satu tuple:


 public static IMappingExpression<TSource, TDest> To <TSource, TDest, TProjection, T>(this MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) arg0) { //    RegisterByConvention(mapperExpressionWrapper); //    expreession RegisterRule(mapperExpressionWrapper, arg0); //  IMappingExpression,     //   extension  return mapperExpressionWrapper.MappingExpression; } 

Pertama, mari kita periksa pemetaan properti apa yang dapat ditemukan oleh konvensi, ini adalah metode yang cukup sederhana, untuk setiap properti di DTO kita mencari jalan di entitas asli. Metode harus disebut secara refleksif, karena Anda perlu mendapatkan lambda yang diketik, dan tipenya tergantung pada prop.


Tidak mungkin mendaftarkan lambda dari tipe Expression <Func <TSource, object >>, maka AutoMapper akan memetakan semua properti DTO untuk mengetik objek


 private static void RegisterByConvention<TSource, TDest, TProjection>( MapperExpressionWrapper<TSource, TDest, TProjection> mapperExpressionWrapper) { var properties = typeof(TDest).GetProperties().ToList(); properties.ForEach(prop => { // mapperExpressionWrapper.FromExpression = x=>x.Identity.Passport // prop.Name = Name // ruleByConvention Expression<Func<Pupil,string>> x=>x.Identity.Passport.Name var ruleByConvention = _cachedMethodInfo .GetMethod(nameof(HelpersMethod.GetRuleByConvention)) .MakeGenericMethod(typeof(TSource), typeof(TProjection), prop.PropertyType) .Invoke(null, new object[] {prop, mapperExpressionWrapper.FromExpression}); if (ruleByConvention == null) return; // mapperExpressionWrapper.MappingExpression.ForMember(prop.Name, s => s.MapFrom((dynamic) ruleByConvention)); }); } 

RegisterRule menerima tuple yang mendefinisikan aturan pemetaan, itu perlu "terhubung" di dalamnya
FromExpression dan ekspresi diteruskan ke tuple.


Ini akan membantu kami Expression. Karena itu, EF Core 2.0 tidak mendukungnya, versi yang lebih baru mulai mendukung. Ini akan memungkinkan Anda membuat "komposisi lambd":


 Expression<Func<Pupil,StudyGroup>> from = x=>x.EducationCard.StudyGroup; Expression<Func<StudyGroup,int>> @for = x=>x.Number; //invoke = x=>x.EducationCard.StudyGroup.Number; var composition = Expression.Lambda<Func<Pupil, string>>( Expression.Invoke(@for,from.Body),from.Parameters.First()) 

Metode RegisterRule :


 private static void RegisterRule<TSource, TDest, TProjection, T (MapperExpressionWrapper<TSource,TDest,TProjection> mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) rule) { //rule = (x=>x.Group,x=>x.Number) var (from, @for) = rule; //      @for = (Expression<Func<TProjection, T>>) _interpolationReplacer.Visit(@for); //mapperExpressionWrapper.FromExpression = (x=>x.EducationCard.StudyGroup) var result = Expression.Lambda<Func<TSource, T>>( Expression.Invoke(@for, mapperExpressionWrapper.FromExpression.Body), mapperExpressionWrapper.FromExpression.Parameters.First()); var destPropertyName = from.PropertiesStr().First(); // result = x => Invoke(x => x.Number, x.EducationCard.StudyGroup) //  ,  result = x=>x.EducationCard.StudyCard.Number mapperExpressionWrapper.MappingExpression .ForMember(destPropertyName, s => s.MapFrom(result)); } 

Metode To dirancang agar mudah diperluas saat menambahkan parameter tuple. Saat menambahkan tuple lain ke parameter, Anda perlu menambahkan generik, parameter lain, dan memanggil metode RegisterRule untuk parameter baru.


Contoh untuk dua parameter:


 IMappingExpression<TSource, TDest> To<TSource, TDest, TProjection, T, T1> (this MapperExpressionWrapper<TSource,TDest,TProjection>mapperExpressionWrapper, (Expression<Func<TDest, T>>, Expression<Func<TProjection, T>>) arg0, (Expression<Func<TDest, T1>>, Expression<Func<TProjection, T1>>) arg1) { RegisterByConvention(mapperExpressionWrapper); RegisterRule(mapperExpressionWrapper, arg0); RegisterRule(mapperExpressionWrapper, arg1); return mapperExpressionWrapper.MappingExpression; } 

Kami menggunakan CSharpSyntaxRewriter , ini adalah pengunjung yang berjalan melalui simpul pohon sintaksis. Kami mengambil sebagai metode dasar dengan To dengan satu argumen dan menambahkan parameter generik dan memanggil RegisterRule ;


 public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { //     To if (node.Identifier.Value.ToString() != "To") return base.VisitMethodDeclaration(node); // returnStatement = return mapperExpressionWrapper.MappingExpression; var returnStatement = node.Body.Statements.Last(); //beforeReturnStatements: //[RegisterByConvention(mapperExpressionWrapper), // RegisterRule(mapperExpressionWrapper, arg0)] var beforeReturnStatements = node.Body.Statements.SkipLast(1); //   RegisterRule  returStatement var newBody = SyntaxFactory.Block( beforeReturnStatements.Concat(ReWriteMethodInfo.Block.Statements) .Concat(new[] {returnStatement})); //     return node.Update( node.AttributeLists, node.Modifiers, node.ReturnType, node.ExplicitInterfaceSpecifier, node.Identifier, node.TypeParameterList.AddParameters (ReWriteMethodInfo.Generics.Parameters.ToArray()), node.ParameterList.AddParameters (ReWriteMethodInfo.AddedParameters.Parameters.ToArray()), node.ConstraintClauses, newBody, node.SemicolonToken); } 

ReWriteMethodInfo berisi simpul pohon sintaks yang dihasilkan yang perlu Anda tambahkan. Setelah itu, kita mendapatkan daftar 10 objek bertipe MethodDeclarationSyntax (pohon sintaks yang mewakili metode).


Pada langkah berikutnya, kita mengambil kelas di mana metode templat Ke terletak dan menulis semua metode baru ke dalamnya menggunakan Pengunjung lain, di mana kita mendefinisikan kembali VisitClassDeclatation.


Metode Pembaruan memungkinkan Anda untuk mengedit simpul pohon yang ada, di bawah kapnya mengulangi semua argumen yang diteruskan, dan jika setidaknya satu berbeda dari yang asli, membuat simpul baru.


 public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { //todo refactoring it return node.Update( node.AttributeLists, node.Modifiers, node.Keyword, node.Identifier, node.TypeParameterList, node.BaseList, node.ConstraintClauses, node.OpenBraceToken, new SyntaxList<MemberDeclarationSyntax>(ReWriteMethods), node.CloseBraceToken, node.SemicolonToken); } 

Pada akhirnya, kita mendapatkan SyntaxNode - sebuah kelas dengan metode yang ditambahkan, menulis simpul ke file baru. Sekarang kita telah membebani metode To , yang mengambil 1 hingga 10 tupel dan pemetaan yang jauh lebih ringkas.


Titik ekspansi


Mari kita lihat AutoMapper sebagai sesuatu yang lebih. Penyedia Kueri tidak dapat menguraikan cukup banyak kueri, dan bagian tertentu dari kueri ini dapat ditulis ulang secara berbeda. Di sinilah AutoMapper berperan, ekstensi adalah titik ekstensi tempat kami dapat menambahkan aturan kami sendiri.


Kami akan menggunakan pengunjung dari artikel sebelumnya yang menggantikan interpolasi string dengan penggabungan dalam metode RegusterRule . Akibatnya, semua ekspresi yang menentukan pemetaan dari entitas akan melewati pengunjung ini, sehingga menghilangkan kebutuhan untuk memanggil ReWrite setiap kali. Ini bukan obat mujarab, satu-satunya cara yang kami bisa mengelola adalah proyeksi, tetapi masih membuat hidup lebih mudah.


Kami juga dapat menambahkan beberapa ekstensi yang nyaman, misalnya, untuk pemetaan berdasarkan kondisi:


 CreateMap<Passport,PassportDto>() .ToIf(x => x.Age, x => x < 18, x => $"{x.Age}", x => "Adult") 

Hal utama adalah tidak bermain dengan ini dan tidak mulai mentransfer logika kompleks ke level tampilan
Github

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


All Articles