Entity Üzerinde Generic Attribute Tanımlayarak Repository Katmanında Özelleştirme

Selamlar,

Bugün DBFirst yöntemi ile oluşturulmuş Entityleri, C#11 ve .Net 7.0 ile gelen Generic Attribute ile nasıl özelleştirebileceğimiz hep beraber inceleyeceğiz. Bu özelleştirmeye göre, Repository katmanında farklı aksiyonlar alacağız.

Seattle Bilgisayar Müzersi®

Seattle 1980ler’den Bilgisayar Müzesi

Öncelikle “GenericAttribute” adında bir “Console Application” oluşturulur.

DAL

DAL: Solution altında, DAL adında class library oluşturulup, Nugetten aşağıdaki kütüphaneler indirilir.

Bu örnekde, Northwind database’i üzerinden DBFirst yapılmıştır. Aşağıdaki “scaffold” komutu ile NorthwindContext ve Entityler oluşturulmuştur.

DAL Projesinin örnek görünümü

Öncelikle gelin tüm entitylerin türeyeceği BaseEntity oluşturalım.

BaseEntity: Tüm Entityler bu sınıftan türetilecektir. Bu şekilde tüm entitylere ortak propertyler atanabilecektir. Ayrıca Reporsitory katmanına kısıtlama olarak, “BaseEntitiy“‘den türemeyen bir sınıf implemente edilemiyecektir.

PartialEntites(1): Entity sınıflar, otomatik oluşturulurken partial keyword’ü ile oluşturulmaktadırlar. Böylece partial başka bir lokasyonda, yine aynı sınıfın kodlarını devam ettirebilmektedir. Yani derleme zamanında, alttaki partial DbUser sınıfı ile Entity DBUser sınıfı birleştirilecektir. Burada partial kullanılmasındaki amaç, DB’de bir değişiklik olduğu zaman, yukarıdaki “scaffold” komutu tekrar çalıştırılır ve “Entities” folder’ı altında var olan kodlar ezilir. İşte bu durumun olmaması için, ayrı bir sınıf içine kodlarımızı geliştiriyoruz.

Şimdi sıra geldi Users Entity üzerindeki “PasswordHash“, “Gsm” ve “Email” kolonlarına, Custom Attributelar eklemeye. Daha sonra bu işaretleyicilere göre, Repository katmanında property bazında farklı aksiyonlar alınacaktır.

Öncelikle Attribute, Entity’ye ait farklı bir Partial classda, doğrudan property üzerine atanamaz. Orijinal Entity sınıfında aynı atama yapılmak istenir ise, demin de yukarıda bahsedildiği gibi var olan kodlar ezilebilir. Eskiden, herbir kolon için, farklı bir attribute yapmak gerekiyordu. Artık Generic bir Attribute yapıp, bunu tüm kolonlar için kullanabiliyoruz.

GenericEntityAttribute: 

Aşağıda görüldüğü gibi 2 parametre alan ve 2. parametresi null olarak atanamasa bile, Constructor’ında nullable olarak tanımlanan bir Attribute görülmektedir. Bu Attribut’ün kullanıldığı bazı durumlarda 1 parametre, bazı durumlarda da 2 paramtereye ihtiyaç duyulmaktadır. Bu nedenle, 2. parametre nullable olarak atanmak istenmektedir. Aslında “T” paramteresi, Enum Attribute tipine, “T2” Parametresi de Length değerine karşılık gelmektedir.

Not: Generic Attributelarda <T, T2>, parametreleri nullable olarak “<T, T2?>” gibi henüz set edilememektedir. Ama onun da eli kulağındadır :) 

PartialEntites(2): Şimdi nasıl olacak da, Users tablosundaki “PasswordHash, Gsm ve Email” kolonlarına bu GenericAttribute’u atanacaktır.

Öncelikle “scaffold” komutu ile önceden oluşturulmuş bir partial class’a, ilgili attributelar başka bir partial sınıf içinde doğrudan atanamaz. Bunun için bir “MetadataType” ve ona ait bir “Class“‘ın tanımlanması gerekmektedir.

Not: Eğer, Entities folder’ı altında otomatik oluşturulmuş Users sınıfı üzerinde, ilgili attribute doğrudan eklenir ise, bir sonraki “scaffold” komutu çalıştırıldığında, yazılan kod üzerine ezileceği için silinecektir.

  • [GenericEntityAttribute<AttributeType, int>(AttributeType.CryptoData, 5)]“:  “CryptoData” attribute tipi aşağıda tanımlanmıştır. Parametre olarak integer bir sayı almaktadır. Bu da şifrelenecek olan abağı, temsil etmektedir. Tanımlandığı property’i çift yönlü şifreleyecektir.
  • [GenericEntityAttribute<AttributeType, string>(AttributeType.HashData, null)]“: HashData attribute tanımlandığı kolonu, tek yönlü geri dönülemez olarak şifreleyecektir. Herhangi bir parametre almamaktadır. Bu nedenle string tanımlanan T2 parametresi null olarak atanmıştır. Maalesef GenericAttributelar’da, <AttributeType, string?> şeklinde nullable parameter tanımlaması yoktur.
  • [GenericEntityAttribute<AttributeType, int>(AttributeType.NumberValidateData, 6)]“: Üçüncü ve son Attribute’ümüz, “NumberValidateData” attribute’ü dür. Amaç, girilen sayının tam olarak hane sayısının belirlenmesidir. Anlaşıldığı üzere 2. parametre, girilen sayının hanesini göstermektedir.
  • [MetadataType(typeof(UserMetaData))]“: Son olarak, “UserMetaData” sınıfı, DBUser Entity üzerine MetadataType olarak atanmıştır. Eşleşmenin başarılı olması için, tanımlanan kolon isimlerinin iki sınıf içinde aynı olması gerekmektedir.

Enum: Proje içinde ihtiyaç duyulabilecek Attribute Tipleri, burada tanımlanmıştır.

Repository

Şimdi gelin Repository katmanını yazalım. Öncelikle Solution altında, Repository adında yeni bir class library oluşturup, kütüphane olarak Projects/DAL’a ekleyelim.

Repository/IRepository.cs: Örnek amaçlı, “Insert(T)” ve “Insert(IEnumerable<T>)” methodları tanımlanmıştır. Amaç, işaretlenmiş propertylerde araya girip, ilgili operasyonların otomatik bir şekilde gerçekleşmesini sağlamaktır. “isEncrypt” parametresi, performans amaçlı her entity için değil de, User tarafından “GenericEntityAttribute” atanmış Entitylerin propertylerinin gezilmesi için tanımlanmıştır. Çünkü Reflection ile kaydedilecek Entity’nin propertylerini gezip, tanımlı attributeları bulmak bir maliyettir.

Repository/GeneralRepository: Solution içindeki tüm DB işlemlerinin yapıldığı yer, burasıdır. Amaç DB operasyonları için alınan Entitylerin tek bir yerden yönetilerek, tanımlanan Attributelara göre middlewarede araya girilip, dataya istenen müdahalenin yapılabilmesidir. Böylece yeni bir Entity eklendiğinde, hiçbir kod değişikliğine gidilmeyecek, hatta var olan Entitylere yeni Attributeler ile farklı operasyon eklenmek istendiğinde de, kodun sadece tekbir yerinde değişiklik yapılması yeterli olacaktır.

  • GeneralRepository<T> sınıfı IRepository’den türetilmiş, using ile Scoped olarak kullanılabilmesi için bir de IDisposible interfaceinden kalıtım alınmıştır. “T” tipinin, yani tanımlanacak Entity’nin mutlaka “BaseEntity”‘den türetilmesine zorlanılmıştır. Böylece Repository katmanına, yanlış tipte bir Entitiy’nin gelmesi engellenmiştir. Son olarak, “DBContext” ve işlem yapılacak “Entity”, private olarak tanımlanmıştır.

  • Insert(T entity)” ve “Insert(IEnumerable<T>)” methodları IRepository interfaceinden kalıtım ile gelmektedir. “_entites” değişkeni işlem yapılacak “<T> Entity“‘ye göre DBContext’den Set()’lenerek alınır. “isEncrypt” parametresine, default olarak false değeri atanmıştır. Eğer ilgili Entity’de tanımlanmış Custom bir Attribute var ise, bu değişken “true” atanmalıdır. Amaç makalenin öncesinde de bahsedilen performans konusudur. “DetachedAttributeEntityFields()” makalenin devamında anlatılacak olan, atanan GenericEntityAttributelere göre, propertylerin değerlerine MiddleWare’de müdahale edip değiştiren methoddur. Değişen entity, _entities’e eklenip, DbContext “Save ()” edilir. Bu sırada, BaseEntity’den gelen ve tüm entityler için ortak olarak tanımlanan “UsedTime” property’si, güncel zaman ile setlenir. Diğer “Insert(IEnumrable<T>)” methodu için bir implementasyon bu makale için yazılmamıştır.

  • DetachedAttributeEntityFields() methodunda, parametre olarak işlem yapılacak Entity ve ilgili DBContext alınır.

  • metadaTypes” değişkenine, Entity üstünde tanımlı tüm “MetadataType” attributeleri çekilir ve loop içinde gezilir. Bizim Users Entity örneğinde, 1 MetadataType’ımız bulunmaktadır(“UserMetaData“).
  • properties” System.Reflection ile MetadataType class’ında tanımlı tüm tüm propertyler çekilir ve loop içinde gezilir. Bizim “UserMetaData” sınıfında, toplam 3 Property tanımlanmıştır(“Email, PasswordHash, Gsm“).

  • *if (Attribute.IsDefined(pi, typeof(DAL.GenericEntityAttribute<AttributeType, int>)))“: Bu kısma lütfen dikkat ediniz. UserMetaData sınıfında tanımlanan “GenericAttributelerden <T, T2>” kullanılan parametre tiplerine göre, yani işaretleyicilere göre yakalanan Attribute’un tipi belirlenmeye çalışılmaktadır. Örneğin “<AttributeType, int>” işaretleyicine uygun, “CryptoData” ya da “NumberValidateData” attributeları uygundur.

  • “prm” : Attribute içinde tanımlı bir parametre var ise, bu değer buradan çekilir. Örneğin => “(AttributeType.CryptoData, 5)“‘de 5 parametresi atanmıştır. Burada “5”, örneğin şifreleme için hangi abağın kullanılacağını göstermektedir.
  • if (type == AttributeType.CryptoData)” : Yakalanan Attribute’ın yani işaretleyicinin hangisi olduğu, Parametre olarak atanan “AttributeType” enumuna göre belirlenmektedir.
    • $”Encrypted[{prm}]_“: Eğer CryptoData ise, gerçek bir şifreleme yapılmamış ve örnek amaçlı göstermelik yakalanan property değerinin başına, “Encrypted[5]” değeri eklenmiştir.
  • else if (type == AttributeType.NumberValidateData)“: Eğer yakalanan Attribute “NumberValidateData” ise, property değerinin toplam uzunluğu alınır ve olması gereken uzunluk da “prm” değişkeni ile atanan parametreden alınarak, eksik kalan basamak değerleri, ilgili property value’u ya, “0” eklenerek tamamlanır. Örnek : “Paramtere olarak 6 sayısı girilmiş ise 3398 => 339800’e çevrilir.

  • Eğer yakalanan Attribute <T, T2> => <AttributeType, string> imzalarına uyuyor ve AttributeType’ı = “HasData” ise, yakalanan property değerinin başına, örnek amaçlı “HashData_” keyword’ü eklenecektir.
  • Son olarak değerleri Middleware’de tanımlı Attiributelara göre değişen Entityler, kaydedilmek üzere geriye dönülürler.

  • “Dispose()” Methodu GeneralRepository: IDisposable interface’inden türetildiği için implemente edilmiştir. Amaç GeneralRepository’i Scoped olarak “using()” içinde kullanıldıktan sonra, GC tarafından memory’den kaldırılmasını sağlamaktır.

Repository/GeneralRepository:

GenericAttribute

Şimdi sıra geldi GenericAttribute adında bir Console Application yaratıp, yeni girilen bir User’ı, DB’ye kaydetmeye.

GenericAttribute/Program.cs: Aşağıda görüldüğü gibi yeni bir User kaydı oluşturulmuş ve daha sonra Repository katmanındaki “Insert()” methodu çağrılarak yeni User, SqlDB’ye kaydedilmiştir. Repository katmanında, User Entity’nin tüm propertyleri gezilmiş ve tanımlı attributeların bulunduğu propertyler, Middlewarede ilgili işaretleme tipine göre, değiştirilip geri dönülmüştür.

Kaydedilen User kaydı, aşağıda görüldüğü gibidir.

  • PasswordHash : “HashData” attribute’ü ile işaretlenmiş ve Middleware’de başına “Hashdata_” keyword’ü getirilmiştir.
  • Email: “CryptoData” attribute’ü ile işaretlenmiş ve Middleware’de başına “Encrypted[5]” keyword’ü getirilmiştir.
  • Gsm: “NumberValidateData” attribute’ü ile işaretlenmiş ve Middleware’de en az “6” hane olması şeklinde bir parametre ataması yapıldığı için “3334” string’inin sonuna “00” getirilerek, uzunluk 6’ya tamamlanmıştır. Son durum “333400” şeklindedir.

Generic Attibutelar, .Net 7.0 ile hayatımıza girmiş olan yeniliklerden sadece 1 tanesidir. Bu makalede, yeni bir teknolojinin kodlarından ziyade, nerde ve nasıl kullanılabileceğinin öğrenmenin, onun nasıl yazıldığını öğrenmekten çok daha önemli olduğunu göstermek istedim. Repository Pattern, güncel iş hayatında ilk yazımı uzun süren ama sonrasında birçok işi kolaylaştıran bir Design Patterndir. Kod okunaklığı ve yeni geliştirmelerin kolaylıkla yapılabilmesi, reflection ile az da olsa yaşanan performans kaybının göz ardı edilmesini sağlamaktadır. Generic Attribute sayesinde, herbir bussines için tanımlanması gereken Attribute sayısı teke düşürülmekte, bu sayede kod okunaklığı ve yönetimi kolaylaştırılmaktadır.

Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Github Source: https://github.com/borakasmer/GenericAttribute

 

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. Güzel yazılar yazıyorsunuz ama bence biraz basitleştirerek yazın.
    Entity sınıflar, otomatik oluşturulurken partial keyword’ü ile oluşturulmaktadırlar. Böylece partial başka bir lokasyonda, yine aynı sınıfın kodlarını devam ettirebilmektedir. Yani derleme zamanında, alttaki partial DbUser sınıfı ile Entity DBUser sınıfı birleştirilecektir.
    Eminim çoğu insan alttaki partial DbUser sınıfı ile Entity DBUser sınıfı birleştirilecektir.anlamamıştır.
    Yazının bir çoğu böyle….Yani kodlar vs güzel de naçizane öneri tekrar okuyun ve hiç bilmeyen acaba analr mı deyin.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.