Our Journey to .NET 7

logo
Admir Mujkic CTO @ Penzle
a robot with a light bulb

These days most engineers are working on upgrading their code base from .NET Framework to .NET 7. I believe you are not alone, and almost every engineer has experienced something similar at some point in their career. This post is part guide, part how-to. The main focus here is on upgrading the back-end to .NET 7, but this will be useful if you need to upgrade any .NET Framework code to .NET 7. Of course, if you do have many breaking changes, this could be very painful if your team does not have proper unit, functional, and other tests that will save your life. :-)

Let's start

You might have an older legacy system running the .NET Framework 4.X.X. It's probably a confused mess with .NET Framework, .NET Standard, and .NET Core libraries crowded. Some of your code may be ready to run, while others may need to be first solved by breaking changes. From personal experience, it is best to take things slowly at the outset. As a result, it is necessary first to understand the relationship between your projects and domain and then proceed in that order.

Keep going even if your project can't be compiled right away. It's pretty standard. For instance, in 99% of my cases, it was not compiled the first time. As a result, you must begin with a project without any dependencies on others. It's prevalent to call it Common, Shared, SharedKernel, Kernel, or whatever, but your projects mustn't have any internal dependencies.

Review your project architecture

One of the most important things is that you will have to look at each project individually and make crucial decisions. The good news is that we often don't have time for comprehensive codebase analysis, so you may find that you don't need some code elements. You will be able to remove them.

However, you must decide what to do with the necessary code and, most importantly, consult with the original code owners if they can help you make a decision. Another option is to look through the references and see where the potential code is used to find the source of the problem.

Under refactoring the code base, existing code has to be upgraded to run in a new environment under .NET 7. With multi-targeting, it will most likely also target the .NET Framework. As a result, you'll have to rewrite some things, which means writing the code from scratch. The new code is unlikely to run on the .NET Framework, so the only goal is to write the code that will run under .NET 7.

Let's start with the first step

Let's assume that we have a relatively simple architecture that is divided into multiple projects, like in the image below. The first step is to find the root project without dependencies marked with red and try to first work on it. The first step will be downloading the official .NET 7 SDK from the Microsoft site. https://dotnet.microsoft.com/en-us/download/dotnet/7.0

diagram

Open project.csproj and make the following adjustments to the target framework:

<PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

In the event that you are using containers, you will need to upgrade your base images, such as:

FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
...

This means we'll be using BCL from .NET 7, but the most challenging part is figuring out which dependencies we have under this project and attempting to upgrade them. If a specific dependency does not yet support .NET 7, you must wait for the library's author to make it compatible, or you can remove it if you believe your project can work without it.

Once you have completed this section and you are able to successfully compile it, it is time to repeat the process for each project, but first, you must verify the finished project using your test project(s), which also means you must upgrade it to .NET 7 and all dependencies.

diagram

Solve breaking changes

As previously explained, using a newer .NET version, such as .NET 7, will cause compilation errors and issues. Some .NET Framework libraries could be incompatible in .NET 7 edition. So the bad news is that you must resolve all code issues that come up in your code base, but this is also an opportunity for you to take ownership of this piece of code and better understand what it is doing for you.

Let's imagine that the library you use in the .NET Framework is not accessible in .NET 7. In that case, you will need to find an alternative or write the code yourself. The fastest option is to find a more recently supported library and replace the existing code with a new one. Otherwise, you'll have to use #if statements to make a distinctive code base to target different paths.

#if NETSTANDARD2_0
   ...
#else
   ...
#endif

You may also use partial classes, with the main class in one file and two different versions for .NET 7 and other .NET Frameworks. You can, based on conditions, include or exclude files dependent on the framework target. Pretty much the same story for the NuGet package.

What about your database?

This may be the most difficult phase of your trip to .NET 7. Use Dapper or another (O)RM like RepoDB. There is probably not much difference between the older .NET Frameworks and .NET 6 or .NET 7. Entity Framework (Classic), on the other hand, is not the same as Entity Framework Core. Some EF will compile for EF Core, yet it will provide different results and run different SQL. This is why testing is critical. You should write a set of database tests to check that the new .NET 7 database access code accomplishes the same as the .NET Framework code.

diagram

Branch strategy during the upgrade?

The reality is that you must supply new features, and the world cannot stop till you finish your update. Therefore, you must design a really practical approach in terms of branching strategy. This is very dependent on team size and whether the project architecture is monolithic, hexagonal, microservice or even specific for your organization; however, I can share some of my advices.

application

Based on my experience, the ideal approach is to prioritize projects that are critical to your organization and improve them first. Of course, this is risky, but you want to reduce the amount of time you spend working on code that is not in production since you have to deliver .NET 7 to your clients as soon as possible, at least the most used features. If you keep your code on a branch for a long time, it will become non-production code, and you will have to deal with more and more merge conflicts. Trust me - this is a nightmare for engineers and a paradise for bugs.

Conclusion

I would love to share a magic recipe to solve this problem with you, but there is no such thing. However, if your team does not communicate, understand the decisions, and make guesses without strong arguments, you will be very close to feeling pain.

We tested after the upgrade and noticed immediate improvements. The diagrams below show that we can handle >63K RPS in less than 20 minutes.

chart, line chart

If you believe we can assist you, please contact us and we would love to share our experience, especially since we are already running on .NET 7.

Best Luck!