.Net 6 ve Yenilikleri #2 (Preview 4)

.Net dünyası yeni bir versiyon hazırlığında. .Net 5 yayını oldukça önemli bir dönüş noktasıydı ve bu süreci tamamladık. Umarım projelerinizde .Net 5 upgrade yapmışsınızdır çünkü yeni bir sürüme geçmenin en kolay yolu bir önceki sürümde hazır ve nazır beklemektir. 🧐 Eğer projeleriniz .Net Core 2.x veya .Net Core 3.x sürümlerinde ise adım adım upgrade yapmanızı öneririm aksi takdirde çok fazla breaking change ile karşı karşıya kalabilirsiniz.

.Net 5 sürümünde aramıza katılan ve aramızdan ayrılan özelliklerin listesini görmek ister misiniz? 🤔 Cevabınız evet ise o zaman aşağıdaki linklere tıklayabilirsiniz. 🎉
(.Net 5 ve Yenilikleri #1-.Net 5 ve Yenilikleri #2)

Geçtiğimiz günlerde .Net 6 Preview 4 yayımlandı. Release çıkana kadar sanırım yaklaşık altı veya yedi adet preview sürümü gelecek. Kullandığımız teklonojileri iyi takip etmek ve gelişmelerden haberdar olmak için her preview sürümünde gerçekleşen yenilikleri kaleme almaya çalışacağım. 🎉

.Net 6 sürümü ile hedeflenen en temel feature .Net 5 ile başlayan birleştirme senaryosunun son kısımlarını sunmaktır.
Her yeni sürümde olmazsa olmazlardan performans iyileştirmeleri de Preview 4 sürümünde duyuruldu.
Planlanan yayın tarihi Kasım 2021. Şu anda LTS (Long Term Support) .Net Core 3.1 sürümünde ancak planlamalara göre .Net 6 yayımlandıktan sonra üç yıl süre ile LTS alacaktır.

Eğer planlamayı daha net bir şekilde görmek istiyorsanız temalarof.net uygulamasını kullanabilirsiniz.

Preview 4 ile beraber bir çok özellik test edilebilir durumda yeni gelecek özellikler için yapı taşları oluşturulmuş ve önümüzdeki versiyonlarda yayımlanması planlanıyor.

.Net 6 ile ilgili 9–11 Kasım tarihinde .Net Conf 2021 var. Bu etkinlikte .Net 6 ile ilgili bilmek istediğimiz her şeyi bize anlatan içerikler olacak. .Net Conf 2021 ön kaydına şuradan ulaşabilirsiniz.

System.Text.Json

.Net dünyasında genellikle Json işlemleri yapmak istediğimizde aklımıza ilk gelen kütüphane Newtonsoft.Json oluyor. Aklımıza ilk bu kütüphanenin gelmesi çok garip değil çünkü yıllarca .Net ekosistemi içerisinde built-in olarak gelen bir kütüphaneydi. Ancak .Net 3.x sürümleriyle beraber bu senaryo biraz değişti. .Net uygulamalarında çok güçlü bir bağımlılığı olan bu kütüphaneden kurtulma senaryosu için karşımıza System.Text.Json çıkıyor. Neden kurtulmak istiyoruz? En önemli nedeni tabii ki performans. Ancak Newtonsoft.Json kütüphanesi çok advance bir kütüphane. Bir çok senaryoyu handle edebilecek şekilde tasarlanmış. Buna karşılık olarak System.Text.Json tarafına ciddi bir yatırım var ve hemen hemen her sürümde bu Api üzerine ciddi performans iyileştirmeleri ve yeni özellikler katıldığını görüyoruz.

Preview 4 ile beraber Writeable Dom ve IAsyncEnumerable desteklerinin eklendiğini görüyoruz.

System.Text.Json.Node için eklenen tipler.

namespace System.Text.Json.Node
{
public abstract class JsonNode {...};
public sealed class JsonObject : JsonNode, IDictionary<string, JsonNode?> {...}
public sealed class JsonArray : JsonNode, IList<JsonNode?> {...};
public abstract class JsonValue : JsonNode {...};
}

Örnek kodu da incelediğimizde anlıyoruz ki aramıza katılan bu yeni yapılar Json Serializer işlemlerinde de kullanılabilir. Örneğin şemasını belirleyemediğiniz yapılarda bu tipleri kullanabilirsiniz.

Ek olarak bu yeni yapılarla beraber dynamic desteği de bulunmaktadır.

Preview 4 ile beraber IAsyncEnumerable<T> desteği eklendi. Böylece asenkron bir kaynaktan gelen datayı serialize ve deserialize edebiliyoruz.

Streaming Serialization

Streaming Deserialization

System.Linq

Preview 4 ile beraber System.Linq üzerinde de iyileştirmeler ve yeni özellikler mevcut.

Enumerable.Range(1, 10).ElementAt(^2); // returns 9
  • source.Take(..3) instead of source.Take(3)
  • source.Take(3..) instead of source.Skip(3)
  • source.Take(2..7) instead of source.Take(7).Skip(2)
  • source.Take(^3..) instead of source.TakeLast(3)
  • source.Take(..^3) instead of source.SkipLast(3)
  • source.Take(^7..^3) instead of source.TakeLast(7).SkipLast(3).

Bir selector yardımı ile nesne içerisindeki bir parametreyi belirterek Max veya Min değerlerini alabilirsiniz.

var people = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
people.MaxBy(person => person.Age); // ("Ashley", 40)

Sıralanabilir bir kaynağı eşit parçalara bölmek için Chunk kullanılabilir.

IEnumerable<int[]> chunks = Enumerable.Range(0, 10).Chunk(size: 3); // { {0,1,2}, {3,4,5}, {6,7,8}, {9} }

DateTime ve TimeZone yapılarında gerçekleştirilen performans düzenlemelerini görmek için ilgili PRlara bakabilirsiniz.

Improve time zone display names on Unix by mattjohnsonpint · Pull Request #48931 · dotnet/runtime (github.com)

Improve performance of DateTime.UtcNow on Windows by GrabYourPitchforks · Pull Request #50263 · dotnet/runtime (github.com)

ASP.NET CORE

Preview 4 sürümü ile beraber ASP.NET Core tarafında da bizi heyecanlandıran bazı yenilikler ve en önemlisi performans iyileştirmeleri bulunuyor.

Küçük bir hafta sonu projesi yapacaksanız veya micro-service mimarisini kullanıyorsanız yada sadece minik bir servis yazmak istiyorsanız; daha basit işleri gerçekleştiren, içerisinde daha az middleware olduğu bir servis istiyorsunuz demektir. Preview 4 ile beraber MinApi bu konuda bizim yoldaşımız olacak. Hızlı hafta sonu projeleri veya küçük servisler için MinApi kullanabilirsiniz. Ancak unutmamız gereken konu şu, MinApi içerisinde ASP.NET CORE ile gelen tüm özellikleri beklememeliyiz. Adı üstünde MinApi. 😊
Yeni bir MinApi template üzerinde çalışmak için;

dotnet new web -o MinApi

komutunu kullanabilirsiniz. Tek bir dosya ve bir kaç satır kodla HTTP servisiniz olacaktır.

Bu kadar min olmasın ya! Biraz özelleştirelim en azından. Swagger, authentication gibi yapılar olsun istiyorsanız aşağıdaki örnek kodlar size yardımcı olacaktır.

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Api", Version = "v1" });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Api v1"));
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

Bildiğiniz üzere routing isteğin herhangi bir action üzerine yönlendirilmesi için kullandığımız bir yapı. Routing üzerinde Preview 4 ile beraber yeni bir format getirilirken bu yeni formatın eskisine göre olan performans farkı beni heyecanlandırdı. 🤩

Before;

app.MapGet("/", async httpContext =>
{
await httpContext.Response.WriteAsync("Hello World!");
});

After;

app.MapGet("/", (Func<string>)(() => "Hello World!"));

.Net 6'dan önce endpointler içerisinde yazdığımız kodlar serialization işlemlerinden önce memory üzerinde ara bellek dediğimiz buffer bölgesine alınıp sonrasında serialization işlemleri gerçekleştirilirdi. Preview 4 ile birlikte bu senaryo daha performansı olacak şekilde düzenlendi. Kaynağınızdan IAsyncEnumerable döndürdüğünüzde ara belleğe alınmaz. Böylece büyük verilerde bellek kullanımı minimum seviyeye çekilmiş olur.

public IActionResult GetProducts()
{
return Ok(dbContext.Products);
}

EF Core kullanıyorsanız ve Lazy Loading aktif ise o zaman bu yapıda bazı hatalar oluşabilir. Lazy Loading kapatmak tabii en iyi tercih olacaktır ancak kullanmak istiyorsanız kendiniz datayı belleğe alarak bu davranışı kullanabilirsiniz.

Özellikle hoşuma giden bir özellik oldu çünkü bu tür bilgileri loglamak istediğimizde custom middleware yazarak bu sorunu çözüyorduk. Preview 4 ile beraber aramıza built-in olarak katılan bu middleware, request ve response değerlerinin loglanmasında işimizi kolaylaştırdı. Derdimi daha iyi anlatmak için örnek kodlar üzerinden konuşacağım.

Custom Http Logging Middleware

Preview 4 ile birlikte yazdığımız bu kodları aşağıdakilerle yer değiştirebiliriz.

public void ConfigureServices(IServiceCollection services)
{
services.AddHttpLogging(logging =>
{
// Customize HTTP logging here.
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("My-Request-Header");
logging.ResponseHeaders.Add("My-Response-Header");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpLogging();
}

Eğer bu kullanım size yeterli gelmiyorsa yukarıdaki yöntemi kullanarak custom bir middleware yazabilirsiniz.

.Net 6 ile birlikte razor içerisinde klasik generic type constraintsleri kullanabilirsiniz.

@typeparam TEntity where TEntity : IEntity

AspNetCore tarafında performans ile ilgili dökümanda detaylı bahsedilmeyen ancak github üzerinde konu ile ilgili PR bulunan bazı konular var. İlgili dökümana buradan ulaşabilirsiniz.

Blazor, WebAssembly performansını iyileştirmek için .Net kodunuzu doğrudan WebAssembly üzerinde derleyebileceğimiz AOT derlemeyi destekliyor.

Bu sürümden önce Blazor WebAssembly uygulamaları .NET IL kullanarak çalışıyordu. .Net kodu yorumlandığı için performans olarak bir dezavantaj oluşturuyordu. AOT derlemesi direkt olarak .Net kodunu WebAssembly’de derleyerek yaklaşık 5x bir performans artışı sağlamış oldu.

AOT derlemesini kullanmak için;

dotnet workload install microsoft-net-sdk-blazorwebassembly-aot

AOT derlemeye izin vermek için project file içerisini şunu ekleyin;

<RunAOTCompilation>true</RunAOTCompilation>

AOT derlemesi sadece projeyi publish ettiğinizde gerçekleşir. Bunun sebebi, AOT derlemesinin uzun ve maliyetli bir işlem olmasıdır. Küçük projelerde bile dakikalar sürerken büyük projelerde çok daha uzun sürecektir. Yayımlanan dökümanda bu konu ile ilgili çalıştıklarını belirtmeleri güzel bir detay olmuş.

Ek olarak AOT ile derlenmiş ve .NET IL ile derlenmiş uygulamalar arasında boyut farkı olacaktır. Tahmin ettiğiniz üzere AOT ile alınan çıktılar daha büyüktür. Ancak hem cam kenarı hem de en ön sıra genelde mümkün olmuyor. Performans için boyut takası gerçekleştirmek gerekiyor.

Aradaki farkın anlaşılması için güzel bir mini klip paylaşılmış.

Entity Framework Core

Preview 4 ile EF Core tarafında temel odak nokta performans iyileştirmeleri olmuş.

Developerlar için tatlı bir özet hazırlamışlar;

  • EF Core 6.0, EF Core 5.0 ile karşılaştırıldığında %70 seviyesinde bir iyileştirme olmuş. Yani sadece target framework değerini değiştirerek ve paketleri güncelleyerek %70 daha hızlı bir uygulamaya sahip olabilirsiniz.
  • Tabii bu hıza sadece EF Core bazında bakmak doğru değil. .Net Runtime üzerinde yapılan geliştirmelerin çok büyük bir rolü var.
  • EF Core 6.0 ile sorguların %31 oranında daha hızlı yürütüldüğü iddia edilmiş.
  • Heap allocation %43 oranında azaltılmış.

Şimdiye kadar çok fazla performans odağında işler yapmadığını ve bundan sonra ürünü daha stabil ve daha performans odaklı geliştireceklerini belirtiyorlar. Linux üzerinde PostgreSQL veri tabanına uygulanan testlerde TechEmpower Fortunes karşılaştırmasında .Net şimdiden çok yüksek bir puan alarak 12. sıraya yükseldi. İlgili dökümana buradan ulaşabilirsiniz.

EF Core 6.0 sürümünde ekibin temel motivasyonu Dapper’ın Fortunes karşılaştırmasındaki performansına mümkün olduğunca yaklaşmak olduğunu belirtmişler. “Dapper daha hafif ve performans odaklı bir ORM olduğu için bize ilham veriyor.” demişler.

Peki ne oldu? Kim kazanmış? 🧐 Aradaki fark %55 seviyesinden %5 seviyesine kadar inmiş. Böylece EF Core, Dapper kadar hafif bir ORM olmadığı halde performans olarak gerçekten iyi bir noktaya gelmiş diyebiliriz.

Tabii bu testler ideal ortamlarında network vb. kavramlardan arındırılmış bir şekilde yapılıyor. Gerçek dünya senaryolarında bu sonuçlar farklılık gösterebilir.

Peki bu iyileştirmeler hangi kavramlar üzerinde yapıldı?

Kaynakları oluşturmak ve bunları dispose etmek genellikle zaman alan işlemlerdir. İyi performansın temelinde, genellikle pooling ve recycling vardır. DbContext, uygulamalarımızda bir işin tamamlanması ve yürütülmesi için bir giriş kapısıdır. Bazen DbContext nesnemizi new anahtar kelimesi ile üretiriz bazen ise Dependency Injection ile bir örneğini alırız.

Küçük ölçekli uygulamalarda new anahtar kelimesini kullanmak çok sorun çıkarmayabilir. Ancak yüksek ölçekli ve performans odaklı uygulamalarda context ayaklanırken tüm bağlamlarının yeniden ayaklanması yüksek ihtimalle bize sorun çıkaracaktır. İşte tam olarak bu sebeple DbContext nesnemizi bağımlılık ekleme yöntemi ile kullanıyoruz. Hem Garbage Collector üzerine çok fazla iş bırakmıyoruz hem de DbContext ile işimiz bittiğinde durumunu sıfırlıyoruz. Bu sebeple EF Core, DbContext Pooling özelliğini destekler. Temel mantık şu, bir DbContext ile işlem tamamlandığında onu ve tüm bağımlı hizmetlerini elden çıkarmak yerine EF Core durumunu sıfırlar ve daha sonra yeniden kullanılmasına izin verir. Benim de yeni öğrendiğim bir bilgi oldu ve aklıma hemen şu soru geldi. Bu pooling için üst sınır nedir?

Havuza alınmış DbContext örnekleri için varsayılan üst sınır 128'imiş. Preview 4 ile bu sınırı 1024'e yükseltmişler ve %23 seviyesinde bir performans artışı gözlemlemişler.

En az performans kadar önemli bir diğer konu ise tabii ki güvenlik. Bildiğimiz üzere EF Core concurrent usage konusunda katıdır. Neredeyse hiçbir zaman concurrent kullanımına izin vermeyen bir veri tabanı bağlantısı alır. DbContext örneğinin eşzamanlı erişimi developer hatası olarak kabul edilir. Ancak EF Core, bu durumu yakalamak ve geliştiriciye bir bilgi vermek için arka tarafta bir iş parçacığı barındırır. EF Core eşzamansız sorgu yürütmeyi desteklediğinden sorguya katılan iş parçacıkları arasında Async Local isimli bir yapı kullanır. Bu yapının oldukça fazla heap allocation yaptığı testler sonrasında ortaya çıkmış.

Güvenlik çok ama çok önemli bir parametre olduğu için direkt olarak bu yapıyı devre dışı bırakmak çok sağlıklı olmayacaktır. Bu sebeple bir flag yardımıyla bunu kapatabileceğimiz bir interface sunduklarını belirtiyorlar ancak dökümanda bu yapının bir örneğini göremedim. Github üzerinden tamamlanan issuelar arasında da konuyla ilgili bir sonuç bulamadım. Sanırım bir sonraki preview sürümlerinde bu konuyla ilgili daha detaylı bir açıklama gelecektir. Sonuç olarak bu yapıyı bir flag yardımıyla kapattıklarında %6.7 seviyesinde bir iyileştirme elde edilmiş.

DbCommandInterceptor yazarsanız bir SQL sorgusunun yürütülürken bir giriş ve bir çıkış olmak üzere 7 adet adımdan oluştuğunu görürsünüz. Bu adımların her biri loglanabilir. Amaç ise burada bir profil oluşturmaktır.

Evet bu yapı, bize esneklik kazandırıyor ama yapılan testler sonrasında maliyetinin yüksek olduğu ortaya çıkmış.

Akla ilk gelen yöntem bir flag yardımıyla bu işlemleri kapatmak ve performansı korumak olmuş. Tabii “Ya hep ya hiç” mantığı böyle geniş kitlelerin kullandığı bir ORM tool için çok mantıklı olmayacaktır. Bu nedenle burada bir saniye olacak şekilde bir sınırlama getirilmiş. Özetleyecek olursak; uygulama runtime üzerinde bir noktada bir DiagnosticListener kaydederse, olayların orada görünmeye başlaması bir saniye kadar sürebilir. Anlaşılan burada bir değiş tokuş yaparak performans odaklı bir geliştirme olmuş. Rakamsal olarak bu iyileştirme EF Core üzerine %7 oranında bir katkı sağlamış.

Son olarak diğer bloglar üzerinde Node.js ile karşılaştırıldığında 10x daha hızlı olduğunu söyleyen bazı cümleler okudum. Bu testler ideal ortamlarda yapılmıştır. Preview sürümlerinin gerçek dünya senaryolarında tekrar test etmek için geldiğini unutmamalıyız.Teklonojilerin taraftarı değil bilinçli kullanıcısı olmalıyız. Son olarak bu yapılan geliştirmeler ve eklenen yeni özellikler değişiklik gösterebilir. İlerleyen sürümlerde tekrar değerlendirmek üzere… 👋

3 Haziran 2021 tarihinde Koddayı ekibi olarak Enis Necipoğlu ile birlikte bu konuyla ilgili daha detaylı bir etkinliği yapacağız. “.Net 6 ve Yenilikleri” başlığı altında enine boyuna tartışacağız. Etkinliğe katılmak için aşağıdaki linke tıklayabilirsiniz.

.Net 6 ve Yenilikleri | Kommunity
Güncelleme
Etkinlik tamamlandı. Tekrarını izlemek isterseniz aşağıdaki linke tıklayabilirsiniz.
.NET 6 ve Yenilikleri — YouTube

Yazdığınız kodlar production ortamlarında hatasız koşsun. 👋

ASP.NET Core updates in .NET 6 Preview 4 | ASP.NET Blog (microsoft.com)

Routing in ASP.NET Core | Microsoft Docs

Add new Use middleware extension method by BrennanConroy · Pull Request #31784 · dotnet/aspnetcore (github.com)

Reduce allocations for Cookies. by jkotalik · Pull Request #31258 · dotnet/aspnetcore (github.com)

Announcing Entity Framework Core 6.0 Preview 4: Performance Edition (microsoft.com)

Advanced Performance Topics | Microsoft Docs

EntityFrameworkServiceCollectionExtensions.AddDbContextPool Method (Microsoft.Extensions.DependencyInjection) | Microsoft Docs

Interceptors — EF Core | Microsoft Docs

Round 20 results — TechEmpower Framework Benchmarks

.NET 6 Preview 4 İle Beraber System.Text.Json API’larına Gelen Yenilikler (ilkayilknur.com)

Solution Developer — I want to change the world, give me the source code.

Medium independent DevOps publication. Join thousands of aspiring developers and DevOps enthusiasts