• DİKKAT !

    Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak Dosya Yükleme tamamen ücretsizdir.

Çözüldü C# Repository Pattern Hk.

Bu konu çözüldü olarak işaretlenmiştir. Çözülmediğini düşünüyorsanız konuyu rapor edebilirsiniz.
Durum
Konu Çözümlendiği İçin Kapatılmıştır.

aeGNoR

Destek Ekibi
Katılım
10 Mar 2021
Mesajlar
887
Çözümler
116
Aldığı beğeni
1,055
Excel V
Office 2021 TR
Konu Sahibi
Merhaba,
C# .NET Framework 4.8 Windows Forms Application geliştirirken, uzun zamandır araştırdığım bütün CRUD (Create, Read, Update, Delete) operasyonlarını tek bir sınıf ile yapabilen bir sistem arıyordum. Her bir çözüm çözümü çok zor problemle geliyordu.

Her problemi çözmese de temel bütün problemleri çözen bir sistemi sonunda geliştirdim ve geliştirdiğim bu sistemi sizlere kabaca anlatmaya çalışacağım.


Öncelikle veritabanındaki bütün tablolar C# tarafında bir class olarak modellenmeli. (NTier mimaride buna Entity diyoruz.)
Genelde bütün tablolarda olma ihtimali yüksek alanlar için base classlar tanımlıyoruz.

namespaceler içindeki "MNZ.Core" kısmı benim proje adımı temsil ediyor, diğer kısımlar projedeki klasörleri temsil ediyor.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Hemen hemen her veritabanı tablosunda int türünde Id alanı olduğu için EntityBase sınıfına bir int Id alanı tanımlanıyor.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Daha sonra yine pek çok tabloda olması muhtemel Audit alanlar için de AuditEntity tablosunu tanımlıyoruz.
Audit bilgisi bize Oluşturma zamanı, güncelleme zamanı, silme zamanı, kim oluşturdu, kim güncelledi, kim sildi gibi bilgiler veriyor.

Hem Audit bilgisi hem Id bilgisini bir araya toplayan AuditEntityBase tablosunu tanımlıyoruz.


HTML:
Kod:
İçeriği görebilmek için Giriş yap ya da Üye ol.

AuditEntityBase sınıfı AuditEntity sınıfından inherit ettiği için AuditEntity alanlarını da otomatik almış oluyor. Bir class sadece tek bir sınıftan inherit edebileceği için EntityBase sınıfındaki Id alanını alamıyoruz elle public int Id tanımlıyoruz.

Sistemi bölüm bölüm anlatacağımdan temel entity sınıflarını oluşturma kısmını anlatıyorum şimdilik.
Bundan sonraki bölümde her bir entity için temel SQL sorgularını üreten sistemi anlatacağım.
 
Konu Sahibi
Sırada konu her entity için otomatik SELECT, INSERT INTO, DELETE, UPDATE methodlarını oluşturan bir sistem oluşturabilmek.

Öncelikle C# kodlama dilinde isimlendirme standartlarından bahsetmek gerekiyor. Veritabanı tablo isimleri çoğul olmak zorunda. (isimlendirme standardına göre)
Örnek olarak;
Users, Permissions gibi. Bu tabloların C# tarafındaki karşılıkları ise tekil isim olmalı. User, Permission gibi.

Bu isimlendirme standardını da ele aldığımıza göre tablo bilgilerini C# class seviyesinde tutacak ve bize sunacak bir Attribute sistemine ihtiyacımız olacak.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Burada tablo adı ve tablo içindeki birincil anahtar bilgilerini tutuyoruz. Birincil anahtar bilgisini string array olarak tutmamızın sebebi birden fazla birincil anahtar olma ihtimali olduğu için.
Bir tabloda nasıl birden fazla birincil anahtar olabilir sorusu aklınıza gelirse de çoka çok ilişkileri tanımladığımız junction tablolar dediğimiz tabloları örnek gösterebiliriz.
Users (Kullanıcılar), Permissions (Yetkiler), UserPermissions (Kullanıcı Yetkileri) Bu örnekte UserPermissions tablosunda UserId, PermissionId alanları olacağından iki alan da birincil anahtar özelliğine sahiptir.

Şimdi gelelim bu attribute nesnelerinin nasıl kullanılacağına.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

User entity nesnesinde görüldüğü gibi bir önceki konumuzda işlediğimiz EntityBase nesnesinden public int Id alanını alıyor. [Table("Users", "Id")] tanımı da bize tablo bilgilerini veriyor.


HTML:
Kod:
İçeriği görebilmek için Giriş yap ya da Üye ol.

CurrentAccount (Cari Hesap) nesnesinde de bir önceki konumuzdan AuditEntityBase nesnesini alıyor. Base classlar bize sürekli tekrar eden alanları tekrar tekrar yazmama konusunda yardımcı oluyorlar. Şuanki CurrentAccount nesnesi CreatedOn, ModifiedOn, DeletedOn, CreatedBy, ModifiedBy, DeletedBy, IsDeleted ve Id alanlarını otomatik olarak sağlamış oluyor.

Temel entity tanımlama sistemimiz bu şekilde.
Bu sistem çok daha ileri götürülebilecek bir sistem ama çok fazla uğraş gerektirdiği ve buna da vaktim olmadığı için bu kadarı ile yetiniyorum. (Normalde bu sistemde veritabanındaki alan isimleri ile C# entity sınıflarındaki alan isimleri birebir aynı olmak zorunda değil ama bunu sağlamak için çok fazla uğraşmak gerektiğinden C# entity nesnesi ile veritabanındaki tablo alanlarının aynı olması zorunlu tutuyorum. Eğer aynı olmazsa veritabanından çekilen verilerin alan isimleri ile C# entity nesnesinin alan isimleri uyuşmayacağından veriler boş ya da default değerleri ile gelecektir.)

Bir sonraki seviyede ise otomatik sorgu oluşturma sistemine değineceğiz.
 
Konu Sahibi
Gelelim her entity için sürekli SQL sorgusu (Temel sorgular için) yazmaktan kurtaran sisteme.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

QueryStorage<T> adını verdiğim sistem ile temel CRUD sorgularını otomatik oluşturuyoruz.

Tek tek anlatacak olursak. sınıfta tanımlı <T> kısmı generic sınıf olduğunu belirtiyor. T kısaltması için Type diyebiliriz. Yani içeri alacağı nesnenin tipi buna sonra geleceğiz.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

QueryStorage nesnesi ilk oluşturulurken otomatik çalışacak olan Constructor method (yapıcı method) içinde T tipi için bilgileri topluyoruz.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Bu satır typeof(T) ile T nesnesinin tipini alıyor yani entity nesnelerimizin (User, Permission, CurrentAccount gibi) tipini aldığı nesnede atanmış TableAttribute adında CustomAttribute var mı diye bakıyor. Eğer tanımlı değilse throw new Exception ile hata fırlatıyor ve sistem çalışmıyor. Yani kısaca bütün entity nesnelerinde TableAttribute olmak zorunda. (Default davranışlar eklenebilir ama bu davranışların performans optimizasyonlarını yapmak zor geldiği için böyle bir zorunluluk getirdim.)

TableAttribute bilgisi alınan entity nesnesi için bilgileri almaya devam ediyoruz.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

bu kodlarda QueryStorage içinde tanımlanan Fields'lere TableAttribute içindeki bilgileri alıyoruz.
TableName, PrimaryKeys gibi bilgiler.
Daha sonra diğer kolon bilgilerini topluyoruz AllColumns, ColumnsWithoutPrimaryKeys alanlarına. Yani Tüm kolonlar ve PrimaryKey olmayan kolonlar şeklinde 2 farklı bilgi alıyoruz.

Neden bütün kolonlara ve primary key olmayan kolonlara ayrı ayrı ihtiyacımız var diye soracak olursanız select sorguları için bütün alanlara ihtiyacımız var ama Insert sorguları için genelde Id alanları otomatik artan sayı olduğundan dolayı bu alan insert değerlerine eklenmez.

Diğer bütün alanlar ise bizim için;
"SELECT Id, UserName, FirstName, LastName .... FROM Users"
"UPDATE Users SET (UserName, FirstName, LastName....) VALUES (username, @FirstName, @LastName....) WHERE Id=@Id"
"DELETE FROM Users WHERE Id=@Id"
"SELECT Id, UserName, FirstName, LastName .... FROM Users Where Id=@Id"
gibi sorgular üretiyor.

Sorgu üretme kısmı kabaca bu şekilde. Bir sonraki aşamada Artık bağlantı yönetimi ve CRUD operasyonlarımızı yöneten Repository pattern'e giriş yapacağız.
 
Konu Sahibi
Şimdi bağlantımızı ve transaction sistemimizi yönetecek ve tekrar tekrar sıfırdan SqlConnection, OleDbConnection, SQLiteConnection, Command, DataReader, DataSet gibi nesneleri tanımlamaktan kurtaracak sistemimize.

C# da interface'ler bir sözleşme gibidir. interfaceler bir imza tanımlar ve class'lar bu interface'i implemente ederler. İmplemente eden sınıflar buradaki sözleşmeye uymak zorundadırlar. Gelelim Connector adını verdiğim bağlantı yönetim sistemine.

HTML:
Kod:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Bu arayüz bizim Connection nesnemizi, Transaction nesnemizi yönetmek için ihtiyacımız olan bir arayüz. İçerisindeki void methodlar Open, Close veritabanı bağlantısı açma kapatma, BeginTransaction Transaction başlatma, Commit ve Rollback veri işleminin başarısız ve başarısızlığına göre işlemi geri alma ya da veritabanına kaydetme şeklinde işimize yarayacak.

Gelelim bu arayüzün bir class tarafından implemente edilmesine.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

IConnector arayüzünden de farketmiş olacağınız gibi Connection ve Transaction nesneleri belirli bir bağlantı türüne değil hepsini kapsayan bir arayüze göre ayarlanmış durumda. Ben Microsoft Access ile çalıştığım için OleDbConnector adında bir connector tanımladım. Siz SqlConnector adını verip içeride SqlConnection ve SqlTransaction yöneten bir sistem tanımlayabilirsiniz.

Gelelim bu sınıfın ne yaptığına;

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Constructor methodlardan da anlaşılacağı gibi OleDbConnector parametreli ve parametresiz şekilde oluşturulabiliyor.
Yani;
IConnector connector = new OleDbConnector("Bağlantı dizesi buraya"); şeklinde tanımlayacağım gibi
IConnector connector = new OleDbConnector() olarak da tanımlayabiliyorum. (Burada bir kıstasımız var. System.Configuration referansı projeye ekli olmalı ve App.config içinde açağıdaki ayarlar tanımlanmış olmalı.
HTML:
XML:
İçeriği görebilmek için Giriş yap ya da Üye ol.
App.config içindeki DefaultConnection ayarı sayesinde her defasında connectionstring vermek zorunda kalmıyoruz. nesne oluştuğu anda kendi içinde connectionstring alıyor.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.
HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Connector nesnesinin nasıl kullanılacağı ile ufak bir bilgilendirme. Daha sonraki süreçlerde anlatım detaylandırılacak.

Bir sonraki aşamamızda Repository Pattern uygulamayı göreceğiz.
 
Konu Sahibi
Repository sınıfımızı oluştururken Connection nesnesine Extension method dediğimiz methodlar yazarak çözüm bulmaya çalışmıştım fakat zaten yapılmışı ve daha kapsamlısı Dapper adında NuGet Package halinde sunulmuş. Bu sebeple zaten yapılmış ve çalışan bir sistem varken sıfırdan yazmanın zahmetine ve zorluğuna katlanmak istemedim açıkçası. Bu sebeple repository sınıfımız için Dapper NuGet paketini indirmemiz gerekiyor.

Şimdi gelelim repository sınıfının sözleşmesine yani interface yapısına.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Bu yapımızla Add, AddReturnId (bazen eklenen nesnenin Id değerine ihtiyacımız olabiliyor.), Update, Delete, SoftDelete (Audit entity ise IsDeleted true işaretlemek için.), GetById, GetAll, Get methodlarımız var. isimlerinden de anlaşılacağı gibi veri ekleme, silme, güncelleme veri çekme işlerimize yarıyor.

Gelelim IRepository arayüzünün implemente edilmesine.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Burada repository yapımız bağlantımızı kontrol ettiğimiz IConnector nesnesine ihtiyaç duyuyor (daha önce OleDbConnector olarak implemente etmiştik.). aynı zamanda QueryStorage<T> nesnesine ihtiyaç duyuyor.

Peki bu DapperOleDbRepository<T> ve QueryStorage<T> T tipinin ne olduğunu nasıl biliyor sorusuna gelecek olursak, işin güzelliği de tam olarak burada. Repository ve QueryStorage hangi entity nesnesinin geldiğini bilmiyor, onlar sadece sözleşmeye uyuyorlar. Biz ona User, Permission, CurrentAccount her ne olursa olsun, ne gönderirsek onunla işlem yapıyorlar.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

bu alanlar Connection, Transaction, QueryStorage nesnelerini içeri almamıza yarıyorlar.

Add methodu üzerinden gidecek olursak;


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Bu methodda;
HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.
bize vermiş olduğumuz T türünün insert sorgusunu otomatik olarak veriyor.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.
Burası işin bir diğer büyülü kısmı. Daha önce bağlantı açıyor, command nesnesi açıyor içine tek tek parametre gönderiyorduk.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.
AddWithValue satırları ile tablodaki her bir alan için parametre geçirmek zorunda kalıyorduk. Fakat Dapper içindeki DynamicParameters nesnesi sayesinde bu iş o kadar da zor değil.
DynamicParameters nesnesi içine benzer şekilde parametreler alabilen tek bir nesne şeklinde karşımıza çıkıyor.
DynamicParameters parameters = new DynamicParameters();
parameters.Add("username", "Kullanıcı adı değeri");
bu şekilde değerler eklenebiliyor.

Fakat yazdığım Helper sınıfı ile bunu da tek satırda her nesne için halledebiliyoruz.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

DynamicParametersHelper.GetParameters methodu içine sql sorgusu ve entity nesnesi alıyor. Regular Expression ile @ParamName şeklinde yazılmış parametreleri buluyor ve gönderilen entity nesnesindeki alan isimleri ile eşleştiriyor. Eşleştirdiği her bir alanı DynamicParameters nesnesine parametre olarak ekliyor.

Böylece Connection'ı execute edebilmek için ihtiyacımız olan iki kritik değişken elimizin altında olmuş oluyor. sql sorgusu ve içeri göndereceğimiz parametreler.
HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.
Son olarak bu satır ile herhangi bir entity nesnesini veritabanına eklemiş oluyorz.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Bu method içindeki Query methodu ise veritabanından SELECT sorgusu ile çekilen verileri T tipindeki entity nesnesinin alanları ile eşleştirerek bize List<T> türünde verileri getiriyor.

Kullanım örneklerine bir sonraki mesajımda değineceğim.
Ayrıca proje dosyasını da sizlerle paylaşmış olacağım.
 
Konu Sahibi
Örnek bir User nesnesi ile Add operasyonunu test ediyoruz. PasswordHash ve PasswordSalt alanları için aşağıdaki helper sınıfa ihtiyacımız var.


HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.


Örnek User sınıfımız.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.


QueryStorage<T> nesnesinden inherit eden UserQueryStorage sınıfımız.

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

DapperOleDbRepository nesnesinden inherit eden UserRepository sınıfımız;

HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

ConnectionString yönetmesi için App.config ayarlarımız.
HTML:
XML:
İçeriği görebilmek için Giriş yap ya da Üye ol.


Son olarak Console uygulamasında AddTest ile ekleme işlemini test ettiğimiz methodumuz.
HTML:
C#:
İçeriği görebilmek için Giriş yap ya da Üye ol.

Bu şekilde sorgu yazmaktan, command.Parameters.AddWithValue ile bir sürü parametre ekleyeme çalışmaktan kurtuluyoruz.
Veritabanımıza yeni bir tablo eklendiğinde hemen veritabanı karşılığı olan Entity nesnesi, Repository sınıfı, ve QueryStorage sınıfı oluşturuyoruz ve bütün CRUD operasyonlarımız hazır hale geliyor.
 
Durum
Konu Çözümlendiği İçin Kapatılmıştır.
Geri
Üst