اخبار، مطالب و رویدادهای مرتبط با توسعه نرم افزار رادکام

object-to-object mapping

مقدمه

از auto mapper ها برای نگاشت کردن (Mapping) دو شیء یا ایجاد آبجکت‌های دارای دیتا از روی آبجکت دیگر استفاده می‌کنیم. یک نمونه استفاده از این کتابخانه، در زمان نگاشت کردن DTO ها به شیء مدل است.
امروز دو مورد از این ابزار را با یکدیگر بررسی کرده و به سؤالات زیر پاسخ می‌دهیم:
  1. آیا دو شیء باید از هم ارث برده باشند تا نگاشت صورت بگیرد؟
  2. آیا بدون ارث‌بری و تنها با شبیه بودن نوع داده و نام ویژگی (Property)، همچنان نگاشت صورت می‌گیرد؟
  3. ازلحاظ بهینه بودن کدام‌یک بهتر است؟

انواع کتابخانه‌های نگاشت

نام پکیج تعداد دانلود آخرین تاریخ آپدیت تا زمان نوشته شدن این مطلب حجم فایل توضیحات
AutoMapper 37,160,701 2019/04/26 335kb
  • کندتر
  • استفاده CPU کمتر
Mapster 346,858 2019/04/13 232kb
  • سریع‌تر
  • استفاده CPU بیشتر
TinyMapper 139,155 2018/6/7 94kb از سال 2015 تاکنون فقط نسخه بتا منتشر کرده است.
OoMapper 8,456 2014/6/30 107kb --
EmitMapper 174,707 2011/7/17 64kb تنها همین یک نسخه منتشرشده است.
ValueInjecter 898,512 2018/6/1 44kb --
در این قسمت به بررسی Mapster و Automapper می‌پردازیم.

Mapster

شیوه دریافت

برای دریافت دو راه داریم:
  1. یکی از طریق package manager console که عبارت زیر را داخل آن تایپ می‌کنیم:
    Install-package mapster
  2. یکی از طریق آدرس زیر:
    Tools > NuGet package manager > Manage NuGet packages for solution
    و بعد عبارت Mapster را در تب browse صفحه بازشده، جستجو می‌کنیم.
    تا زمان نوشتن این مطلب mapster تعداد 347 هزار بار در پروژه‌های مختلف دانلود و نصب‌شده است.

شیوه استفاده

شیوه‌های استفاده به‌صورت زیر است:
  1. در حالت ارث‌بری: طبق کد انجام می‌شود.
     
            public class Person
            {
                public string Firstname { get; set; }
                public string Lastname { get; set; }
                public int Age { get; set; }
            }
    
            public class LegalInformation : Person
            {
                public bool IsLegalAge
                {
                    get { return Age > 18; }
                }
            }
            
            static void SimpleMapping()
            {
                var person = new Person()
                {
                    Firstname = "joe",
                    Lastname = "smith",
                    Age = 30
                };
    
                var personWithLegalInfo = person.Adapt<Person, LegalInformation>();
                Console.WriteLine(string.Format("Firstname: {0}, Lastname: {1}, Age: {2}, is legal Age: {3}", personWithLegalInfo.Firstname, personWithLegalInfo.Lastname, personWithLegalInfo.Age, personWithLegalInfo.IsLegalAge));
            }
    
  2. در حالت ویژگی‌های همنام:
    • در حالتی که هردو آبجکت از یک نوع کلاس باشند: برای clone کردن یک آبجکت بدون ارث‌بری از ICloneable مناسب است.
    • آبجکت با نوع کلاس‌های متفاوت با ویژگی‌های هم نام و هم نوع: جدای از نوع ویژگی، اگر کوچک و بزرگ بودن نام ویژگی را رعایت کرده باشیم، نگاشت صورت می‌گیرد. اگر نام ویژگی‌ها کاملاً مشابه بوده ولی نوع داده‌های متفاوت داشته باشند، با اکسپشن مواجه می‌شویم و باید با استفاده از پیکره‌بندی (Config) مناسب از بروز خطا جلوگیری کنیم.
  3. با پیکره‌بندی: استفاده از Mapster با تعریف پیکره‌بندی از دو راه صورت می‌گیرد:
    • استفاده از یک آبجکت TypeAdapterConfig که تنظیمات مناسب را در اختیار Mapster قرار می‌دهد.
              static void ConfigMapping()
                  {
                      var person = new Person()
                      {
                          Firstname = "joe",
                          Lastname = "smith",
                          Age = 30
                      };
          
                      var personWithLegalInfo = person.Adapt<person,>(
                          new TypeAdapterConfig()
                          .Default
                          .IgnoreMember((member, side) => (member.Name == "Age" && side == MemberSide.Source))
                          .EnableNonPublicMembers(true)
                          .MaxDepth(1)
                          .IgnoreNullValues(true)
                          .Config
                          );
          
                      Console.WriteLine(string.Format("Firstname: {0}, Lastname: {1}, Age: {2}, is legal Age: {3}", personWithLegalInfo.Firstname, personWithLegalInfo.Lastname, personWithLegalInfo.Age, personWithLegalInfo.IsLegalAge));
                  }
      </person,>
    • دیگری استفاده از دو صفت (Attribute) زیر در کلاس مدل:
      • [AdaptIgnore] همان‌طور که از نام صفت پیداست، در زمان نگاشت ویژگی موردنظر را نگاشت نمی‌کند.
      • [(AdaptMember(name]: این صفت یک String قبول می‌کند که نام ویژگی هدف در زمان نگاشت شدن اشاره دارد. به عبارتی با ویژگی هم نام خود نگاشت نمی‌شود و در کلاس هدف ویژگی با این عنوان را پیدا می‌کند.

Auto mapper

شیوه دریافت

برای دریافت دو راه داریم:
  1. یکی از طریق package manager console که عبارت زیر را در آن وارد می‌کنیم:
    Install-package automapper
  2. یکی از طریق آدرس زیر:
    Tools > NuGet package manager > Manage NuGet packages for solution
    و بعد عبارت automapper را در تب browse صفحه بازشده، جستجو می‌کنیم.
    تا زمان نوشتن این مطلب automapper در حدود 37 میلیون بار در پروژه‌های مختلف دانلود و نصب‌شده است.

شیوه استفاده

یک تفاوتی که در سینتکس و شیوه استفاده با Mapster دارد، این است که باید پیش از تبدیل و استفاده، تبدیل‌هایی که می‌خواهید از آن‌ها استفاده کنید را در اختیار mapster گذاشته و Mapper را initialize کرده باشید:
Mapper.Initialize(cfg => cfg.CreateMap<person,>());
همچنین توجه کنید که در هر context باید تنها یک‌بار این متد Mapper.Initialize را صدا بزنید؛ چراکه استاتیک بوده و با بیش از یک‌بار صدازدن با اکسپشن مواجه می‌شوید.
اگر احتیاج به استفاده از پیکره‌بندی‌های متعدد دارید و باید چندین نوع کلاس را به یکدیگر نگاشت کنید، می‌توانید خودتان کلاس Mapper را initialize کرده و به‌صورت زیر استفاده کنید:
        var person = new Person()
            {
                Firstname = "joe",
                Lastname = "smith",
                Age = 30
            };

            var config = new MapperConfiguration(cfg =>
            {
                cfg.ShouldMapProperty = info => info.GetMethod.IsPublic;
                cfg.CreateMap<person,>().MaxDepth(2)
                    .ForMember(destination => destination.Age, option => option.Condition(source => source.Age < 5)) //will be mapped if source age is less than 5
                    ;
            });

            var myMapper = new Mapper(config);

             var personWithLegalInfo = myMapper.DefaultContext.Mapper.Map<LegalInformation>(person);

            Console.WriteLine(string.Format("Firstname: {0}, Lastname: {1}, Age: {2}, is legal Age: {3}", personWithLegalInfo.Firstname, personWithLegalInfo.Lastname, personWithLegalInfo.Age, personWithLegalInfo.IsLegalAge));
شیوه‌های استفاده به‌صورت زیر است:
  1. در حالت ارث‌بری: طبق روال انجام می‌شود.
             static void SimpleMapping()
            {
                var person = new Person()
                {
                    Firstname = "joe",
                    Lastname = "smith",
                    Age = 30
                };
    
                var personWithLegalInfo = person.Adapt<Person, LegalInformation>();
                Console.WriteLine(string.Format("Firstname: {0}, Lastname: {1}, Age: {2}, is legal Age: {3}", personWithLegalInfo.Firstname, personWithLegalInfo.Lastname, personWithLegalInfo.Age, personWithLegalInfo.IsLegalAge));
            }
  2. در حالت ویژگی‌های همنام:
    • در حالتی که هردو آبجکت از یک نوع کلاس باشند : برای clone کردن یک آبجکت بدون ارث‌بری از ICloneable مناسب است.
    • آبجکت با نوع کلاس‌های متفاوت با ویژگی‌های هم نام و هم نوع: جدای از نوع ویژگی، اگر کوچک و بزرگ بودن نام ویژگی را رعایت کرده باشیم، نگاشت صورت می‌گیرد. اگر نام ویژگی‌ها کاملاً مشابه بوده ولی نوع داده‌های متفاوت داشته باشند با اکسپشن مواجه می‌شویم و باید با استفاده از پیکره‌بندی مناسب از بروز خطا جلوگیری کنیم.
    • با پیکره‌بندی: AutoMapper از همان ابتدا به پیکره‌بندی احتیاج دارد که در کد زیر برخی از موارد استفاده آن را می‌بینیم. برای مثال‌های بیشتر به لینک منبع مراجعه کنید.
            static void ConfigMappingAutoMapper()
            {
                var person = new Person()
                {
                    Firstname = "joe",
                    Lastname = "smith",
                    Age = 30
                };
    
                var config = new MapperConfiguration(cfg =>
                {
                    cfg.ShouldMapProperty = info => info.GetMethod.IsPublic;
                    cfg.CreateMap<person,>().MaxDepth(2)
                        .ForMember(destination => destination.Age, option => option.Condition(source => source.Age < 5)) //will be mapped if source age is less than 5
                        ;
                });
    
                var myMapper = new Mapper(config);
    
                 var personWithLegalInfo = myMapper.DefaultContext.Mapper.Map<LegalInformation>(person);
    
                Console.WriteLine(string.Format("Firstname: {0}, Lastname: {1}, Age: {2}, is legal Age: {3}", personWithLegalInfo.Firstname, personWithLegalInfo.Lastname, personWithLegalInfo.Age, personWithLegalInfo.IsLegalAge));
            }
    

مقایسه

  1. AutoMapper برای استفاده به پیکره‌بندی پیچیده‌تری نیاز دارد.
  2. گفته‌شده که mapster تا 2.5 برابر سریع‌تر از automapper و هر Mapper شناخته‌شده دیگری است.
  3. حجم فایل mapster، 232kb و حجم فایل autoMapper، 335kb است.
  4. Automapper به‌صورت پیش‌فرض فقط ویژگی‌های پابلیک را نگاشت می‌کند و اگر بخواهیم ویژگی با سایر accessor ها را نگاشت کند، باید در پیکره‌بندی تنظیمات مناسب را انجام دهیم.
  5. در مقایسه کارایی (Performance) برای نگاشت کردن یک نوع کلاس و در یک متد، Mapster تا 44% سرعت بالاتر دارد؛ اما از CPU به مقدار بسیار کمی، بیشتر کار می‌کشد.
  6. در مقایسه کارایی برای نگاشت کردن یک نوع کلاس و در یک متد، Automapper کندتر عمل می‌کند اما لود آن بر CPU به مقدار بسیار اندک، کمتر است.

کارایی Mapster:

Mapster Performance

کارایی AutoMapper:

AutoMapper Performance

نتیجه

اگر احتیاج به نگاشت ساده و سبک دارید، بهتر است که از Mapster استفاده کنید.
اگر احتیاج به نگاشت پیچیده دارید که باید قوانین متعددی را در آن مدنظر داشته باشید، بهتر است که از AutoMapper استفاده کنید.

منابع


نام را وارد کنید
تعداد کاراکتر باقیمانده: 1000
نظر خود را وارد کنید