efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Improve documentation and exception message for .Navigation()

Open voroninp opened this issue 1 year ago • 7 comments

File a bug

I am getting Navigation was not found. exception when creating a migration.

Include your code

See this repository

Here are the mapped classes:

public sealed class Owner
{
    public int Id { get; private set; }

    private readonly List<Owned> _ownedItems = new List<Owned>();

    public IReadOnlyList<Owned> Items { get; }

    public void AddItem(Owned item)
    {
        _ownedItems.Add(item);
    }

    public Owner()
    {
        Items = _ownedItems.AsReadOnly();
    }
}

public sealed class Owned
{
    public int Id { get; private set; }

    public int OwnerId { get; private set; }

    private readonly List<Owned2> _ownedItems = new List<Owned2>();

    public IReadOnlyList<Owned2> Items { get; }

    public Owned()
    {
        Items = _ownedItems.AsReadOnly();
    }

    public void AddItem(Owned2 item)
    {
        _ownedItems.Add(item);
    }
}

public sealed class Owned2
{
    public int Id { get; private set; }

    public int OwnerId { get; private set; }
}

And here's the mapping part:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Owner>(b =>
    {
        b.Navigation(e => e.Items).HasField("_ownedItems");
        b.OwnsMany(e => e.Items, b =>
        {
            b.WithOwner().HasForeignKey(e => e.OwnerId);
            b.HasKey(e => e.Id);
            // If you comment this out, migrations work.
            b.Navigation(e => e.Items).HasField("_ownedItems");
            b.OwnsMany(e => e.Items, b =>
            {
                b.WithOwner().HasForeignKey(e => e.OwnerId);
                b.HasKey(e => e.Id);
            });
        });
    });
}

While first b.Navigation(e => e.Items).HasField("_ownedItems"); works fine. The one for nested collection causes migration to fail.

Include verbose output

Using project 'C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj'.
Using startup project 'C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj'.
Writing 'C:\Users\pavel\source\repos\voroninp\EfBackingFields\obj\EfBackingFields.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\pavel\AppData\Local\Temp\tmpoi5ukq.tmp /verbosity:quiet /nologo C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj
Writing 'C:\Users\pavel\source\repos\voroninp\EfBackingFields\obj\EfBackingFields.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=C:\Users\pavel\AppData\Local\Temp\tmph2kupa.tmp /verbosity:quiet /nologo C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj
Build started...
dotnet build C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj /verbosity:quiet /nologo /p:PublishAot=false

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.73

Workload updates are available. Run `dotnet workload list` for more information.
Build succeeded.
dotnet exec --depsfile C:\Users\pavel\source\repos\voroninp\EfBackingFields\bin\Debug\net8.0\EfBackingFields.deps.json --additionalprobingpath C:\Users\pavel\.nuget\packages --additionalprobingpath "C:\Program Files (x86)\DevExpress 21.1\Components\Offline Packages" --additionalprobingpath "C:\Program Files\DevExpress 22.2\Components\Offline Packages" --additionalprobingpath "C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages" --runtimeconfig C:\Users\pavel\source\repos\voroninp\EfBackingFields\bin\Debug\net8.0\EfBackingFields.runtimeconfig.json C:\Users\pavel\.dotnet\tools\.store\dotnet-ef\8.0.8\dotnet-ef\8.0.8\tools\net8.0\any\tools\netcoreapp2.0\any\ef.dll migrations add Migration --assembly C:\Users\pavel\source\repos\voroninp\EfBackingFields\bin\Debug\net8.0\EfBackingFields.dll --project C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj --startup-assembly C:\Users\pavel\source\repos\voroninp\EfBackingFields\bin\Debug\net8.0\EfBackingFields.dll --startup-project C:\Users\pavel\source\repos\voroninp\EfBackingFields\EfBackingFields.csproj --project-dir C:\Users\pavel\source\repos\voroninp\EfBackingFields\ --root-namespace EfBackingFields --language C# --framework net8.0 --nullable --working-dir C:\Users\pavel\source\repos\voroninp\EfBackingFields --verbose
Using assembly 'EfBackingFields'.
Using startup assembly 'EfBackingFields'.
Using application base 'C:\Users\pavel\source\repos\voroninp\EfBackingFields\bin\Debug\net8.0'.
Using working directory 'C:\Users\pavel\source\repos\voroninp\EfBackingFields'.
Using root namespace 'EfBackingFields'.
Using project directory 'C:\Users\pavel\source\repos\voroninp\EfBackingFields\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'EfBackingFields'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Found DbContext 'Ctx'.
Using context 'Ctx'.
Microsoft.EntityFrameworkCore.Design.OperationException: Unable to create a 'DbContext' of type ''. The exception 'Navigation 'Owned.Items' was not found. Please add the navigation to the entity type before configuring it.' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
 ---> System.InvalidOperationException: Navigation 'Owned.Items' was not found. Please add the navigation to the entity type before configuring it.
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.Navigation(String navigationName)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.InternalEntityTypeBuilder.Navigation(MemberInfo memberInfo)
   at Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder`2.Navigation[TNavigation](Expression`1 navigationExpression)
   at Ctx.<>c.<OnModelCreating>b__4_3(OwnedNavigationBuilder`2 b) in C:\Users\pavel\source\repos\voroninp\EfBackingFields\Program.cs:line 43
   at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder`1.OwnsMany[TRelatedEntity](Expression`1 navigationExpression, Action`1 buildAction)
   at Ctx.<>c.<OnModelCreating>b__4_0(EntityTypeBuilder`1 b) in C:\Users\pavel\source\repos\voroninp\EfBackingFields\Program.cs:line 38
   at Microsoft.EntityFrameworkCore.ModelBuilder.Entity[TEntity](Action`1 buildAction)
   at Ctx.OnModelCreating(ModelBuilder modelBuilder) in C:\Users\pavel\source\repos\voroninp\EfBackingFields\Program.cs:line 35
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService(IInfrastructure`1 accessor, Type serviceType)
   at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   --- End of inner exception stack trace ---
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Unable to create a 'DbContext' of type ''. The exception 'Navigation 'Owned.Items' was not found. Please add the navigation to the entity type before configuring it.' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

Include provider and version information

EF Core version: 8.0.8 Database provider: does not matter. Fails both for SQL Server and Postgres. Target framework: (e.g. .NET 8.0) Operating system: Windows 11 Pro 23H2 IDE: Visual Studio 2022 17.12 Preview 2.1

voroninp avatar Sep 27 '24 21:09 voroninp

it still repros on current main, however if one swaps the order of OwnsMany and Navigation calls in the nested case (i.e. OwnsMany first then Navigation) things work just fine:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Owner>(b =>
    {
        b.Navigation(e => e.Items).HasField("_ownedItems");
        b.OwnsMany(e => e.Items, b =>
        {
            b.WithOwner().HasForeignKey(e => e.OwnerId);
            b.HasKey(e => e.Id);
            // If you comment this out, migrations work.
            b.Navigation(e => e.Items).HasField("_ownedItems");
            b.OwnsMany(e => e.Items, b =>
            {
                b.WithOwner().HasForeignKey(e => e.OwnerId);
                b.HasKey(e => e.Id);
            });
        });
    });
}

maumar avatar Oct 02 '24 06:10 maumar

This is expected, the relationship needs to be configured (with OwnsMany, HasOne, etc.) before Navigation can be called if it's not discovered by convention.

AndriySvyryd avatar Oct 03 '24 23:10 AndriySvyryd

Ok, thanks, @AndriySvyryd.

So add navigation from the error message means calling Has|OwnsOne|Many. Not that obvious TBH, because it's all about configuring mappings. Maybe you could slightly change the wording of the error message?

And this xmldoc for Navigation method is likely missing a remarks section ;-)

/// <summary>
///     Returns an object that can be used to configure an existing navigation property
///     from the owned type to its owner. It is an error for the navigation property
///     not to exist.
/// </summary>
/// <typeparam name="TNavigation">The target entity type.</typeparam>
/// <param name="navigationExpression">
///     A lambda expression representing the navigation property to be configured (
///     <c>blog => blog.Posts</c>).
/// </param>
/// <returns>An object that can be used to configure the navigation property.</returns>

Is there an analyzer for this, btw?

voroninp avatar Oct 04 '24 04:10 voroninp

@AndriySvyryd , by the way, I see that order: Navigation() and HasMany() does not cause an issue. Looks like it matters only for owned entities.

voroninp avatar Oct 09 '24 09:10 voroninp

@AndriySvyryd , by the way, I see that order: Navigation() and HasMany() does not cause an issue. Looks like it matters only for owned entities.

It does if the navigation is not discovered by convention. E.g. it has [Ignore] on it.

AndriySvyryd avatar Oct 09 '24 17:10 AndriySvyryd

@AndriySvyryd Will public property of any type implementing IEnumerable<TEntity> be treated as navigation?

voroninp avatar Oct 09 '24 17:10 voroninp

@AndriySvyryd Will public property of any type implementing IEnumerable<TEntity> be treated as navigation?

Yes, in most cases

AndriySvyryd avatar Oct 09 '24 17:10 AndriySvyryd

im facing the same problem

Ok, thanks, @AndriySvyryd.

So add navigation from the error message means calling Has|OwnsOne|Many. Not that obvious TBH, because it's all about configuring mappings. Maybe you could slightly change the wording of the error message?

And this xmldoc for Navigation method is likely missing a remarks section ;-)

toanpn avatar May 17 '25 03:05 toanpn

What?! So my question is, what's the point of configuring .Navigation(...) anything, if we still have to do the additional many, one, owns, etc... I was under the impression Navigation somewhat (?) if not entirely absolved the other.

I should also mention, how do we sort this out loading multiple such EF IEntityTypeConfiguration<T> configurations? Definitely not a nested declaration as posited here.

mwpowellhtx avatar Nov 17 '25 00:11 mwpowellhtx

@AndriySvyryd , by the way, I see that order: Navigation() and HasMany() does not cause an issue. Looks like it matters only for owned entities.

Could we clarify, what do we mean "owned" entities? One in which the child should also have bidirectional relationship to the parent?

mwpowellhtx avatar Nov 17 '25 00:11 mwpowellhtx

@mwpowellhtx Owned entity types are the target of navigations configured with Owns*

AndriySvyryd avatar Nov 21 '25 18:11 AndriySvyryd

@mwpowellhtx Owned entity types are the target of navigations configured with Owns*

Yeah, I'm not sure what that MEANS semantically speaking, and last time I checked, defining terms with the same words, is not great practice. How is that any different, or similar, to Having One, or Having Many, With One, and so forth, establishing bidirectional parent-child relationships.

mwpowellhtx avatar Nov 22 '25 16:11 mwpowellhtx

They are conceptually part of the owner. You can read more about them at https://learn.microsoft.com/ef/core/modeling/owned-entities

AndriySvyryd avatar Nov 22 '25 17:11 AndriySvyryd