Anyone who has worked on a large .NET repository knows the dread of opening a merge conflict inside a .sln file. Two developers add a project on separate branches, git tries to reconcile a wall of GUIDs it cannot understand, and you end up hand editing configuration blocks you barely recognize. Microsoft has finally addressed this with .slnx, an XML based solution format that ships as part of the .NET 9 and .NET 10 tooling. This article walks through what changes, how to migrate an existing solution, and what to watch out for before you roll it out across a team.
Why the classic .sln format causes pain
The .sln format has barely changed since early Visual Studio versions. Every project entry carries a type GUID and its own instance GUID, and the Global section repeats those GUIDs again for every build configuration and platform combination. On a moderately sized solution with ten or fifteen projects, this file easily runs past a hundred lines of near identical GUID mappings.
Here is a trimmed excerpt from a real .sln file, showing just two projects and their configuration mappings. Notice how the same GUID appears four separate times for a single project, once for each Debug and Release, ActiveCfg and Build combination.
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "src\Domain\Domain.csproj", "{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "src\Application\Application.csproj", "{0F576D4A-156D-4626-A4D5-83DD0F6FAFE7}"
EndProject
Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6448ADE8-34BC-4F2F-A68C-5B2D6BF4FB0B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
None of these GUIDs are meant to be read by a human. They exist so Visual Studio can track project identity internally, but the format forces every developer touching the file to scroll past them anyway. When two branches add or remove a project around the same location, git has no semantic understanding of the structure, so it flags a conflict even in cases where the actual intent does not overlap at all.
What the .slnx format looks like instead
The new .slnx format drops the GUID bookkeeping and represents the same solution as plain XML, structured around Folder and Project elements. It reads a lot like a .csproj file, which makes sense since Microsoft aligned the two formats deliberately. Here is the equivalent of the solution excerpt above, rewritten in .slnx.
<Solution>
<Folder Name="/src/">
<Project Path="src/Application/Application.csproj" />
<Project Path="src/Domain/Domain.csproj" />
<Project Path="src/Infrastructure/Infrastructure.csproj" />
<Project Path="src/SharedKernel/SharedKernel.csproj" />
<Project Path="src/Web.Api/Web.Api.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ArchitectureTests/ArchitectureTests.csproj" />
</Folder>
<Project Path="docker-compose.dcproj">
<Build />
</Project>
</Solution>
Compare this to the earlier excerpt. There are no per configuration GUID mappings at all, because .slnx assumes Debug and Release apply uniformly unless you explicitly override them for a project. Adding a new project is a single line inside the relevant Folder element, which is exactly the kind of change git merges cleanly without help.
Migrating an existing solution from the command line
If your team already has the .NET 9 SDK (specifically 9.0.200 or later) or .NET 10 installed, migration is a single CLI command run from the folder containing your .sln file.
dotnet sln migrate
This command reads your existing .sln, generates a new .slnx file with the same name alongside it, and leaves the original .sln untouched. Expect the command to finish in well under a second even on solutions with dozens of projects, since there is no build involved, just a format conversion. Once you have verified the new file opens correctly in Visual Studio, Rider, or the CLI, delete the old .sln. Keeping both files in the same repository defeats the purpose, because most tools will pick whichever one comes first alphabetically, and your team will end up editing the wrong one without noticing.
Migrating through the Visual Studio GUI
If you would rather avoid the terminal, Visual Studio 2022 (17.13 and later) and Visual Studio 2026 support the same conversion through the Save As dialog. Select the solution node in Solution Explorer, then go to File, Save Solution As.

In the Save As dialog, change the Save as type dropdown from the default Visual Studio Solution File to Xml Solution File (*.slnx). Visual Studio writes out the new file in the same location and keeps the original .sln in place, so you get the same safety net as the CLI approach.

Why this change is worth adopting
The headline benefit is fewer merge conflicts, and in practice this is the reason most teams will care. Because the file is plain XML without random GUIDs regenerating on every save, git diffs stay small and semantic, showing exactly which project or folder changed rather than a wall of noise.
- Human readable: you can open a .slnx in any text editor, understand the structure immediately, and make a small edit without risking a corrupted solution.
- Format consistency: .slnx finally aligns the solution file with the simplified .csproj format that shipped years ago, so the whole toolchain speaks the same XML dialect.
- Faster loads on large solutions: smaller file size and simpler parsing shave a bit of time off solution load, which is noticeable once you cross fifty or so projects.
- Fewer accidental GUID collisions: since there is nothing to regenerate, tools that used to occasionally corrupt a .sln during automated refactors have one less failure mode.
Should you adopt it now
As of this writing, .slnx is technically still marked as a preview feature, though the file format itself is stable and not expected to change in breaking ways. Visual Studio 2022, Visual Studio 2026, Rider, and the .NET CLI all handle it correctly. The gap to watch is older CI or build tooling, some of which still hardcodes a search for files ending in .sln and will silently skip a .slnx file sitting right next to it.
My practical recommendation is to try the migration on a side project or a feature branch first, rather than converting your main branch directly. Run your full CI pipeline against the .slnx file before merging anything. If your Azure DevOps or GitHub Actions jobs invoke dotnet build or dotnet test against the solution rather than an individual project, they generally pick up .slnx automatically, since the SDK resolves the solution file by convention. Where teams get caught out is with older MSBuild based release pipelines or third party static analysis tools that were configured with an explicit .sln path; those need a manual update once you drop the old file.
For new projects, there is little reason to hold back. Several teams I have worked with have already switched every new solution to .slnx by default and migrated a handful of existing repositories without issues once CI was verified. For an established production monorepo with a complex release pipeline, it is worth budgeting a small amount of testing time before removing the .sln file, simply because the blast radius of a broken build pipeline is larger than the convenience the new format buys you in the short term.
Leave a Reply