Entity Framework Core Üzerinde DBSet Olmadan Raw SQL Query Yazma

Selamlar,

  Bu makalede, Entity Framework Core ile çalışırken, bazen Linq ile yazılmayacak kadar complex sorgular ile uğraşmanız gerekebilir. Bu gibi durumlarda kodların DB’de olmaması, test senaryoları ve  Debug yapılamaması yüzünden, View veya Procedurler tercih edilmeyebilir. Bu durumda, Entity Framework üzerinde Raw Sql Query yazmak, doğru bir seçenek olabilir.

İşin zor kısmı, bu makale itibari ile artık Entity Framework Core 3.1’de DBSet olmadan RawSql yazmak mümkün değildir. Kısaca artık dbData.Database.SqlQuery<SomeModel> kaldırılmış, onun yerine her zaman kullanılması zorunlu olan DBSet<> getirilmiştir. dbData.Product.FromSql(“SQL SCRIPT”) gibi.

İşte bu makalede, Sql tarafında View, Procedure veya yeni bir tablo oluşturmadan, nasıl custom sorgular yazılabileceğini hep beraber inceleyeceğiz.

Öncelikle çekilmek istenen sorgu aşağıdaki gibidir: Amaç, Customer servisi altında Northwind database’i üzerinde en fazla miktarda sipariş veren 5 müşterinin listesinin çekilmesidir. “GetCustomerOrderByRawSql()” methodunda, 3 farklı tablodan gruplanıp,sıralanarak bir sorgu çekilmektedir. İlgili sorgunun sonucu, projede “Top5OrderModel” modelidir. Bu, bir çeşit bizim ViewModelimizdir. Aşağıdaki CustomService, geriye ServiceResponse model dönmektedir. ServiceResponse modelinin List property’sine, sorgudan dönen “List<Top5OrderModel>()” değeri atanmaktadır.

Services/Customers/CustomerService:

Services/Customers/ICustomerService: Aşağıda görüldüğü gibi  CustomerService, GetCustomerOrderByRawSql() methoduna zorlanmıştır.

Servisden dönen JsonResult:

Repository/GeneralRepository: Servis tarafından çağrılan GetSql() methodu, aslında FromSqlRaw() methodunun karşılığıdır.

Core/ApiResponse/ServiceResponse: Tüm servisler, aşağıda görüldüğü gibi ServiceResponse tipinde ortak bir model döner. Burada önemli olan “List” List of <T> tipinde Top5OrderModel dönülür. Tek bir model dönülse, Entity tipinde dönülmektedir. Token, RefreshToken, CreatedTokenTime authentication amaçlı kullanılan alanlardır.

Core/Models/Customer/Top5OrderModel: Sorgu sonucu ihtiyac duyulan ViewModel, aşağıdaki gibidir. Ama gerçekte, DB tarafında böyle bir tablo bulunmamaktadır. Ve oluşturulmayacaktır.

Şimdi sıra geldi, bu modeli DBFirst ile oluşan Context’de tanımlamaya. Tabi ki DB’den komut ile oluşan NorthwindContex’e, bir tanımlama yapılmayacaktır. Çünkü yapılan tanımlama, tekrar komutun çalıştırılması ile ezilecektir.

    • İlgili Komut: “dotnet ef dbcontext scaffold “Server=localhost\SQLEXPRESS;Database=Northwind;Trusted_Connection=True;” Microsoft.EntityFrameworkCore.SqlServer –output-dir Entities –force

DB/PartialEntites/DBContext: Aşağıda görüldüğü gibi “DBContext“, NorthwindContext’den türetilmiştir. Böylece bu kod üzerinde yapılan değişiklikler, “dotnet ef dbcontext scaffold” komutu ile yeniden oluşan DBContext ve DBSetler ile üzerine ezilmemiş olunur.

  • “public DbSet<Top5OrderModel> Top5OrderModel { get; set; }” : Top5OrderModel model sanki DB’den var olan bir tablo gibi tanımlanır. Bu aslında, sahte bir modeldir. Ne DB’de yaratılacaktır. Ne de zaten vardır. Tek amacı, Raw Sql şeklinde yazılan sorgudan dönen soncu, karşılamaktır.
  • *“modelBuilder.Entity<Top5OrderModel>(entity => { entity.HasNoKey(); })” : OnModelCreating() methodunda, yaratılan Top5OrderModel DBSet’inin bir keyinin olmadığının tanımlanması gerekmektedir. Aksi takdirde Index Hatası alınır.
    • Not: HasNoKey tanımlaması, tüm Viewlar için de yapılmalıdır.

Şimdi sıra geldi, CustomerService’indeki “Top5CustomerOrder()” methodunu, CustomerController’dan çağırmaya.

Bu makalede, farklı tablolardan Raw Sql Query ile data çeken bir servisin, .Net Core Entity 3.1’de nasıl yazılabileceğini hep beraber inceledik. Normal şartlar altında pek de tercih edilmemesi gereken bu yöntem, çok kompleks querylerde linq yazmak yerine kullanılabilir. Son olarak bunun yerine, Sql tarafında bir View oluşturulup, DB First ile oluşturulan bu View, projeye “DBSet<>” olarak generate edilebilir. Ve çekilmek istenen query, bu view üzerinden yapılabilir. Ama tabi ki bu da, hem Database katmanında, hem de kod tarafında ayrı ayrı çalışma yapılmasını gerektirmektedir. Eğer bir rapor projesi yazılıyor ise, herbir kompleks query için, ayrı ayrı View oluşturmak hem çok mantıklı değil hem de performans açısından tercih edilmeyebilir.

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

Source:

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

18 Cevaplar

  1. Serdar dedi ki:

    var result = _customerOrderRepository.GetSql(sqlQuery);

    bu satırın kodunu göremedim bu result nasul dönüyor

    • borsoft dedi ki:

      Öncelikle Selamlar,

      Methodun result tipi budur ==> public ServiceResponse GetCustomerOrderByRawSql()
      Ve aşağıda görüldüğü gibi “ServiceResponse” tipinde bir sonuş dönülmüştür. “List” ServiceResponse tipinin bir property’sidir ve result’ı almaktadır.
      Result tek başına değil response sınıfının bir property’si olarak geriye dönülmektedir.

      Iyi çalışmalar.

      var response = new ServiceResponse(null);
      response.List = result.ToList();
      return response;

      • Caglar dedi ki:

        Hocam öncelikle elinize sağlık

        _customerOrderRepository.GetSql metodu nun içeriğini paylaşırsanız daha açıklayıcı olacaktır.

        Teşekkürler

        • borsoft dedi ki:

          Selamlar Çağrı,

          Teşekkürler. Bildiğin eklemeyi unutmuşum :)
          GeneralRepository() sınıfını da makale içine ekledim.

          İyi çalışmalar.

  2. eray dedi ki:

    selamlar, where condition ların olacağı bir blok eklemeyi düşünürsek bunun içerisinde parametrik bir şekilde condition lar eklenebilir mi? veya Expression<Func> gibi yapılar kullanabilir mi?

    Teşekkürler

    • borsoft dedi ki:

      Selam Eray,
      Evet neden olmasın. SQL Raw zaten string bir query. Buna Func yazılarak conditionlar eklemek mümkün. Hali hazırda zaten şunun gibi kullanıyorum.

      Örnekler: “var result = _customMenuRepository.GetSql($”SELECT * FROM [dbo].[fn_GetMenu]({userId},{isAdmin}) ORDER BY OrderNumber”);”

      iyi çalışmalar.

  3. Serkan dedi ki:

    işin ilginç tarafı aynı şekilde tanımlanmış şu _context.GetNotDeletedIds.FromSqlRaw(“call sp_notdeletedids({0}, {1})”, 1, 3).ToListAsync();
    kod çalışmakta

  4. Caglar dedi ki:

    Merhaba Hocam

    protected virtual DbSet Entities => _entities ?? (_entities = _context.Set());
    bunu neden kullanıyoruz ?
    Zaten ctor da tabloyu _entities e eşleştirdik.

    Bu kullanımın amacı nedir ?

    Teşekkürler

    • borsoft dedi ki:

      Selamlar,
      Tanımlanan _entites private. Sadece içerde kullanılan bir değişken. Ama Entities property. Dışarıya açık.

      İyi çalışmalar.

  5. Turgay dedi ki:

    Merhaba Hocam
    VIEW yazmak performansı neden/ne derece etkiler. Ayrıca bu sorgu müşteri bazında değişiyorsa (tablo yapısı aynı olmasına rağmen, tablo adları farklı ise) VIEW den yönetmek daha kolay olmaz mı? Bu durumnda sql string parametrik gönderilebilir, procedure veya function yazılabilir. Siz olsanız nasıl ilerlerdiniz?
    Diğer bir soruda , sql sorgu gönderildiğinde entity ve buna bağlı prop lar dinamik olarak üretilebilir mi?
    Özellikler bir rapor uygulamasında her müşterinin ihtiyacı farklı olduğu için dinamik bir yapıya ihtiyaç var.
    Bu şekilde bir yapı kurmak mümkün mü?
    Değerli bilgileriniz için teşekkürler.

  6. Oğulcan dedi ki:

    Merhaba,

    Bu şekilde iki tane dbcontext oluşturduğumuz zaman, uygulamamıza da iki tane context inject etmeliyiz değil mi ?

  7. Murat Saygılı dedi ki:

    Merhaba hocam,

    .FromSqlRaw(queryText).Where(burada bazı parametrik filtreler).Select(new object).Tolist() olarak deneme yaptım ve filtreler değiştikçe gelen verilerin de değiştiğini gördüm yani çalıştırabildim. Benim merak ettiğim acaba verileri dbden çektikten sonra mı filtreliyor yoksa benim where bloğunda belirttiğim filtrelerle beraber mi veritabanında sorgulama yapıyor biliyor musunuz?

    • borsoft dedi ki:

      Selam,
      Hayır SQL Query bağlı olarak filitrelenen verileri DB’den çekiyor. Yani Önce hepsini çekip, sonra filitrelemiyor. Zaten böyle birşeyi asla düşünmeyin :)

      • Murat Saygılı dedi ki:

        Teşekkürler bu kadar iyi çalışması şaşırttı beni o yüzden merak ettim elinize sağlık :)

  8. Selim dedi ki:

    Konu ile alakalı interneti arşınlarken, çözümü bir Türkçe kaynaktan bulmak gerçekten çok güzel oldu. Teşekkür ederim, elinize sağlık. Sadece bir eleştiri yapmak istiyorum. Örneğe, generic repository, service response gibi classlar ve metotlarını eklemenizin konuyu biraz karmaşık hale getirdiğini düşünüyorum.

    DBContext kısmı sizin yaptığınız ile aynı olacak şekilde, amaca uygun bir clas yazmak ve sorgu sonucu dönecek sütunlara sınıfın propertlerine uygun alias vermek konuyu özetliyor aslında. Saygılar, iyi günler.

    public class QueryModel
    {
    public string Result { get; set; }
    }

    List list = dbContext.QueryModel.FromSqlRaw(“SELECT name as Result FROM sys.databases WHERE HAS_DBACCESS(name) = 1”).ToList();

  9. Samet dedi ki:

    Merhaba,

    Yazdığınız yazıda bir DTO oluşturup o DTO’ya uygun şekilde sorgu yazıp veri çekmek gerekiyor.
    DTO oluşturmadan object veya dynamic tipinde veri çekmek mümkün mü?

Bir cevap yazın

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