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

More than one ForeignKeyAttribute pointing to same property causes stackoverflow #3799

Closed
darkurse opened this issue Nov 19, 2015 · 11 comments
Closed
Assignees
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

@darkurse
Copy link

Hi,

I have just upgraded my project to RC1 and have now a few issues with my DbContext setup.
I actually have two issues that are described in the code below :

void ConfigureServices(IServiceCollection services)
{
    services
        .AddEntityFramework()
        .AddSqlite()
        .AddDbContext<MyDbContext>(options => options.UseSqlite(MyConnectionString));
    services.AddScoped(typeof(IMyDbContext), typeof(MyDbContext));
}

void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
    using (MyDbContext db = app.ApplicationServices.GetService<MyDbContext>())
    {
        // The following line gives me a
        // StackOverflowException
        db.Database.EnsureCreated();

        // Alternatively, if I omit the above line and do the following line, I get an 
        // ObjectDisposedException
        var result = db.MyDbSet.FirstOrDefault();
    }
}

For more information, the ObjectDisposedException gives me the following call stack :

at Microsoft.Data.Entity.DbContext.get_ServiceProvider()
at Microsoft.Data.Entity.DbContext.Microsoft.Data.Entity.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
at Microsoft.Data.Entity.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
at Microsoft.Data.Entity.Internal.InternalDbSet`1.<.ctor>b__2_0()
at Microsoft.Data.Entity.Internal.LazyRef`1.get_Value()
at Microsoft.Data.Entity.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
at MyNamespace.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)

[Edit]
Regarding the StackOverflowException, this might be interesting (or not)

[Managed to Native Transition]  
>mscorlib.dll!System.Globalization.CompareInfo.Compare(string string1, string string2, System.Globalization.CompareOptions options) Unknown
mscorlib.dll!string.CompareTo(string strB)  Unknown
mscorlib.dll!System.Collections.Generic.GenericComparer<string>.Compare(string x, string y) Unknown
System.dll!System.Collections.Generic.SortedDictionary<string, Microsoft.Data.Entity.Metadata.Internal.EntityType>.KeyValuePairComparer.Compare(System.Collections.Generic.KeyValuePair<string, Microsoft.Data.Entity.Metadata.Internal.EntityType> x, System.Collections.Generic.KeyValuePair<string, Microsoft.Data.Entity.Metadata.Internal.EntityType> y)   Unknown
System.dll!System.Collections.Generic.SortedSet<System.Collections.Generic.KeyValuePair<string, Microsoft.Data.Entity.Metadata.Internal.EntityType>>.FindNode(System.Collections.Generic.KeyValuePair<string, Microsoft.Data.Entity.Metadata.Internal.EntityType> item) Unknown
System.dll!System.Collections.Generic.SortedDictionary<string, Microsoft.Data.Entity.Metadata.Internal.EntityType>.TryGetValue(string key, out Microsoft.Data.Entity.Metadata.Internal.EntityType value)    Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.Model.FindEntityType(string name)  Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.InternalModelBuilder.Entity.AnonymousMethod__0()   Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.MetadataDictionary<Microsoft.Data.Entity.Metadata.Internal.EntityType, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder>.GetOrAdd(System.Func<Microsoft.Data.Entity.Metadata.Internal.EntityType> getKey, System.Func<Microsoft.Data.Entity.Metadata.Internal.EntityType> createKey, System.Func<Microsoft.Data.Entity.Metadata.Internal.EntityType, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder> createValue, System.Func<Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder> onNewKeyAdded, Microsoft.Data.Entity.Metadata.Internal.ConfigurationSource configurationSource)    Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.InternalModelBuilder.Entity(string name, Microsoft.Data.Entity.Metadata.Internal.ConfigurationSource configurationSource)  Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.CreateForeignKey(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder principalEntityTypeBuilder, System.Collections.Generic.IReadOnlyList<Microsoft.Data.Entity.Metadata.Internal.Property> dependentProperties, System.Collections.Generic.IReadOnlyList<Microsoft.Data.Entity.Metadata.Internal.Property> principalProperties, string navigationToPrincipalName, bool? isRequired, Microsoft.Data.Entity.Metadata.Internal.ConfigurationSource configurationSource, bool runConventions) Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.Relationship(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder principalEntityTypeBuilder, Microsoft.Data.Entity.Metadata.Internal.ConfigurationSource configurationSource)  Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder.Relationship(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder targetEntityTypeBuilder, System.Reflection.PropertyInfo navigationToTarget, System.Reflection.PropertyInfo inverseNavigation, Microsoft.Data.Entity.Metadata.Internal.ConfigurationSource configurationSource)    Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Conventions.Internal.RelationshipDiscoveryConvention.CreateRelationships(System.Collections.Generic.IReadOnlyList<Microsoft.Data.Entity.Metadata.Conventions.Internal.RelationshipDiscoveryConvention.RelationshipCandidate> relationshipCandidates, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder entityTypeBuilder)   Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Conventions.Internal.RelationshipDiscoveryConvention.Apply(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder entityTypeBuilder) Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Conventions.Internal.RelationshipDiscoveryConvention.Apply(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder sourceEntityTypeBuilder, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder targetEntityTypeBuilder, string navigationName) Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Conventions.Internal.ConventionDispatcher.OnNavigationRemoved(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder sourceEntityTypeBuilder, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder targetEntityTypeBuilder, string navigationName)  Unknown
EntityFramework.Core.dll!Microsoft.Data.Entity.Metadata.Internal.InternalRelationshipBuilder.Relationship(Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder principalEntityTypeBuilder, Microsoft.Data.Entity.Metadata.Internal.InternalEntityTypeBuilder dependentEntityTypeBuilder, string navigationToPrincipalName, string navigationToDependentName, System.Collections.Generic.IReadOnlyList<Microsoft.Data.Entity.Metadata.Internal.Property> dependentProperties, System.Collections.Generic.IReadOnlyList<Microsoft.Data.Entity.Metadata.Internal.Property> principalProperties, bool? isUnique, bool? isRequired, Microsoft.Data.Entity.Metadata.DeleteBehavior? deleteBehavior, bool strictPrincipal, bool oldRelationshipInverted, string oldNavigationToPrincipalName, string oldNavigationToDependentName, Microsoft.Data.Entity.Metadata.Internal.ConfigurationSource configurationSource, bool runConventions)  Unknown
[...]
The maximum number of stack frames supported by Visual Studio has been exceeded.    

Also, it is worth noting that I had to convert my migration files to match the new naming convention ( Annotation() => HasAnnotation(), Index() => HasIndex(), Unique() => IsUnique() )

What am I missing ?

@rowanmiller
Copy link
Contributor

Hey,

You shouldn't really be disposing something that you get from DI (since DI will take care of disposing it for you). Also, you really need to get the context from a scoped set of services, not the root one. Confusing I know... such is the nature of DI 😄.

Can you try swapping to the following code and see if things work?

using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
    .CreateScope())
{
    var db = serviceScope.ServiceProvider.GetService<MyDbContext>();
    // whatever you want to do with the context
}

@darkurse
Copy link
Author

Thank you, the scope actually does make more sense now ( although confusing at first :) ).
Out of curiosity, What if the service you try to get is a singleton, is there a special way to get it or can we just get it from the root scope ?

Regarding the test, it doesn't help I'm afraid, I still have the StackOverflowException ( I have edited the initial post to show the call stack.

@rowanmiller
Copy link
Contributor

Also, it is worth noting that I had to convert my migration files to match the new naming convention

Did you have existing migrations that you hand edited to still compile on RC1? If so, could you try removing those and regenerating? I wonder if the old migrations are hitting a code path that should not be hit.

@darkurse
Copy link
Author

Did you have existing migrations that you hand edited to still compile on RC1?

Yes I did and on your suggestion I removed all my migrations directory and tried to regenerate a migration via dnx ... Dnx crashed with StackOverflowException..

nb1: None of my models changed while upgrading to rc1 and it was working until today.
nb2: Could navigation properties in my models can cause this with the new version ?

@darkurse
Copy link
Author

@rowanmiller , I think there is a good chance my issue could be the same as issue #3806 ( One-to-one relationship with navigation properties on both ends throws StackOverflowException )

I don't know if it is related to one-to-one navigation property in particular, but I have lots of models with lots of navigation properties in them :S

@darkurse
Copy link
Author

Bingo ! it was indeed related to navigation properties.
The tool I originally used to generate my entities ( reverse poco ) somehow created a double navigation property for one relationship.

public class ModelA
{
    public virtual ICollection<ModelB> Bs   { get; set; }
    public virtual ICollection<ModelB> Bs_1 { get; set; }
}

public class ModelB
{
    public virtual ModelA A   { get; set; }
    public virtual ModelA A_1 { get; set; }
}

In beta7/beta8 this behaviour seemed to be tolerated and have no impact.
In RC1, it gives the StackOverflowException.

Hope this helps someone and thanks for having a look at it anyway Rowan.

@ErikEJ
Copy link
Contributor

ErikEJ commented Nov 20, 2015

Make sure to report this bug o the reverse poco codeplex site

@rowanmiller
Copy link
Contributor

Re-opening... even if the code is incorrect you should not get a StackOverflow

@smitpatel
Copy link
Contributor

@julienbjuice - Can you share full model and your model configuration? The model you have posted would not generated any issues since there are 2 navigations at both ends and it is ambiguous how to create relationship so EF will not do anything by convention.

@darkurse
Copy link
Author

Sorry for the delay,

@smitpatel, I simplified my models as much as I could. My full database has more than 100 tables so it was less than ideal to share here.

The Exception is easy to reproduce with the following models. To remove the exception, just comment out the code between the tags [STACKOVERFLOWEXCEPTION SECTION]

public class Model_A
{
    public Guid Other_Id { get; set; }

    [ForeignKey("Other_Id")]
    public virtual Model_B model_b { get; set; }

    // [STACKOVERFLOWEXCEPTION SECTION] 
    [ForeignKey("Other_Id")]
    public virtual Model_B model_b_1 { get; set; }
    // [/STACKOVERFLOWEXCEPTION SECTION] 
}

public class Model_B
{

    public Model_B()
    {
        model_a = new HashSet<Model_A>();

        // [STACKOVERFLOWEXCEPTION SECTION] 
        model_a_1 = new HashSet<Model_A>();
        // [/STACKOVERFLOWEXCEPTION SECTION] 
    }

    [InverseProperty("model_b")]
    public virtual ICollection<Model_A> model_a { get; set; }

    // [STACKOVERFLOWEXCEPTION SECTION]
    [InverseProperty("model_b")]
    public virtual ICollection<Model_A> model_a_1 { get; set; }
    // [/STACKOVERFLOWEXCEPTION SECTION]
}

@ErikEJ, the poco reverse project was not to blame on this. The original sql script used to generate the database was flawed. The tool did what it had to do.

Hope this helps,
Julien

@smitpatel
Copy link
Contributor

@julienbjuice - Thanks for the model details. As you can see the attribute configuration is ambiguous for model creation. For InverseProperty attribute the last one wins so last propertyInfo with actually be using the attribute value. The stackoverflow is coming due to ForeignKey, since 2 different relationships are trying to use the same foreign key property, when second relationship tries to set the foreign key property it cannot fully build the relationship which puts navigations in the model without relationship and EF will try to create the relationship again by convention. This causes infinite loop. When multiple navigations specifying same foreign key, it should throw exception because that is invalid model.

@smitpatel smitpatel changed the title RC1 StackOverflowException on DbContext.Database.EnsureCreated() More than one ForeignKeyAttribute pointing to same property causes stackoverflow Nov 23, 2015
@smitpatel smitpatel modified the milestones: 7.0.0-rc2, 7.0.0 Nov 24, 2015
@ajcvickers ajcvickers removed this from the 1.0.0-rc2 milestone Oct 15, 2022
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Oct 15, 2022
@ajcvickers ajcvickers added this to the 1.0.0 milestone Oct 15, 2022
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