1 Ağustos 2013 Perşembe

Kendi AutoMapper'ımızı Yapalım


Merhaba Arkadaşlar,

Veri merkezli uygulamalarda, katmanlar arasında veri alışverişi yarparken kullanıcı katmana veriler çoğu kez aracı tipler(DataTransferObject) vasıtasıyla taşınır. Özellikle veri katmanında orm teknolojilerinin kullanıldığı projelerde aracı servis katmanında, transfer objesi(dto) ve veritabanı objesi(entity) arasında kolonsal(properties) eşleştirmeyi tek tek yapmak yazılımcı için sıkıcı bir süreç. Konuyu koda dökerek somutlaştırılam:
// Entity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
    public int Salary { get; set; }
    public bool IsActive { get; set; }
}

// Transfer Object
internal class CustomerDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

internal class Program
{
    private static void Main(string[] args)
    {
        // kaynak nesne, gerçek senaryolarda muhtemelen
        // sorgu ile veritabanı üzerinden elde edilir
        var customer = new Customer
            {
                Id = 1,
                Name = "aa",
                Surname = "bb",
                Age = 22,
                IsActive = true,
                Salary = 3000
            };

 // Entity üzerinden taşıyıcı nesne oluşturulur
        var customerDTO = new CustomerDto
            {
  //kolonsal olarak tek tek uye degiskenler atanır
                Id = customer.Id, 
                Name = customer.Name,
                IsActive = customer.IsActive
            };
    }
}
Görüldüğü üzere varlık(entity) tipinden taşıyıcı tipi elde etmek için üye değişken bazında tek tek atama yaptık. Eşleme(mapping) işlemini bizim için yapan AutoMapper adlı açık kaynak bir proje mevcut, nuget konsoluna "Install-Package automapper" komutunu girerek kütüphaneyi uygulamaya dahil edelim. Daha sonra aşağıdaki şekilde kullanıp sıkıcı eşleme işleminden kurtulalım ve de kodumuz daha yalın hale gelsin:
Görüldüğü üzere AutoMapper kütüphanesi 2 satır kodla bizim için eşleme işlemini halletti. Tabii bu işlemi yapmak için yansıma(reflection) kullanıdığı için peformans düşüşü söz konusu, özellikle işlem büyük koleksiyonlar üzerinden yapılıyorsa; bu senaryoya dair sentetik test sonuçları için güzel bir anlatım linkte mevcut: http://www.bayramucuncu.com/mapping-islemleri-ve-automapper-performans-testleri/
"Hazır kütüphanenin kodları açık bu işlemi nasıl yapıyor?" diye merak edip projenin kodlarını inceledim desem yalan olur :] Yerine kendimce "manuel" bir autoMapper yazdım:
public class QMapper
    {
        public static object CloneClassicVersion(Customer source, Type targetType)
        {
// ihtiyac duyulan tipe ait nesne runtime'da örneklenir
            var result = Activator.CreateInstance(targetType);
// .net reflection api kullanılırak kaynak ve hedef tipin üye degiskenleri elde edilir
            var sourceProperties = source.GetType().GetProperties();
            var targetProperties = targetType.GetProperties();

// hedef tipin uye degiskenleri uzerinden donguye girlir
            foreach (var targetProperty in targetProperties)
            {
                // linq kullanıp ikinci donguye girilmeyebilinirdi, okunabilirlik ve basitlik
                // adına böyle bıraktım, linq ifade: 
                //var currentProperty = sourceProperties.First(a => a.Name == targetProperty.Name);
                foreach (var sourceProperty in sourceProperties)
                {
// eslesen uye degiskenler saptanır, kaynaktan hedefe dogru deger atanır
                    if (sourceProperty.Name == targetProperty.Name)
                    {
                        targetProperty.SetValue(result, sourceProperty.GetValue(source));
                        break;
                    }
                }
            }

            return result;

        }

        public static TTarget CloneGenericVersion<TTarget>(object source)
        {
            var result = (TTarget)Activator.CreateInstance(typeof(TTarget));

            var sourceProperties = source.GetType().GetProperties();
            var targetProperties = typeof(TTarget).GetProperties();

            foreach (var targetProperty in targetProperties)
            {
                foreach (var sourceProperty in sourceProperties)
                {
                    if (sourceProperty.Name == targetProperty.Name)
                    {
                        targetProperty.SetValue(result, sourceProperty.GetValue(source));
                        break;
                    }
                }
            }

            return result;

        }
    }
Aynı örneği kendi yazdığımız mapper(QMapper) ile çalıştıralım:
Taaadaam! :] Görüldüğü üzere eşleme işlemi başarılı üstelik kullanım için AutoMapper'daki gibi "createMap" gibi fazladan bir hazırlık metodu çağrımında bulunmadık, yine map'leme metodu çağrımı daha sade oldu :] Şaka bir yana AutoMapper'ın createMap metodu farklı durumları(kaynak ve hedef ile eşlenmesi istenen üye değişkenlerinin isimlerinin farklı olması vs.) ele almak için kullanılır. Kullanım yanında performans da önemli bir kriter; hangi yöntemin daha performanslı çalıştığını söylemek yerine testi deneyerek görmeniz adına size bırakıyorum.

Umarım faydalı blog girdisi olmuştur, tarzsal kodlamalar :]

Hiç yorum yok:

Yorum Gönder