Skip to main content

How to Upgrade a Transitive NuGet Package with Central Package Management (Transitive Pinning)

· 6 min read
Jagdish Kumawat
Founder @ Dewiride

You don't only get the packages you reference: each one brings its own dependencies, the transitive packages, in whatever version it asked for. Oracle's MySQL provider for EF Core asks for EF Core 10.0.12 or later, and NuGet gives it exactly 10.0.12, even though 10.0.14, with two patch releases of fixes, is out. With Central Package Management, one setting lets you raise that version from the central file. This guide finds the transitive version, raises it, and checks the result.

Prerequisites​

Step 1: See the Transitive Packages​

dotnet package list shows only what you reference, unless you ask for more:

Terminal
dotnet package list --include-transitive --project src/Dewiride.Inventory.Data
Output (first part)
Project 'Dewiride.Inventory.Data' has the following package references
[net10.0]:
Top-level Package Requested Resolved
> MySql.EntityFrameworkCore 10.0.12 10.0.12

Transitive Package Resolved
> BouncyCastle.Cryptography 2.6.2
> Google.Protobuf 3.35.0
> K4os.Compression.LZ4 1.3.8
> K4os.Compression.LZ4.Streams 1.3.8
> K4os.Hash.xxHash 1.0.8
> Microsoft.EntityFrameworkCore 10.0.12
> Microsoft.EntityFrameworkCore.Abstractions 10.0.12
> Microsoft.EntityFrameworkCore.Analyzers 10.0.12
> Microsoft.EntityFrameworkCore.Relational 10.0.12
> Microsoft.Extensions.Caching.Abstractions 10.0.12

A terminal where dotnet package list --include-transitive shows one top-level package, MySql.EntityFrameworkCore 10.0.12, and transitive packages including Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.Relational at 10.0.12.

One package referenced, a long list underneath, and EF Core at 10.0.12.

Why NuGet Picks the Older Version​

The provider's dependency is "10.0.12 or later", and NuGet deliberately takes the bottom of that range. Its dependency resolution page calls it the lowest applicable version rule: it "restores the lowest possible version of a package as defined by its dependencies." That makes restores repeatable, but it means patch releases of transitive packages never arrive on their own.

Step 2: Pin the Transitive Package​

The NuGet docs describe the feature: "You can automatically override a transitive package version without an explicit top-level <PackageReference /> item by opting into a feature known as transitive pinning." Two changes in Directory.Packages.props: switch it on, and give the package a version.

Directory.Packages.props
<Project>
<PropertyGroup>
<!-- Enable central package management, https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management -->
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- Let a PackageVersion below also set the version of a package that only comes in transitively. -->
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.14" />
<PackageVersion Include="MySql.EntityFrameworkCore" Version="10.0.12" />
<!-- MySql.EntityFrameworkCore 10.0.12 asks for EF Core 10.0.12 or later; use the newest 10.0 patch. -->
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.14" />
</ItemGroup>
</Project>
  • CentralPackageTransitivePinningEnabled lets a PackageVersion apply to packages that no project references directly.
  • The PackageVersion for Microsoft.EntityFrameworkCore.Relational is the pin. One is enough here: version 10.0.14 of the relational package itself requires EF Core 10.0.14, so the rest of EF Core follows.
  • The comment says why the line exists, so nobody removes it as unused.

The project files don't change.

Step 3: Check the Result​

Terminal
dotnet package list --include-transitive --project src/Dewiride.Inventory.Data
Output (first part)
Top-level Package Requested Resolved
> MySql.EntityFrameworkCore 10.0.12 10.0.12

Transitive Package Resolved
> BouncyCastle.Cryptography 2.6.2
> Google.Protobuf 3.35.0
> K4os.Compression.LZ4 1.3.8
> K4os.Compression.LZ4.Streams 1.3.8
> K4os.Hash.xxHash 1.0.8
> Microsoft.EntityFrameworkCore 10.0.14
> Microsoft.EntityFrameworkCore.Abstractions 10.0.14
> Microsoft.EntityFrameworkCore.Analyzers 10.0.14
> Microsoft.EntityFrameworkCore.Relational 10.0.14
> Microsoft.Extensions.Caching.Abstractions 10.0.14

A terminal where dotnet package list --include-transitive, after pinning, shows Microsoft.EntityFrameworkCore, its Abstractions, Analyzers and Relational packages and Microsoft.Extensions.Caching.Abstractions at 10.0.14.

Every EF Core package is now 10.0.14, and so are the Microsoft.Extensions packages they depend on. The provider is still 10.0.12: only its dependencies moved.

You Can Raise, Not Lower​

The docs are explicit: "Note that downgrades are not allowed when transitive pinning a package. If you attempt to pin a package to a lower version than the one requested by your dependencies, restore will raise a NU1109 error." I tried pinning the relational package to 10.0.11, below the provider's 10.0.12:

Output (dotnet restore, the error)
error NU1109: Detected package downgrade: Microsoft.EntityFrameworkCore.Relational from 10.0.12 to centrally defined 10.0.11. Update the centrally managed package version to a higher version.
error NU1109: Dewiride.Inventory.Data -> MySql.EntityFrameworkCore 10.0.12 -> Microsoft.EntityFrameworkCore.Relational (>= 10.0.12)
error NU1109: Dewiride.Inventory.Data -> Microsoft.EntityFrameworkCore.Relational (>= 10.0.11)

The two lines under the message show the conflict: the provider needs 10.0.12 or later, the pin says 10.0.11. Fixed: "NU1109: Detected package downgrade" covers the error.

The Alternative: Reference It Directly​

Without transitive pinning, you can make the package a direct dependency instead: a <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" /> in the project, plus its PackageVersion. A direct reference always wins over the versions its neighbours ask for. That works with or without Central Package Management; transitive pinning keeps the project files free of packages the code doesn't use itself.

Common Mistakes​

MistakeWhat happensDo this instead
Adding a PackageVersion for a transitive package without enabling pinningIt's ignored: with the setting off, EF Core stayed at 10.0.12Set CentralPackageTransitivePinningEnabled
Pinning below what a dependency requiresRestore fails with NU1109Pin at or above the required version
Pinning across a major versionThe provider may not work with itStay within the major version your packages support
Pins without commentsNobody knows later why the line is thereSay why next to each pin

FAQ​

When should I pin a transitive package?​

When a newer patch fixes a bug or a security advisory that affects you, or when your rules say every package runs on its newest patch. dotnet package list --vulnerable --include-transitive checks the transitive packages against known advisories; for this project it answered "The given project Dewiride.Inventory.Data has no vulnerable packages given the current sources."

Do pins need maintenance?​

Yes. When the provider itself moves to a newer EF Core, a pin can become pointless. Check them whenever you update packages, and remove the ones the provider has caught up with.

Conclusion​

Transitive packages arrive at the lowest version your dependencies accept, so their patches don't reach you on their own. In a solution with Central Package Management, CentralPackageTransitivePinningEnabled plus a PackageVersion for the transitive package raises it for every project, and dotnet package list --include-transitive shows the result. You can raise a transitive version, never lower it below what a dependency requires.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.