Entity Framework için Profiler

Merhaba arkadaşlar.

Bu makalemde sizlere Entity Framework kullanılarak geliştirilmiş olan projelerde, performans ölçümleri ve refactoring’ler yapabilmek adına Entity Framework için geliştirilmiş olan bir Profiler aracını tanıtacağım. Bu Profiler aracı her ne kadar ücretli olsa da, 30 günlük bir trial sürümü işimizi görmektedir. Makalenin ilerleyen kısımlarında neden free bir Profiler aracı seçmediğimi anlayacaksınızdır.

Database Administrator’lar için her ne kadar SQL Profiler aracı olmazsa olmazları ise, Entity Frameowork gibi güçlü bir ORM aracını kullanan biz developer’lar için ise Entity Framework Profiler‘ı olmazsa olmazımız olmalıdır. Peki neden olmazsa olmazımız olmalıdır, diyecek olur iseniz;

Entity Framework gibi güçlü bir ORM, database tarafındaki neredeyse tüm işlemlerimizi “kolaylaştırmakta” ve geliştirmekte olduğumuz uygulamaların “zaman” kavramını oldukça hızlandırmaktadır bizler için. Peki her şey “kolaylaştırmak” ve “zaman” kavramları mıdır? Biz bu iki kavrama odaklanırken asıl mainset’imizde olması gereken “kalite” kavramından her ne kadar fark edemesek de bazen ödün vermek durumunda kalabiliyoruz. Biz bir developer olarak kullanmış olduğumuz ORM’in, bizim hayatımızı kolaylaştırırken bunun alt yapısındaki Query Engine’inde neler olup bitiyor? LINQ Query’leri evaluator’dan geçtikten sonra nasıl Query’ler oluşturuyor? gibi sorulara cevap verebilmeliyiz ki bu noktada performans namına iyileştirmeler/refactoringler yapabilir durumda olabilmeliyiz.

Şimdi Profiler aracımızı biraz daha yakından tanıyalım. Profiler aracımızın adı EFProf olup Hibernating Rhinos firması tarafından geliştirilmiştir. Hibernating Rhinos firmasının adı kulağa her ne kadar yabancı gelse de popüler NoSQL veritabanları arasında bulunan doküman tabanlı RavenDB‘nin de geliştiricileridir.

EFProf bize Entity Framework kullanırken arka planda neler olup bittiğini, sorguların nasıl oluştuğunu, Query Planlarının gösterimi ve veritabanına erişirken uygulamada performans kaybına neden olabilecek Unbounded Result’lara kadar değerli bilgileri verebilmektedir.

Şimdi nasıl kullanabileceğimize bir bakalım.

Kullanıma geçmeden önce, http://www.hibernatingrhinos.com/products/EFProf adresinden Download ederek sonrasında Try kısmından 30 günlük deneme lisans’ını elde edebilirsiniz.

EFProf’u deneyebilmek adına bir MVC projesi oluşturarak aşağıda görebileceğiniz üzere Entity Framework Model First yaklaşımı ile Designer üzerinden aşağıdaki gibi bir veritabanı yapısı oluşturdum.

Veri modelimizi oluşturduktan sonra EFProf’un kullanılabilmesi için önce projemize indirmiş olduğumuz EFProf dosyasının içerisinde bulunan “HibernatingRhinos.Profiler.Appender.dll” assembly’sini projemize referans olarak ekliyoruz.

Appender assembly’sini projemize referans olarak ekledikten sonra, EFProf’un çalışabilmesi için yapmamız gereken son bir adım kaldı. Bu adım uygulama başladığında, EFProf’un kendisini akışa register edebilmesi için Global.asax’ın Application_Start event’inde initialize etmeliyiz. Aşağıdaki kod bloğu ile bu işlemi kolaylıkla gerçekleştirebilmekteyiz.

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize();
        }
    }

Evet hepsi bu kadar. Şimdi “ProductController” isminde örnek bir controller ekliyorum ve içerisine “LazyLoadingEnabledProductListResult” ve “EagerLoadingEnabledProductListResult” isminde iki method tanımlıyorum. İsminden de anlaşılabileceği üzere bu örneğimizdeki amacımız; EFProf’un Entity Framework’ün Lazy Loading özelliği açıkken nasıl davrandığı ve Eager Loading yaparken nasıl davrandığını Profiler üzerinden görebilmektir.

using System.Linq;
using System.Web.Mvc;
using EFProfilerTest.Models;

namespace EFProfilerTest.Controllers
{
    public class ProductController : Controller
    {
        // GET: Product
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult LazyLoadingEnabledProductListResult()
        {
            EFProfilerTestContainer container = new EFProfilerTestContainer();
            container.Configuration.LazyLoadingEnabled = true;

            var productList = container.Products.ToList();

            return View(productList);
        }

        public ActionResult EagerLoadingEnabledProductListResult()
        {
            EFProfilerTestContainer container = new EFProfilerTestContainer();

            var productList = container.Products.Include("Brand").ToList();

            return View(productList);
        }
    }
}

Yukarıdaki kod bloğunda yapmış olduğumuz işlemler sırasıyla Lazy Loading olan method içerisinde Lazy Loading‘i enabled hale getirmek, sonrasında ise Eager Loading için Brand tablosunu Include etmek. Bu controller’ın view’larında ise hazır List Template’ini kullandım. Farklı olarak tek yaptığım işlem model üzerinden gelen Product listesini ekranda bir döngü ile dönerken, Brand isimli Navigation Property’si üzerinden de, ekrana kategori ismini basmak oldu. Bunu yapmamdaki amaç ise, Lazy ve Eager loading’leri görebilmektir.

Örnek view aşağıdaki şekildedir:

@model IEnumerable<EFProfilerTest.Models.Product>

@{
    ViewBag.Title = "LazyLoadingEnabledProductListResult";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>LazyLoadingEnabledProductListResult</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Brand.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Price)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Quantity)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.IsDelete)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.CreatedDate)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.LastModifyDate)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Brand.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Price)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Quantity)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.IsDelete)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CreatedDate)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastModifyDate)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new {/* id=item.PrimaryKey */}) |
            @Html.ActionLink("Details", "Details", new {/* id=item.PrimaryKey */}) |
            @Html.ActionLink("Delete", "Delete", new {/* id=item.PrimaryKey */})
        </td>
    </tr>
}

</table>

Şimdi uygulamamızı LazyLoadingEnabledProductListResult action’ı için startladığımızda “http://localhost:51516/Product/LazyLoadingEnabledProductListResult” adresi için karşımıza çıkacak olan ekran en azından benim için aşağıdaki gibi olacaktır eklemiş olduğum örnek verilerden dolayı (makalenin sonunda projeyi paylaşıyor olacağım).

Bu ekranın ardından EFProf’u çalıştıralım ve bir bakalım LazyLoading açık iken nasıl bir sonuç alacağız. EFProf’u çalıştırabilmek için indirmiş olduğumuz  klasörün içerisindeki EFProf uygulamasını çalıştırmamız yeterli olacaktır.

İlk bakışta baktığımızda sayfamız render edildiğinde 3 adet SQL sorgusunun oluşturulduğunu ve detayını görebilmekteyiz. Bu işlemleri gerçekleştirebilmek için ise sol “Application Statistics” bölümünden de görebileceğimiz üzere 2 adet Object Context’in açıldığını görebilmekteyiz. Her sorgunun sonucunda “Row count” kısmında ne kadar satır veri geldiğini, “Duration” kısmında ise harcamış olduğu süreyi görebilirken “Alerts” kısmından ise Profiler’ın performans için gerekli gördüğü önerileri ve uyarıları bize bildirmektedir.

Şimdi ikinci SQL sorgusunun detayına bir bakalım:

Details” kısmından görebildiğimiz gibi Lazy Loading açık olduğu için ve listeleme ekranından ürünleri listelerken, marka bilgisine Brand objesinin Navigation Property’si üzerinden eriştiğimiz için ikinci bir query execute etmektedir.

EFProf Profiler aracının en beğendiğim özelliklerinden biriside şimdi ilk SQL sorgumuz olan tüm listeyi çektiğimiz Query’e dönelim ve “Statements” sekmesinin en sağında bulunan “Alerts” kısmında bulunan top’a tıklayarak bizi yönlendirecek olduğu ekranda performansa yönelik olarak yapabileceğimiz geliştirmeleri görebilmekteyiz.

Alert sekmesinde ise Unbounded result set’in olduğunu bize bir öneri olarak söylemektedir. Yani veritabanından bir veri seti çekerken herhangi bir limitle koymadığımızı ve performansa yönelik bir yavaşlamanın olacağından söz etmektedir. İkinci bir güzel yönü ise “read more” a tıkladığımızda EFProf’un kendi guideline’ına yönlendirerek aşağıdaki kod bloğunda olduğu gibi nasıl yapabileceğimizi kodsal olarak göstermektedir. 🙂

var query = (from post in blogDataContext.Posts            
            where post.Category == "Performance"            
            select post)
            .Take(15);

Ayrıca “Stack Trace” sekmesinden ise gerçekleşen işlemler akışının hangi sınıflardan geçtiğini ve çift tıkladığınız taktirde işleme dair olan method’un satırına kadar Visual Studio üzerinde göstermektedir.

EFProf’un farklı yönlerine de bakabilmek adına şimdi EagerLoadingEnabledProductListResult action’unu açalım ve yine Product’ların listelenmesi sırasında oluşan Query’lere bir bakalım.

Entity Framework, Eager Loading açık olduğundan dolayı Brand tablosunu da join yaparak tek bir sorguda tüm veri setini çekmektedir. Bu veriler doğrultusunda yapmış olduğumuz işlemin performansı için Eager Loading mi yoksa Lazy Loading mi veya Query’lerde daha farklı neleri filtreleyebilirimi görebilmekteyiz. Bunun haricinde ise veritabanı tarafında en çok hangi tabloda süre maliyeti kaybetmekteyiz sorusunu ise Query Plan üzerinden aşağıdaki gibi görebilmekteyiz.

Bunların haricinde ise “Analysis” paneli üzerinden de birden çok rapora ulaşabilmemiz mümkündür. “Overall Usage” sekmesinden ise sayfanın render olma sürecine kadar gerçekleşen genel istatistikleri aşağıdaki gibi görebilmekteyiz.

Ayrıca bu ekran üzerinden yine bize maliyetli olan SQL sorgularını “Expensive Queries” sekmesinden görebilmekteyiz. Ben basic bir veri seti üzerinde çalıştığım için fazla bir rapor oluşmamış durumdadır. Sizler ise Entity Framework kullandığınız projelerinizde bu Profiler aracı ile performansa dair neleri refactor etmeniz gerektiğini veya veritabanında göz atmanız gereken index bilgileri gibi detaylara rahatlıkla erişebiliyor olacaksınız.

Umuyorum ki bu profiler aracı sizlerinde çok işine yarayacaktır.

İlgili örnek proje dosyası ektedir:

EFProfilerTest

 

Gökhan Gökalp

View Comments

  • Merhabalar, öncelikle emeğinize sağlık EntityFramewok'te profiler kullanımı harika, MSSQL Profiler'da da aynı görüntüyü alabiliyoruz. Benim sorum şu şekilde sql sorgusu büyüdükçe (join'ler çoğaldıkça ) extend1,extend2 şeklinde isimlendirerek yazdığımız linq sorgusunu hunharca uzatıyor. Yani kodun okunurluğu EntityFramework'te zorlaşıyor. Bunun dışında performans testlerini yaptığımızda 10.000 satırlık veriyi 19 -25 sn arasında getiriyor ve cache'lemiyor. İkinci üçüncü kez çalıştırdığımda da yaklaşık bu sürede getiriyor. Aynı sorguyu SqlKata kullanarak çektiğimde ise 2sn sürüyor. Bu konuda EntityFramework tarafında nasıl iyileştirme yapabilirim.

Recent Posts

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 yıl ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 yıl ago

Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 yıl ago