Başlık aslında bu içeriği tam anlamıyla anlatmıyor. Fakat böyle bir şeyi google’da arayacak olsaydım buna benzer bir şey yazardım herhalde.
Bugün değinmek istediğim konu, bana birden fazla kez sorulan bir şey aslında. Önce çözmek istediğimiz sorundan bahsedelim. İkinci olarak bunu nasıl yapacağımızı yapalım yani sorunu çözelim. Üçüncü aşamada ise de bunu diğer projelerimizde direk kullanabileceğimiz şekle çevirip dll almayı yapalım.
Sorunsal
Diyelim ki elimizde içinde property’leri olan C# class’ı var. Birisi veritabanımızdan gelen bir instance ve diğeri de kullanıcı tarafından düzenlenmiş olan instance. İstediğim şey ise kısa yoldan düzenlenmiş class’dakileri orjinal class’a aktarma. Fakat doğrudan düzenlenmişi orjinale atayamıyorum çünkü düzenlenmişte null alanlar var veya değiştirmek istemediğim alanlar var. Bunu doğrudan uzun yoldan şu şekilde yapabilirim.
Önce classım:
{
public Guid ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime BirthDate { get; set; }
public string PasswordHash { get; set; }
public DateTime RegisterDate { get; set; }
}
Uzun uzun yapacağım işlem:
User orignalUser = new User { ID = Guid.NewGuid(), BirthDate = DateTime.Now.AddYears(-25), Name = "Ahmet", Surname = "Ahmetoğlu" };
//Bu da kullanıcı tarafından değişiklik yapılmış model
User editedUser = new User { ID = orignalUser.ID, Name = "Ahmet", Surname = "Yılmaz", BirthDate = DateTime.Now.AddYears(-26) };
//editedUser'da birden fazla null alan var. ve diğerinden farklı olan alanlar da var.
//Bunu originalUser değişkenine aktarmak için şunu yapabilirim.
orignalUser.Name = editedUser.Name;
orignalUser.Surname = editedUser.Surname;
orignalUser.BirthDate = editedUser.BirthDate;
//...
//Gibi tüm değiştirmek istediğim property'lerin atamasını elle yapabilirim.
Fakat istemiş olduğum şey, editedUser’daki null olmayan alanları değiştiren ve ID gibi değişmesin istemediğim alanları değiştirmeyen bir algoritmaya ihtiyacım olacak. Şu örnekte çok büyük bir sıkıntı yok fakat çok sayıda property’den oluşan modellerimizde böyle bir yol izlemek tamamen amelelikten başka bir şey olmayacaktır.
Basit Çözüm
İlk akla gelen şey ise bir döngü; Mesela foreach… Benim User class’ımın içindeki property’leri teker teker dönüp bir instance’dan alıp diğer instance’a aktarmak istiyorum. Bunu ise System’den gelen Type class’ının bir metodu olan GetProperties() metodu ile yapacağım. kullanımı şu şekilde:
User orignalUser = new User { ID = Guid.NewGuid(), BirthDate = DateTime.Now.AddYears(-25), Name = "Ahmet", Surname = "Ahmetoğlu" };
//Bu da kullanıcı tarafından değişiklik yapılmış model
User editedUser = new User { ID = orignalUser.ID, Name = "Ahmet", Surname = "Yılmaz", BirthDate = DateTime.Now.AddYears(-26) };
// Herhangi bir kontrol olmadan şu şekilde editedUser'ı originalUser'a şu şekilde aktarabilirim:
foreach (var property in typeof(User).GetProperties())
{
property.SetValue(orignalUser, property.GetValue(editedUser));
}
Buna bir de null kontrolü yazabilirim ekleyelim:
foreach (var property in typeof(User).GetProperties())
{
var editedProp = property.GetValue(editedUser);
if ( editedProp == null) continue;
property.SetValue(orignalUser, editedProp);
}
ID gibi herhangi bir şekilde değişiklik olmasını istemediğimiz property’leri ayırt etmek için yeni bir attribute yazalım. Class’ımı Property’lerimin üzerine attribute olarak yazabilmek için Attribute class’ından türettim:
{
}
Şimdilik bunun içini boş bıraktım. User class’ıma giderek ID’nin üzerine IgnoreCopy attribute’ümü ekledim. Yeni hali aşağıdaki gibi:
{
[IgnoreCopy]
public Guid ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime BirthDate { get; set; }
public string PasswordHash { get; set; }
public DateTime RegisterDate { get; set; }
}
Şimdi foreach foreach içerisindeki null kontrolüme bir kontrol daha ekliyorum, bu attribute’ün olup olmadığı:
foreach (var property in typeof(User).GetProperties())
{
if (property.GetCustomAttributes(typeof(IgnoreCopy), false).Length > 0) continue;
var editedProp = property.GetValue(editedUser);
if (editedProp == null) continue;
property.SetValue(orignalUser, editedProp);
}
Her objeye uygun hale getirme
ModelMixer adında yeni bir ClassLibrary açtım. İçerisinde Attributes ve Processes adında 2 klasör oluşturdum. Attribute’lerimi ve işlemlerimi bu klasörlerin altında depolayacağım. Daha önce oluşturmuş olduğum IgnoreCopy adlı attribute class’ıma Attributes klasörü içerisine yer verdim.
Proccesses class’ımın içerisine Static olarak Copier adında class oluşturdum. İki farklı şekilde kullanılabilmesi için iki farklı metodu aşağıdaki gibi bu class içerisine yazdım.
{
public static T Copy< T >(T from, T to)
{
foreach (var property in typeof(T).GetProperties())
{
if (property.GetCustomAttributes(typeof(IgnoreCopy), false).Length > 0) continue;
var editedProp = property.GetValue(from);
if (editedProp == null) continue;
property.SetValue(to, property.GetValue(from));
}
return to;
}
public static void CopyFrom(this object to, object from)
{
foreach (var property in to.GetType().GetProperties())
{
if (property.GetCustomAttributes(typeof(IgnoreCopy), false).Length > 0) continue;
var editedProp = property.GetValue(from);
if (editedProp == null) continue;
property.SetValue(to, property.GetValue(from));
}
}
}
Bu şekilde yaparak, ister static class’ı referans edip tüm objelerde Extension method olarak kullanılmasını sağlayabilirim veya Copy metodum ile doğrudan copyalama işlemimi gerçekleştirebilirim. Şimdi gel gelelim bunu başka bir projede kullanmaya. ModelMixer_Debugger adında bir proje daha açtım (Console Application) ve bu Class Library projemizi Build edip, elde etmiş olduğumuz dll’i Debugger’ın referanslarına ekledik.
Model oluşturma. Elimizde zaten bir User modeli vardı. Bunu aynen kullanacağım (IgnoreCopy attribute’ü ile birlikte). Kopyalanmasını istemediğim property’lere kesinlikle IgnoreCopy attribute’ünü koyalım.
Örnek: Entity Framework Modellerinde ilişkili tablodan çekilen veriler gibi. Eğer düzenlenen verinizde o ilişki tablosundaki veriler yoksa SaveChanges yaptığınızda ilişkiyi koparmış ve veriyi silmiş olabilir!
{
User originalUser = new User { ID = Guid.NewGuid(), Name = "Ali", Surname = "Ahmet", BirthDate = DateTime.Now.AddYears(-26), RegisterDate = DateTime.Now.AddDays(-5) };
User editedUser = new User { Name = "Aliahmet", Surname = "Mehmetoğlu" };
//doğrudan bu şekilde kullanabiliriz
Copier.Copy(editedUser, originalUser);
//veya ihtiyacımız olabilme ihtimaline karşı koymuş olduğunuz casting işlemini uygulayarak da kullanabiliriz
Copier.Copy<User> (editedUser, originalUser);
//veya static class'ın namespace'ini using olarak ekledikten sonra;
originalUser.CopyFrom(editedUser);
Console.ReadLine();
}
Kullanımı oldukça kolay hale getirdik. Artık her ihtiyacımız olduğunda tekrar tekrar foreach yazıp if kontrolleri yapıp uzun uzun uğraşmayacağız. Bu en basit halidir. İstediğiniz gibi Custom Attribute’ler yazıp bu attribute’lere göre her property içni farklı davranmasını sağlayabilirsiniz. Üzerinde kullanacağımız modellere bu attribute’leri eklemek gerekiyor sadece. Onun dışında gördüğünüz gibi kullanımını en kolay hale getirdik.
Projenin örneğine buradan ulaşabilirsiniz.
Hocam eline koluna sağlık 🙂