Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EF Core 6 Unable to set up a many-to-many relationship between '' and '' because one or both of the navigations don't have a corresponding CLR property #26721

Closed
ngoov opened this issue Nov 17, 2021 · 4 comments

Comments

@ngoov
Copy link

ngoov commented Nov 17, 2021

I am using a many to many relation in my app that has a navigation property (collection) only on 1 side and using a join table.
After updating the nuget packages of EF Core to 6.0.0 from 5.0.12, I am receiving this error when I am trying to add a new migration using dotnet ef migrations add test.

I have reproduced my problem in this small console app written in .NET 6:

// See https://aka.ms/new-console-template for more information
using Microsoft.EntityFrameworkCore;

Console.WriteLine("Hello, World!");

public class Context : DbContext
{
    public DbSet<Client> Clients { get; set; }

    public string DbPath { get; private set; }

    public Context()
    {
        var folder = Environment.SpecialFolder.LocalApplicationData;
        var path = Environment.GetFolderPath(folder);
        DbPath = $"{path}{System.IO.Path.DirectorySeparatorChar}context.db";
    }

    // The following configures EF to create a Sqlite database file in the
    // special "local" folder for your platform.
    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite($"Data Source={DbPath}");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Client>().HasMany(x => x.Claims).WithMany("Clients").UsingEntity<ClientClaim>(
            e => e.HasOne(x => x.Claim).WithMany(),
            e => e.HasOne(x => x.Client).WithMany(),
            join =>
            {
                join.ToTable("clients_claims");
                join.HasKey($"{nameof(Client)}Id", $"{nameof(Claim)}Id");
            });
    }
}

public class Client
{
    public Guid Id { get; set; }
    public ICollection<Claim> Claims { get; set; }
}

public class Claim
{
    public Guid Id { get; set; }
}

public class ClientClaim
{
    public Client Client { get; set; }
    public Claim Claim { get; set; }
}

Using the EF Core 5.0.12 packages, the output is as expected and the migration is created:

$ dotnet ef migrations add initial
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'

When I update the EF Core nuget packages to 6.0.0, the following error is thrown (I have removed some file paths in the output for privacy reasons):

dotnet ef migrations add initial --verbose
Using project '\EFCore6ManyToMany.csproj'.
Using startup project '\EFCore6ManyToMany.csproj'.
Writing '\obj\EFCore6ManyToMany.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=\tmpEE8D.tmp /verbosity:quiet /nologo \EFCore6ManyToMany.csproj
Writing '\obj\EFCore6ManyToMany.csproj.EntityFrameworkCore.targets'...
dotnet msbuild /target:GetEFProjectMetadata /property:EFProjectMetadataFile=\tmpF0A1.tmp /verbosity:quiet /nologo \EFCore6ManyToMany.csproj
Build started...
dotnet build \EFCore6ManyToMany.csproj /verbosity:quiet /nologo
\Program.cs(51,19): warning CS8618: Non-nullable property 'Client' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
\Program.cs(52,18): warning CS8618: Non-nullable property 'Claim' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
\Program.cs(41,31): warning CS8618: Non-nullable property 'Claims' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
\Program.cs(12,12): warning CS8618: Non-nullable property 'Clients' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]

Build succeeded.

\Program.cs(51,19): warning CS8618: Non-nullable property 'Client' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
\Program.cs(52,18): warning CS8618: Non-nullable property 'Claim' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
\Program.cs(41,31): warning CS8618: Non-nullable property 'Claims' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
\Program.cs(12,12): warning CS8618: Non-nullable property 'Clients' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [\EFCore6ManyToMany.csproj]
    4 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.10
Build succeeded.
dotnet exec --depsfile \bin\Debug\net6.0\EFCore6ManyToMany.deps.json --additionalprobingpath C:\Users\ --additionalprobingpath "C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages" --additionalprobingpath "C:\Program Files (x86)\Microsoft\Xamarin\NuGet" --additionalprobingpath "C:\Program Files\dotnet\sdk\NuGetFallbackFolder" --runtimeconfig \bin\Debug\net6.0\EFCore6ManyToMany.runtimeconfig.json C:\Users\\.store\dotnet-ef\6.0.0\dotnet-ef\6.0.0\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll migrations add initial --assembly \bin\Debug\net6.0\EFCore6ManyToMany.dll --project \EFCore6ManyToMany.csproj --startup-assembly \bin\Debug\net6.0\EFCore6ManyToMany.dll --startup-project \EFCore6ManyToMany.csproj --project-dir \ --root-namespace EFCore6ManyToMany --language C# --framework net6.0 --nullable --working-dir  --verbose
Using assembly 'EFCore6ManyToMany'.
Using startup assembly 'EFCore6ManyToMany'.
Using application base '\bin\Debug\net6.0'.
Using working directory ''.
Using root namespace 'EFCore6ManyToMany'.
Using project directory '\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Finding application service provider in assembly 'EFCore6ManyToMany'...
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 'Context'.
Using context 'Context'.
System.InvalidOperationException: Unable to set up a many-to-many relationship between 'Claim.Clients' and 'Client.Claims' because one or both of the navigations don't have a corresponding CLR property. Consider adding a corresponding private property to the entity CLR type.
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateRelationships(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal.SqliteModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
   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(Type serviceType, 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[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
   at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory)
   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 set up a many-to-many relationship between 'Claim.Clients' and 'Client.Claims' because one or both of the navigations don't have a corresponding CLR property. Consider adding a corresponding private property to the entity CLR type.

EF Core version: 6.0.0
Database provider: Any. This test app uses sqlite, my own project uses npgsql and mssqlserver
Target framework: .NET 6
Operating system: Windows 10 Pro
IDE: Any, using command line tool

@ajcvickers
Copy link
Member

@ngoov This is a duplicate of #23362. Unidirectional many-to-many relationships (tracked by #3864) are not supported. However, the workaround is easy: create a private navigation property for the inverse. See #3864 (comment).

@ngoov
Copy link
Author

ngoov commented Nov 18, 2021

@ajcvickers Thanks for the reply. I will try it with the workaround.
Any idea though why it was working as expected in EF Core 5?

@ajcvickers
Copy link
Member

@ngoov It wasn't really working; but we didn't validate the input and some things worked. So, depending what you did with the relationship, it would work, but not reliably.

@ngoov
Copy link
Author

ngoov commented Nov 18, 2021

@ajcvickers Ok, thanks for the explanation. In the meanwhile, I can confirm that the workaround is working as expected, so I'll close this issue.
I will follow #3864 and hope it is coming to EF Core 7!

@ngoov ngoov closed this as completed Nov 18, 2021
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants