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

Relational model builder: Consistently map shadow FK properties #7240

Closed
HappyNomad opened this issue Dec 13, 2016 · 3 comments
Closed

Relational model builder: Consistently map shadow FK properties #7240

HappyNomad opened this issue Dec 13, 2016 · 3 comments
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@HappyNomad
Copy link

HappyNomad commented Dec 13, 2016

The documentation says that I'm not required to have FK properties in my model, and instead I can rely on the model builder to generate them as shadow properties. This works in most cases, but I've discovered an inconsistency, demonstrated by the below Scenario 4, regarding how those shadow properties are mapped to the database.

I kept the below pseudocode concise for readability, so first note the following:

  • Parent and Child entities each have an Id property.
  • Every property has an auto-implemented getter and setter.
  • Entities and their properties are public.
  • No navigations exist from dependant back to principle.
  • Mappings are by convention except where the fluent API is mentioned.
  • Disjoint child subclasses 1 and 2 directly derive from a common base class that's also mapped.

Scenario 1

class Parent { IList<Child> A; IList<Child> B; }

Generates ParentId and ParentId1 shadow FK properties on Child. These then map to respective FKs in the database.

Scenario 2

class Parent { IList<DisjointChildSubclass1> A; IList<DisjointChildSubclass2> B; }

Generates a ParentId shadow FK property for each subclass. These then map to a single FK in the database.

Scenario 3

class Parent { Child A; IList<Child> B; }

Requires fluent API to specify correct dependant. Specifying a ParentId shadow FK property on Child to correspond to A renames the shadow property corresponding to B to ParentId1. These then map to respective FKs in the database.

Scenario 4

class Parent { DisjointChildSubclass1 A; IList<DisjointChildSubclass2> B; }

Requires fluent API to specify correct dependant. Specifying a ParentId shadow FK property on DisjointChildSubclass1 to correspond to A throws an exception (see below). This is inconsistent and unnecessary; it's better to resolve the problem like in the other scenarios. Resolve it by mapping the two ParentId shadow properties to separate ParentId and ParentId1 FKs in the database.

public class BloggingContext : DbContext
{
	public BloggingContext( DbContextOptions<BloggingContext> options )
		: base( options ) { }

	public DbSet<Parent> Parents { get; set; }
	public DbSet<Child> Children { get; set; }

	protected override void OnModelCreating( ModelBuilder modelBuilder )
	{
		modelBuilder.Entity<Parent>()
			.HasOne( p => p.A )
			.WithOne()
			.HasForeignKey<DisjointChildSubclass1>( "ParentId" );
	}
}

public class Parent
{
	public int Id { get; set; }
	public DisjointChildSubclass1 A { get; set; }
	public IList<DisjointChildSubclass2> B { get; set; }
}

public abstract class Child
{
	public int Id { get; set; }
}

public class DisjointChildSubclass1 : Child { }

public class DisjointChildSubclass2 : Child { }

I just restored my EF-Core reference from the dev feed to 1.2.0-preview1-22878 and retested all scenarios. The exception for Scenario 4 says:

System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message= The foreign keys {ParentId} on DisjointChildSubclass2 and {ParentId} on DisjointChildSubclass1 are both mapped to Children.FK_Children_Parents_ParentId but with different uniqueness.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ShowError(String message)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.EnsureSharedForeignKeysCompatibility(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.SqlServerModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass19_0.b__0(Object k)
at System.Collections.Concurrent.ConcurrentDictionary'2.GetOrAdd(TKey key, Func'2 valueFactory)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
at Microsoft.EntityFrameworkCore.Internal.LazyRef'1.get_Value()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServiceCollectionExtensions.<>c.b__0_6(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactoryService(FactoryService factoryService, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor'2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor'2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor'2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor'2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(IServiceCallSite callSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass16_0.b__0(ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.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.Storage.DatabaseProviderServices.GetServiceTService
at Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerDatabaseProviderServices.get_RelationalDatabaseCreator()
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabaseProviderServices.get_Creator()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServiceCollectionExtensions.<>c.b__0_13(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactoryService(FactoryService factoryService, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor'2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor'2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(IServiceCallSite callSite, ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.<>c__DisplayClass16_0.b__0(ServiceProvider provider)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure'1 accessor)
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.get_DatabaseCreator()
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.EnsureDeleted()
at EFGetStarted.AspNetCore.NewDb.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, BloggingContext dataContext) in C:\Users\Adrian\Development\Examples\ASP.NET Core\EFGetStarted.AspNetCore.NewDb\src\EFGetStarted.AspNetCore.NewDb\Startup.cs:line 58
InnerException:

@smitpatel
Copy link
Member

The error is arising because, the user specified shadow FK property matched with conventional shadow FK in the other subclass, making it to share column in the database though the both FK have different uniqueness.
We should not throw exception for such case, instead change the FK property for the other relationship if the uniqueness is difference. Exception should be thrown only if user explicitly map FK to the same property for relationships with different cardinality.

@HappyNomad - As a work-around you can specify the FK property (different from ParentId) for the other relationship and it would work.

@divega divega added the type-bug label Jan 4, 2017
@divega divega added this to the 2.0.0 milestone Jan 4, 2017
@ajcvickers ajcvickers modified the milestones: 2.0.0-preview1, 2.0.0 Apr 19, 2017
@smitpatel
Copy link
Member

can I poach this?

@smitpatel smitpatel assigned smitpatel and unassigned AndriySvyryd Apr 28, 2017
@AndriySvyryd
Copy link
Member

Poaching this back

AndriySvyryd added a commit that referenced this issue Jun 14, 2017
Use the provider model validator in the ModelBuilding tests
Fix and Refactor RelationalModelValidator.ValidateSharedTableCompatibility

Fixes #7240
AndriySvyryd added a commit that referenced this issue Jun 15, 2017
Use the provider model validator in the ModelBuilding tests
Fix and Refactor RelationalModelValidator.ValidateSharedTableCompatibility

Fixes #7240
AndriySvyryd added a commit that referenced this issue Jun 20, 2017
Use the provider model validator in the ModelBuilding tests
Fix and Refactor RelationalModelValidator.ValidateSharedTableCompatibility

Fixes #7240
AndriySvyryd added a commit that referenced this issue Jun 20, 2017
Use the provider model validator in the ModelBuilding tests
Fix and Refactor RelationalModelValidator.ValidateSharedTableCompatibility

Fixes #7240
@AndriySvyryd AndriySvyryd removed their assignment Jun 20, 2017
@AndriySvyryd AndriySvyryd added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jun 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

5 participants