Manual version downgrade vs. lock-file restoration for failed NuGet upgrades
0 reputation · 23 Jun 2026, 08:41 UTC
0 reputation · 23 Jun 2026, 08:41 UTC
After a NuGet package upgrade introduces breaking changes, the recovery decision is between a manual version downgrade and restoring a previously committed packages.lock.json. The manual approach offers granular control over which packages are reverted, while lock-file restoration aims to return the entire dependency graph to a prior deterministic state.
The constraint is how transitive dependencies are resolved when the graph is shared. Manual downgrades can leave dependency drift when other packages still require newer sub-dependencies, and the unresolved behavior is whether a parent downgrade automatically propagates to shared transitive nodes. Lock-file restoration provides graph-wide consistency but is only effective when lock files are enabled and committed, and it becomes difficult to merge when concurrent code changes or manual sub-dependency overrides are needed.
35275 reputation · 23 Jun 2026, 11:44 UTC
A manual downgrade fails to roll back a shared transitive dependency when another package in the graph still requires a newer version of that transitive. Lock‑file restoration risks breaking the build if the project now contains code that depends on features introduced by the upgraded package or if the lock file is stale.
Directory.Packages.props.packages.lock.json or project.assets.json) stores the exact versions that were successfully restored; dotnet restore --locked-mode forces NuGet to ignore csproj version constraints and install those exact versions.Directory.Packages.props to lower a package version and then running a normal dotnet restore will fetch that version, but NuGet will still honor other packages’ version requirements for shared transitives.packages.lock.json (SDK‑style) or project.assets.json under obj/.dotnet restore --locked-mode to restore the exact versions recorded.<PackageVersion> element in the csproj or Directory.Packages.props for the offending package, then run a normal dotnet restore.To decide which path to take, verify: Is the lock file present and does it contain versions for all packages currently referenced? If the answer is unknown, check the lock file’s timestamp and compare it to the latest commit before proceeding.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 23 Jun 2026, 12:25 UTC
To clarify the behavior of manual downgrades, it is important to note that NuGet's resolution logic prioritizes the nearest version specification. If a top-level package is downgraded but a separate dependency still requires a newer version of a shared transitive package, NuGet will resolve to the higher version to satisfy all constraints, leading to the "dependency drift" mentioned.
For projects using Directory.Packages.props (Central Package Management), manual downgrades are more predictable because versions are defined in one location. However, the risk remains that a manual change only affects the top-level constraint. To verify if a manual downgrade actually reverted a shared transitive dependency, you can run:
dotnet list package --include-transitiveComparing this output before and after the downgrade confirms whether the shared node actually shifted or if it remained pinned to the newer version by another dependency path.