EF Core’da Hayatımı Kolaylaştıran Beş Özellik

Selamlar,

Bu makalede Global Filitreleme, Log tutma, Tür Dönüşümü, Shadow Property, Interceptor gibi özellikleri EF üzerinde nasıl çözebileceğimizi hep beraber tartışacağız.

Öncelikle yeni bir WebApi .Net 9 projesini aşağıdaki gibi yaratılır.

Öncelikle DAL adında yeni bir ClassLibrary projesi bu solution’a eklenir.

EF Tool Scaffold ile Northwind tablosunun pocoları ve DBContext’ini DBFirst yolu ile oluşturacağız.

DAL Projesine, aşağıdaki Kütüphaneler sıra ile eklenir:

Öncelikle çalışacağımız Database, Northwind database’i olacaktır. Buradan indirebilirsiniz.

Son olarak Northwind Database’ine aşağıdaki Users Tablosunu ekliyoruz.

Sonraki adım olarak Terminalden => cd DAL komutu ile dal projesi altında aşağıdaki komut çalıştırılır. Böylece ilgili Entityler ve DBContext’in oluşması sağlanır.

t

Not: Eğer “scaffold” komutunuz, bilgisayarınızda yüklü değil ise,  EntityFrameworkCore.Tools’u aşağıdaki komut ile Global olarak yüklemeniz gerekmektedir.

Şimdi gelin, Entity Framework içinde size hayatı kolaylaştıracak ilk özelliğe :

1-)Global Query Filters:

Sürekli tekrarlanan koşulların, tanımlı her LinqQuery’ye manuel eklenmesi istenmediği durumlarda, örneğin “IsDeleted” =false kolonu gibi koşulların tek bir yerden tanımlanmasına imkan verir. Aşağıdaki örnekte, “Users” tablosu içinden, sadece silinmemiş olan kayıtların getirilmesi, her Linq Query için global olarak sağlamıştır.

DAL/Entities/DbContext/NorthwindContext:

Gelin Öncelikle bir UserController yaratalım ve DAL projemizdeki “NorthwindContex”‘i projemize Dependency Injection ile ekleyelim.

Program.cs:

DefaultConnect’ın appsettings.json’da aşağıdaki gibi tanımlanır:

Users Controller: Aşağıda görüldüğü gibi, Userlar için hiçbir koşul yazılmamasına rağmen, sadece silinmemiş kayıtlar gelmektedir.

DB Üzerinde Bazı Userların Silinmesi:

Sonuç Ekranı: Sadece IsDeleted = False yani silinmemiş kayıtlar, aşağıda görüldüğü gibi sorgu sonucu gelmiştir.

Ayrıca Global Filter sadece bir Entity için değil de, “IsDeleted” property’si olan tüm Entityler için de yapılmak istenir ise, aşağıdaki gibi bir kodun yazılması gerekmektedir. Tek tek tüm entityler gezilmekde ve “isDeletedProperty” propertysi var ise, false değeri atanıp koşula eklenmektedir.

DAL/Entities/DbContext/NorthwindContext:

2-) Derlenmiş Sorgular: Sorguları Tekrardan Kullanarak Sorgu Performansını Arttırma

Diyelim ki, sadece “productID” parametresi farklılaşarak çektiğimiz bir endpoint olsun. Buna benzer binlerce aramanın olduğu bir eko sistemde, performansı arttırmanın bir yolu var “EF.CompiledQuery()” EF Core genellikle her LINQ sorgusunu her kullanıldığında onu derler. Bu da yüksek trafikte, ek bir yüke sebebiyet verir. EF.CompiledQuery() methodu ile, önceden derlenmiş sorgular ile performansı artırmak için sorgunun önceden derlenmiş bir sürümünü yeniden çalıştırabiliriz. Büylece  büyük bir performans kazanımı sağlarız.

UserController.cs:

Sonuç Ekranı:

3-) Gizli Propertyler

Track edilen Audit Data içerisinde görülmesi istenmeyen ama DB’ye arkada kaydedilen propertyler için kullanılan bir özelliktir. Örneğin “UpdatedDate” ya da “CreatedBy” gibi. Aşağıdaki örnekte, Orders tablosundaki OrderDate arkada her zaman tutulacak, değişim track edilecek ve DB’ye kaydedilecek ama Order model içerisinde gözükmeyecektir. Böylece, daha okunabilir ve yalın kodlar ile çalışılırken bir yandan da gizli kolonlar dahil değişikliğin fark edilip DB’ye arkada yazıldığı bir yapı ile çalışılmış olacaktır.

Image Source: https://www.entityframeworktutorial.net/efcore/shadow-property.aspx

DAL/Entities/DbContext/NorthwindContext: Aşağıdaki örnekte scaffold ile oluşturulan “Orders” Entitysinden “OrderDate” alanı kaldırılarak, aşağıdaki satırlar eklenmiştir.

Order Tablosundan son 3 kaydı çeken endpoint.

UserController.cs:

Sonuç Ekranı: Aşağıda görüldüğü gibi OrderDate field’ı gözükmemektedir.

Ayrıca OrderDate Entity üstünde görülmeden kaydedilmek istenir ise, aşağıdaki gibi bir yol izlenebilir.

UserController.cs:

Yukarıda görüldüğü gibi, “Property(“OrderDate”)” ile alınıp set edilen “Order” Entity model geri dönüldüğünde, güncel “OrderDate” görülmemektedir. Ama aşağıda görüldüğü gibi,, database üzerinden bir sorgu çekildiğinde, ilgili “OrderDate” property’sinin güncellendiği görülmektedir.

Yukaridaki kod, hali ile uzun. İlgili propertyler gözükmesin diye sanki daha fazla kod yazıyoruz :) Bu işi, DB katmanında global hale getirilir ise, kodlar çok daha yalın hale gelecektir.

DAL/Entities/DbContext/NorthwindContext: Aşağıda görüldüğü gibi DBContext “SaveChanges()” methodu override edilmiştir. Global olarak tüm Save() işlemlerinde, ilgili Entity’nin varsa “OrderDate” field’ı bulunur ve güncelleme ya da kaydetme işlemlerinden o anki zaman değeri default olarak atanır.

Kullanim Şekli: Gelin belirtilen ID’deki bir Order’ı güncelleyelim.

Servisten Dönen Sonuç: Görüldüğü üzere OrderDate alanı yine yoktur. Yani Entityde gözükmemekte ama trace edilerek, duruma göre DB ile maplenmektedir.

Sql Sorgu Sonucu: Aşağıda görüldüğü gibi OrderDate alanı Entity’de gözükmemesine karşın güncellenmiştir.

4-) SQL Sorguları Arasına Interceptor Koyma:

EF Core’un veritabanına gönderdiği select SQL sorgularının sonuna, o anki zamanı gösteren bir kolon eklemek ister miydiniz? Bunu loglamak, debug etmek ya da belki belirli tipde queryleri çalıştırmadan önce değiştirmek için kullanmak isteyebilirsiniz ?

DAL/Entities/DbContext/NorthwindContext/AddTimeCommandInterceptor: Aşağıda görüldüğü gibi “DbCommandInterceptor” override edilerek “ReaderExecuting()” methodu ezilmiştir. DBCommandInterceptor’un araya girdiği farklı zaman ve query tipleri vardır. Mesela bu örnekteki “ReaderExecuting”, birden fazla satır döndüren sorgularda komut yürütmeden önce çağrılır.

  • ReaderExecuting: Select sorgularında komut yürütmeden önce çağrılır.
  • ReaderExecuted: Select sorgularında komut yürüttükden sonra çağrılır.
  • NonQueryExecuting: Insert, Update, Delete gibi işlemlerde, komut yürütmeden çağrılır. CRUD işlemi iptal ya da üzerinde değişiklik yapabilirsiniz.
  • NonQueryExecuted: Insert, Update, Delete gibi işlemlerde, komut yürütmeden sonra çağrılır. Loglama için kullanabilirsiniz.
  • ScalarExecuting: Veritabanından tek bir değer döndüren sorgularda yürütülmeden önce çağrılır.(COUNT, MAX, MIN gibi)
  • ScalarExecuted: Veritabanından tek bir değer döndüren sorgularda yürütülmeden sonra çağrılır.(COUNT, MAX, MIN gibi)

Aşağıdaki örnekte Select sorgusu çağrılmadan önce (ReaderExecuting),  eğer sorgu “SELECT” ile başlıyor ise “ModifyCommandText()” methodu çağrılır ve sorguda “FROM” keywordu bulunup bu keywordden önce “, GETDATE() AS CurrentTime”  string keywordü başına konarak sorgu değiştirilmiş olunur.

Aşağıdaki endpoint çağrıldığında, Order tablosundan çekilen kolonların sonuna “From”‘dan önce “GETDATE() AS CurrentTime“‘ın geldiği, Output windowda görülür.

localhost:44330/user/GetTop3Orders

Ayrıca aynı koşulun makalenin en başında tanımlanan GetUserList() endpoint’i için de çalışması istenir ise, aşağıdaki “ReaderExecutingAsync()” methodunun “AddTimeCommandInterceptor” sınıfına eklenmesi gerekmektedir. Tanımlanan DbCommandInterceptor interceptor’un, Asenkron methodları da bulunmaktadır.

https://localhost:44330/User

DAL/Entities/DbContext/NorthwindContext/AddTimeCommandInterceptor:

Bu maddeden de anlaşılıcağı üzere, Entity Framework üzerinden yapılan her işlemden önce veya sonra her türlü müdahaleyi yapabiliriz. Bu da bize DB işlemlerinde büyük bir esneklik sağlar.

5-) DB Tarafında Değer Değiştirme => Enum’ı String’e Çevirme:

Genelde Enum alanların DB karşılığı, byte bir sayıya karşılık gelmektedir. Bu da DB tarafında kod okunaklığını, kanımca zora sokmaktadır. Ben bu nedenle kodda Enum olarak atanmış fieldlarn, DB karşılığının string olmasını tercih etmekteyim. İşte bu maddede tüm çalışmalarımız, bu amaca yönelik olacaktır.

1-) Öncelikle kod okunaklığını arttırmak için, Product Tablosuna “CategoryName” kolonunu aşağıdaki gibi eklenir.

2-) Aşağıda görüldüğü gibi CategoryName alanına, tanımlanan ProductCategoryEnum değeri atanacaktır. Bu hali hazırda olan “Categories” tablosundaki kayıtlara, denk gelmektedir. Burada amaç Products tablosuna bakılırken, ilgili product’ın hangi Categorye ait olduğunun kolayca anlaşılmasıdır.

DAL/Enums:


3-) Ayrıca Products Entity Sınıfına, aşağıdaki gibi “ProductCategoryEnum?” tipinde “CategoryName” propertysi eklenmiştir. Amaç başta da bahsedildiği gibi, kod okunaklığını arttırmaktır.
DAL/Entities/Products:

4-) Son olarak DAL/Entities/DBContext/NorthwindContext sınıfında Products Entitysine, aşağıdaki MaxLength kısıtının eklenmesi gerekmektedir.

5-) Tüm hazılıklar bittiğine göre aşağıda görüldüğü gibi, Products Entity sınıfının CategoryName field’ı integer değil stringdir. Bu özellik, DBContext’in OnModelCreating() methodunda aşağıdaki gibi tanımlanmıştır.

DAL/Entities/DbContext/NorthwindContext:

Örnek Kullanım Şekli: Aşağıda görüldüğü gibi “UpdateProducts()” methodunda, gelinen bir productID ile seçilen product’ın “CategoryName” alanı ProductCategory enum ile doldurulur ve DB’de bu enuma karşılık gelen integer bir değer ile değil string bir değer ile dolması beklenir.

UserController.cs:

Sonuç Ekranı: Aşağıda görüldüğü gibi  1,2,3 ID li Productların\ CategoryName alanı Enum bir değer ile güncellenmiştir. Örneğin services Result sonucunda “CategoryName :2” olarak gözükse de, ilgili kolonun(CategoryName) DB karşılığı stringdir.

select top 3 * from [dbo].[Products]

Sonuç:

Entity Framework Core, öyle basit bir ORM toolu değildir. İsminin içinde Framework geçmesini çok haklı bir nedeni vardır. Sorgularınızın veya DB üzerinde yaptığınız tüm CRUD işlemlerin bir customize’ı yani, kendinize göre özelleştirebileceğiniz bir yöntemi vardır. Yukarıda bahsedilen bir kaç pratik özellikle bile kod tekrarını azaltıp, okunaklığı arttırıp, DB’den data çekme hızını ve performansı arttırmaktadır. Yazılan Global Filter işlemler, Interceptor gibi yapılar kodun, bakım, istendiğinde yeni özelliklerin hızlıca eklenmesi ve test maliyetini minimuma indirecektir.

Entity Framework 9.0 ile performance büyük oranda artmış ve her yeni versiyon ile de artmaya devam edecektir. Bu nedenle kanımca, .Net environmentında en günceli takip etmek elzemdir. Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Github: https://github.com/borakasmer/EntityTop5FeaturesBlog

Kaynaklar:

 

 

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

13 Cevaplar

  1. Nurullah dedi ki:

    Müthiş..

  2. Yusuf dedi ki:

    Elinize emeğinize sağlık hocam. Süper olmuş. Özellikle 2. ve 5. maddeyi, gündemimize alıyoruz.

  3. uygar dedi ki:

    Eline emegine saglik.
    Halihazirdaki projemde kullanacagim bir iki sey gordum sayende

  4. Abdulkadir dedi ki:

    Merhabalar Bora Bey,
    Projelerimde bazı kod tekrarlarını ortadan kaldıracak, ekstradan farklı düşüncelere de yol gösteren bir makale oldu benim için. Çok teşekkürler.
    Benim Gizli Property’ler ile ilgili bir sorum olacak. Context class’ında OnModelCreating methodun içinde gizlemek istediğimiz field’ları yazdıktan sonra bazen veri tabanında güncelleme yapıyorum. Haliyle Scaffold’u projede tekrar çalıştırıyorum. Böyle olunca Context’de yenilendiği için gizlemek istediğim field’larda sıfırlanmış oluyor. Bunu kalıcı olarak yapmanın bir yolu var mı?

    • borsoft dedi ki:

      Selamlar,
      Var tabi ki. Aslında DBCntext’den türeyen kendi CustomDBContextinizi yaparsanız ve heryerde onun uzerinden calisirsaniz bu is biter :)

      Hoscakalin..

  5. Sinan dedi ki:

    Abi herşeyi çok güzel anlatıyorsun. :-)
    Abi .net core ve ef core ile katmanlı bir mimari nasıl olmalı detayları ile yazmayı düşünüyormusun?

    Teşekkürler

    • borsoft dedi ki:

      Selamlar Sinan,
      Bu konuda çokça seminer verdim. Makale de yazdım. Biraz gecmise bakarsan kolayca bulabilirsin.

      Hoscakal..

  6. Detaylı ve Bir O Kadarda Açıklayıcı Faydalı Bir Yazı Olmuş Teşekkürler

  7. Asif Tunga Mubarek dedi ki:

    Cok tesekkurler bora abi ???????? Eline saglik

Bir cevap yazın

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