Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for 10 Reasons to Upgrade to .NET 9
Anton Martyniuk
Anton Martyniuk

Posted on • Originally published atantondevtips.com on

     

10 Reasons to Upgrade to .NET 9

.NET 9 and C# 13 were released on November 12, 2024.
In this blog post, I want to show 10 reasons why you should consider upgrading your projects to .NET 9.

On my website:antondevtips.com I share .NET and Architecture best practices.
Subscribe to become a better developer.
Download the source code for this blog post for free.

1. Performance improvements of Minimal APIs

Minimal APIs in .NET 9 received a huge performance boost and can process15% more requests per second than in .NET 8.

Also, Minimal APIs consume93% less memory compared to a previous version.

Screenshot_1

This means that Minimal APIs became much faster than Controllers.

Minimal APIs have the following advantages over Controllers:

  • Better Performance (as I showed you above)
  • Single Responsibility: Each endpoint is responsible for one thing, has its own dependencies, compared to bloated Controllers in many applications that have a lot of dependencies that are used by some methods.
  • Flexibility and Control: The Minimal API approach allows for more granular control over routing and middleware configuration.
  • Development Speed: The ability to quickly set up an endpoint without the ceremony of defining controllers makes you more productive.
  • Minimal APIs also align with such modern Concepts as Vertical Slice Architecture.

2. Performance improvements of Exceptions

Exceptions became 2-4 times faster, according to Microsoft Benchmarks.

Screenshot_2

Exceptions happen everywhere:

  • database access error
  • external system is down
  • file is not found
  • request timeout...

Doesn't matter if you use exceptions for control flow or you have Global Exception Handler - good news, exceptions are now faster.

I have always appreciated such performance improvements for free without a need to touch my code.

Remember, exceptions are for exceptional situations. They are not the best option for a control flow.
A better option is aResult Pattern.

3. New HybridCache

When working with cache, you must be aware of the cache stampede problem: when multiple requests receive a cache miss and will all call the database to retrieve the entity.
Instead of calling the database once to get the entity and write it into cache.

Standard MicrosoftIMemoryCache suffers from this problem.
You can introduce manual locking to solve this problem, but this approach doesn't scale so well.

That's why I am using a new caching libraryHybridCache available in .NET 9 that solves a cache stampede problem.

HybridCache is a great replacement of oldIMemoryCache andIDistributedCache as it combines them both and can work with:

  • in-memory cache
  • distributed cache like Redis

To get started withHybridCache add the following Nuget package:

dotnet add package Microsoft.Extensions.Caching.Hybrid
Enter fullscreen modeExit fullscreen mode

Here is how you can register it in DI:

#pragma warning disable EXTEXP0018services.AddHybridCache(options=>{options.DefaultEntryOptions=newHybridCacheEntryOptions{Expiration=TimeSpan.FromMinutes(10),LocalCacheExpiration=TimeSpan.FromMinutes(10)};});#pragma warning restore EXTEXP0018
Enter fullscreen modeExit fullscreen mode

You need to disable the warning with pragma asHybridCache is in preview at the moment (it might be already released when you're reading this).

By default, it enables the InMemory Cache.

HybridCache API is similar to those from the old caches.
You can useGetOrCreateAsync method that will check if entity exists in the cache and returns it.
If the entity is not found - a delegate method is called and to get the entity from the database:

privatestaticasyncTask<IResult>Handle([FromRoute]Guidid,HybridCachecache,ApplicationDbContextcontext,CancellationTokencancellationToken){varbookResponse=awaitcache.GetOrCreateAsync($"book-{id}",asynctoken=>{varentity=awaitcontext.Books.AsNoTracking().Include(b=>b.Author).FirstOrDefaultAsync(b=>b.Id==id,token);returnentityisnotnull?newBookResponse(entity.Id,entity.Title,entity.Year,entity.Author.Name):null;},cancellationToken:cancellationToken);returnbookResponseisnotnull?Results.Ok(bookResponse):Results.NotFound();}
Enter fullscreen modeExit fullscreen mode

4. New LINQ methods: AggregateBy, CountBy, Index

.NET 9 introduces 3 new LINQ methods:CountBy,AggregateBy andIndex.

Let's explore the code we needed to write before and what improvements these methods bring to the table.

CountBy

Previously, we had to group the items and then count them:

varcountByName=orders.GroupBy(p=>p.Name).ToDictionary(g=>g.Key,g=>g.Count());
Enter fullscreen modeExit fullscreen mode

TheCountBy method simplifies this by directly providing a count for each key:

varcountByName=orders.CountBy(p=>p.Name);foreach(varitemincountByName){Console.WriteLine($"Name:{item.Key}, Count:{item.Value}");}
Enter fullscreen modeExit fullscreen mode

AggregateBy

In older versions, we usedGroupBy to group orders by category and thenAggregate within each group to sum up the prices:

vartotalPricesByCategory=orders.GroupBy(x=>x.Category).ToDictionary(g=>g.Key,g=>g.Sum(x=>x.Quantity*x.Price));
Enter fullscreen modeExit fullscreen mode

The newAggregateBy method simplifies this process by combining grouping and aggregation into one step:

vartotalPricesByCategory=_orders.AggregateBy(x=>x.Category,_=>0.0m,(total,order)=>total+order.Quantity*order.Price);foreach(varitemintotalPricesByCategory){Console.WriteLine($"Category:{item.Key}, Total Price:{item.Value}");}
Enter fullscreen modeExit fullscreen mode

Index

In older versions, we manually managed the index or usedSelect with a projection:

intindex=0;foreach(variteminorders){Console.WriteLine($"Order #{index}:{item}");index++;}foreach(variteminorders.Select((order,index)=>new{order,index})){Console.WriteLine($"Order #{index}:{item}");}
Enter fullscreen modeExit fullscreen mode

The newIndex method provides a more elegant solution by providing direct access to the item and its index in a form of Tuple:

foreach(var(index,item)inorders.Index()){Console.WriteLine($"Order #{index}:{item}");}
Enter fullscreen modeExit fullscreen mode

5. EF Core New Seeding methods.

To seed your database with initial data in previous EF Core versions you had to:

  • create seeding with limited capabilities in DbContext in theOnConfiguring method
  • create separate classes, resolve and call them before the application was started (in Program.cs or a Hosted Service).

EF 9 brings a new way to seed your database.
Microsoft recommends usingUseSeeding andUseAsyncSeeding methods for seeding the database with initial data when working with EF Core.

You can useUseSeeding andUseAsyncSeeding methods directly in theOnConfiguring method in your DbContext when registering DbContext in DI:

builder.Services.AddDbContext<ApplicationDbContext>((provider,options)=>{options.UseNpgsql(connectionString).UseAsyncSeeding(async(dbContext,_,cancellationToken)=>{varauthors=GetAuthors(3);varbooks=GetBooks(20,authors);awaitdbContext.Set<Author>().AddRangeAsync(authors);awaitdbContext.Set<Book>().AddRangeAsync(books);awaitdbContext.SaveChangesAsync();});});
Enter fullscreen modeExit fullscreen mode

UseSeeding is called from theEnsureCreated method, andUseAsyncSeeding is called from theEnsureCreatedAsync method.
When using this feature, it is recommended to implement bothUseSeeding andUseAsyncSeeding methods using similar logic, even if the code using EF is asynchronous.
EF Core tooling currently relies on the synchronous version of the method and will not seed the database correctly if theUseSeeding method is not implemented.

6. EF Core Better LINQ and SQL Translation

EF 9 allows more LINQ queries to be translated to SQL, and many SQL translations for existing scenarios have been improved.
EF 9 has better performance and readability than the previous version.

Let's explore 2 most popular use cases for complex types.

EF9 supports grouping by a complex type instance.

vargroupedAddresses=awaitcontext.Customer.GroupBy(b=>b.Address).Select(g=>new{g.Key,Count=g.Count()}).ToListAsync();
Enter fullscreen modeExit fullscreen mode

ExecuteUpdate has been improved to accept complex type properties.

However, each member of the complex type must be specified explicitly:

varaddress=newAddress("New York City","Baker's st.","54");awaitcontext.Customers.Where(e=>e.Address.City=="New York City").ExecuteUpdateAsync(s=>s.SetProperty(b=>b.StoreAddress,address));
Enter fullscreen modeExit fullscreen mode

7. C# 13 Params Collection

Previously, when usingparams keyword you were able to use only arrays:

PrintNumbers(1,2,3,4,5);publicvoidPrintNumbers(paramsint[]numbers){// ...}
Enter fullscreen modeExit fullscreen mode

C# 13 introduces Params Collections, allowing you to use the following concrete types:

  • Arrays
  • IEnumerable
  • List
  • Span

For example, you can use a list:

List<int>numbers=[1,2,3,4,5];PrintNumbers(numbers);publicvoidPrintNumbers(paramsList<int>numbers){// ...}
Enter fullscreen modeExit fullscreen mode

And further inPrintNumbers method you can use, for example, LINQ methods over the params collection.

8. C# 13 New Lock Object

System.Threading.Lock is a new thread synchronization type in .NET 9 runtime.
It offers better and faster thread synchronization through the API.

How it works:

  • Lock.EnterScope() method enters an exclusive scope and returns a ref struct.
  • Dispose method exits the exclusive scope

When you replaceobject with a newLock type inside alock statement, it starts using a new thread synchronization API.
Rather than using old API throughSystem.Threading.Monitor.

publicclassLockClass{privatereadonlySystem.Threading.Lock_lockObj=new();publicvoidDo(inti){lock(_lockObj){Console.WriteLine($"Do work:{i}");}}}
Enter fullscreen modeExit fullscreen mode

9. ASP.NET Core: Static Asset delivery optimization

Did you know that you can serve static files from your ASP NET Core application?
Kestrel web server supports static files, and in .NET 9 it became even faster.

You can host your backend and frontend code using a single service (executable), so you don't need an extra service or docker container to host your frontend inside a nginx or Apache.

In .NET 8 and earlier versions you could add static files usingUseStaticFiles function.

In .NET 9 there is a new function calledMapStaticAssets which is a preferable way of serving static content.
In .NET 9 static files are much better compressed compared to .NET 8.

MapStaticAssets provides the following benefits when compared toUseStaticFiles:

  • Build time compression for all the assets in the app: gzip during development and gzip + brotli during publish.
  • All assets are compressed with the goal of reducing the size of the assets to the minimum.
  • Content based ETags: The Etags for each resource are the Base64 encoded string of the SHA-256 hash of the content. This ensures that the browser only redownloads a file if its content has changed.

Screenshot_3

10. Blazor Improvements

.NET 9 brings the following nice updates to Blazor:

  1. .NET MAUI Blazor Hybrid and Web App solution template
  2. Detect rendering location, interactivity, and assigned render mode at runtime
  3. Improved server-side reconnection experience
  4. Simplified authentication state serialization for Blazor Web Apps
  5. Add static server-side rendering (SSR) pages to a globally-interactive Blazor Web App
  6. Constructor injection
  7. Websocket compression for Interactive Server components
  8. Handle keyboard composition events in Blazor

Summary

.NET 9 release has brought a lot of new and improvements to the existing features.
The main focus for this release was:

  • to improve the performance of .NET runtime, including WebApplications running on Kestrel, Minimal APIs, Exceptions, EF Core, LINQ and others
  • to extend existing features to support more scenarios
  • add completely new features to make development better and more enjoyable

Note: .NET 9 is STS (Standard Term Support) but Microsoft's stated quality of STS and LTS (Long-Term Support) are the same.
So I see no reason why you shouldn't upgrade to .NET 9.
I have already migrated a lot of services to .NET 9 without a significant effort.

On my website:antondevtips.com I share .NET and Architecture best practices.
Subscribe to become a better developer.
Download the source code for this blog post for free.

Top comments(3)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
stevsharp profile image
Spyros Ponaris
Tech Lead | Senior Software Engineer | .NET Enthusiast (BSc, MSc, MCP, MCSD)Contact Information:https://www.linkedin.com/in/spyros-ponaris-913a6937/
  • Email
  • Location
    Athens
  • Education
    BSc University of Sunderland- MSc University of Greenwich
  • Pronouns
    Tech Lead and Senior Software Engineer with a strong focus on .NET technologies.
  • Work
    Tech Lead | Senior Software Engineer |
  • Joined

Thank you for sharing these valuable tips , they're helpful!

CollapseExpand
 
antonmartyniuk profile image
Anton Martyniuk
Microsoft MVP | Helping Software Engineers improve their Skills in .NET and Architecture, and Craft Better Software from my Newsletter (with source code) and by daily posts on LinkedIn and X (Twitter)
  • Location
    Ukraine
  • Work
    Microsoft MVP | Technical Lead
  • Joined

You're welcome!

CollapseExpand
 
jodydonetti profile image
Jody Donetti
  • Joined

HybridCache is both an abstraction and a Microsoft-provided default implementation.

The abstraction came out with .NET 9 and is GA, whereas the Microsoft-provided implementation is not out yet (it's in preview).

But something else you can try right now is FusionCache: with the v2 release a couple of weeks ago it is now also the first production-ready implementation of HybridCache (but with more features 😬).

Hope this helps.

github.com/ZiggyCreatures/FusionCa...

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Microsoft MVP | Helping Software Engineers improve their Skills in .NET and Architecture, and Craft Better Software from my Newsletter (with source code) and by daily posts on LinkedIn and X (Twitter)
  • Location
    Ukraine
  • Work
    Microsoft MVP | Technical Lead
  • Joined

More fromAnton Martyniuk

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp