dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.78k stars 3.19k forks source link

Model.Validate errors do not indicate the specific property affected by misconfiguration/validation errors #17010

Open rbarna1 opened 5 years ago

rbarna1 commented 5 years ago

This has been an issue several times for us. We have a reasonably complex POCO based data model. Several base types are re-used across our models, and many of our POCOs include Uri properties (though this could affect other framework types). When creating new top-level entities we often hit mis-configurations of our child properties. These are legitimate misconfigurations of the Uri (they often need HasConfiguration to configure their serialization) based on the functionality of EF Core. Our problem isn't with the error itself. The problem is that the error does not include the path to the specific affected property. It would be VERY helpful if errors of this type pointed to the specific instance of the Uri (or other framework type) that has validation/configuration errors.

Stack trace:

Using context 'ModelsPostgresContext'.
System.InvalidOperationException: No suitable constructor found for entity type 'Uri'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'uriString' in 'Uri(string uriString)'; cannot bind 'uriString', 'dontEscape' in 'Uri(string uriString, bool dontEscape)'; cannot bind 'baseUri', 'relativeUri', 'dontEscape' in 'Uri(Uri baseUri, string relativeUri, bool dontEscape)'; cannot bind 'uriString', 'uriKind' in 'Uri(string uriString, UriKind uriKind)'; cannot bind 'baseUri', 'relativeUri' in 'Uri(Uri baseUri, string relativeUri)'; cannot bind 'serializationInfo', 'streamingContext' in 'Uri(SerializationInfo serializationInfo, StreamingContext streamingContext)'; cannot bind 'baseUri', 'relativeUri' in 'Uri(Uri baseUri, Uri relativeUri)'; cannot bind 'flags', 'uriParser', 'uri' in 'Uri(Flags flags, UriParser uriParser, string uri)'.
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConstructorBindingConvention.Apply(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelBuilt(InternalModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.Validate()
   at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.<>c__DisplayClass5_0.<GetModel>b__1()
   at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
   at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
   at System.Lazy`1.CreateValue()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder, IModelValidator validator)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_2(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(IServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.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_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance()
   at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.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)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_1.<.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)

Steps to reproduce

Create a POCO for a DbSet where the DbSet object type has child properties with Uri properties. When trying to add a migration (e.g.: dotnet ef migrations add someMigration--context MyModelPostgresContext), note that this will require Uri property configuration, but the error will not point to the specific property.

Further technical details

EF Core version: 2.2 Database Provider: NpgSql for Postgres Operating system: Win 10 IDE: Visual Studio 2019 16.1

ajcvickers commented 5 years ago

@rbarna1 The exception message for this case has been improved in 3.0--see #12894. It now has the form:

No suitable constructor found for entity type 'BlogNone'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'did' in 'BlogNone(string title, int did)'; cannot bind 'notTitle' in 'BlogNone(string notTitle, Nullable<Guid> shadow, int id)'; cannot bind 'dummy' in 'BlogNone(string title, Nullable<Guid> shadow, bool dummy, int id)'; cannot bind 'dummy', 'description' in 'BlogNone(string title, Nullable<Guid> shadow, bool dummy, int id, string description)'

Does this address your issue?

rbarna1 commented 5 years ago

@rbarna1 The exception message for this case has been improved in 3.0--see #12894. It now has the form:

No suitable constructor found for entity type 'BlogNone'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'did' in 'BlogNone(string title, int did)'; cannot bind 'notTitle' in 'BlogNone(string notTitle, Nullable<Guid> shadow, int id)'; cannot bind 'dummy' in 'BlogNone(string title, Nullable<Guid> shadow, bool dummy, int id)'; cannot bind 'dummy', 'description' in 'BlogNone(string title, Nullable<Guid> shadow, bool dummy, int id, string description)'

Does this address your issue?

I'm not sure, but my initial thought is no. The problem isnt one of constructors, since the type is Uri, not a custom class. The original problem is lack of serialization configuration on a property on one of our POCOs that happens to be of type Uri. My issue here is the error simply pointing to Uri (which we have many of).
The improvement you reference still doesnt seem to include the full path to the property having the issue. Thanks, Bob

orcun commented 5 years ago

I have a similar issue, where entity type is 'string' and I am working on a tool to auto-generate entity framework code. It is very difficult to locate the 'string' in a very large auto-generated code base. Using --verbose doesn't help either. Here is a probable cause:

public List<string> ProblemHere { get; set; }
builder.HasMany(e=>e.ProblemHere);

Question is where is this? It would be nice to know which class has this problematic property.

BTW, is there an easy way to debug dotnet ef tools? I guess it spawns child processes which makes it difficult to attach a debugger.

Thanks, Orcun

ajcvickers commented 5 years ago

@rbarna1 Please post a small, runnable project/solution or complete code listing that shows how to generate the exception you are seeing. That way I can join the dots of what the issue is in the model to the exception message you are seeing.

ajcvickers commented 5 years ago

@orcun Mapping string as an entity type is very unusual. Can you please create a new issue and post a small, runnable project/solution or complete code listing that shows how you are doing this and the issue that you are running into.

AndriySvyryd commented 5 years ago

We could add the path that model building took to discover an entity type if we implemented Configuration layering

rbarna1 commented 5 years ago

@orcun Mapping string as an entity type is very unusual. Can you please create a new issue and post a small, runnable project/solution or complete code listing that shows how you are doing this and the issue that you are running into.

@AndriySvyryd issue seems to be the same as mine. I dont think his ENTITY is a string...its the string property that he has misconfigured that he's having a hard time finding from the stack trace.
I can try to reproduce the error, standalone, sure, will take me a while to untaggle it all. I do think the stack trace does point directly to the problem in ConstructoreBindingConvention.Apply(). If you look at the InvalidOperationException being thrown you can see that the error message simply includes the details of the type (Uri), not the entity path to that instance of the type that is having the problem:


if (constructorBindingList.Count == 0)
          {
            IEnumerable<string> values = source.SelectMany<IEnumerable<ParameterInfo>, ParameterInfo>((Func<IEnumerable<ParameterInfo>, IEnumerable<ParameterInfo>>) (f => f)).GroupBy<ParameterInfo, ConstructorInfo>((Func<ParameterInfo, ConstructorInfo>) (f => f.Member as ConstructorInfo)).Select<IGrouping<ConstructorInfo, ParameterInfo>, string>((Func<IGrouping<ConstructorInfo, ParameterInfo>, string>) (x => CoreStrings.ConstructorBindingFailed((object) string.Join("', '", x.Select<ParameterInfo, string>((Func<ParameterInfo, string>) (f => f.Name))), (object) (Microsoft.EntityFrameworkCore.Metadata.Internal.EntityTypeExtensions.DisplayName(entityType) + "(" + string.Join(", ", ((IEnumerable<ParameterInfo>) x.Key.GetParameters()).Select<ParameterInfo, string>((Func<ParameterInfo, string>) (y => y.ParameterType.ShortDisplayName() + " " + y.Name))) + ")"))));
            throw new InvalidOperationException(CoreStrings.ConstructorNotFound((object) Microsoft.EntityFrameworkCore.Metadata.Internal.EntityTypeExtensions.DisplayName(entityType), (object) string.Join("; ", values)));
          }```
ajcvickers commented 5 years ago

Notes from triage: putting this on the backlog as something we can improve if we implement #15898 first.

One way to debug these kinds of issues is to look at the model that EF has built before it is validated. For example, put a breakpoint at the end of OnModelCreating, and then inspect modelBuilder.Model.DebugView.View as shown below:

image

This can then be used to find out which types are configured as entity types as well as where they are referenced from.