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.79k stars 3.19k forks source link

Allow related entities to be passed to constructor of aggregate root #12078

Open andriysavin opened 6 years ago

andriysavin commented 6 years ago

Note--see comments for actual issue.

Original issue: I'm trying constructor injection feature of EF Core 2.1 RC1. Consider the following entities and DBContext configuration:

 public class MyEntity
    {
        public int Id { get; set; }
        public OwnedEntity Owned1 { get; private set; }
        public OwnedEntity Owned2 { get; private set; }

        private MyEntity() { }

        public MyEntity(OwnedEntity oned1, OwnedEntity owned2)
        {
            Owned1 = oned1;
            Owned2 = owned2;
        }
    }

    public class OwnedEntity
    {
        public double MyProperty { get; }
        public OwnedEntity(double myProperty) => MyProperty = myProperty;
    }

 protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<OwnedEntity>(b =>
            {
                b.Property(e => e.MyProperty);
            });

            modelBuilder.Entity<MyEntity>(cb =>
            {
                cb.OwnsOne(seg => seg.Owned1);
                cb.OwnsOne(seg => seg.Owned2);
            });
        }

When I'm trying to add a new entity to the set, I'm getting the following exception:

Unhandled Exception: System.InvalidOperationException: No suitable constructor found for entity type 'MyEntity.Owned2#OwnedEntity'. The following parameters could not be bound to properties of the entity: 'myProperty'.
   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.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_1(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.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.EntryWithoutDetectChanges[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
   at TestEfCtorInjectionWithOwnedTypes.Program.Main(String[] args) 

What is interesting is that if I remove Owned1 or Owned2 property and all references to it, all works fine, though they are identical from configuration point of view. So its essential for this issue that at least two owned properties are present.

Further technical details

EF Core version: 2.1.0-rc1-final Database Provider: doesn't matter, but is reproducable with InMemory and SqlServer. IDE: Visual Studio 2017 15.7

Link to the VS solution on OneDrive: https://1drv.ms/u/s!AuaAKPMkiTEAxqgGlJX81J_GvbwIGA

ajcvickers commented 6 years ago

@andriysavin The issue here is that owned types cannot be configured using a call the top level .Entity method. This was supposed to throw in 2.1, but that change was accidentally reverted-see issue #9148.

To make your code work you instead need to configure the property mapping for the owned type in each of the places it is used. For example:

modelBuilder.Entity<MyEntity>(cb =>
{
    cb.OwnsOne(seg => seg.Owned1).Property(e => e.MyProperty);
    cb.OwnsOne(seg => seg.Owned2).Property(e => e.MyProperty);
});
andriysavin commented 6 years ago

@ajcvickers Thanks for explaining, that makes it work, though this configuration may look messy if you have many properties in the owned type - you will have to repeat the same configuration:

cb.OwnsOne(seg => seg.Owned1, b =>
                {
                    b.Property(e => e.MyProperty1);
                    b.Property(e => e.MyProperty2);
                    b.Property(e => e.MyProperty3);
                    b.Property(e => e.MyProperty4);
                });

cb.OwnsOne(seg => seg.Owned2, b =>
                {
                    b.Property(e => e.MyProperty1);
                    b.Property(e => e.MyProperty2);
                    b.Property(e => e.MyProperty3);
                    b.Property(e => e.MyProperty4);
                });

cb.OwnsOne(seg => seg.Owned3, b =>
                {
                    b.Property(e => e.MyProperty1);
                    b.Property(e => e.MyProperty2);
                    b.Property(e => e.MyProperty3);
                    b.Property(e => e.MyProperty4);
                });

This can be simplified by extracting to a separate method or local function, of course, but looks not very fluent:

void buildAction(ReferenceOwnershipBuilder<MyEntity, OwnedEntity> b)
                {
                    b.Property(e => e.MyProperty1);
                    b.Property(e => e.MyProperty2);
                    b.Property(e => e.MyProperty3);
                    b.Property(e => e.MyProperty4);
                }

                cb.OwnsOne(seg => seg.Owned1, buildAction);
                cb.OwnsOne(seg => seg.Owned2, buildAction);
andriysavin commented 6 years ago

However, it's not all from me :) I want constructor injection in MyEntity as well, so I commented out the parameterless constructor (see code below). And now I'm getting:

System.InvalidOperationException: No suitable constructor found for entity type 'MyEntity'. The following parameters could not be bound to properties of the entity: 'owned1', 'owned2'.

I can guess that this is due to (from the docs):

EF Core cannot set navigation properties (such as Blog or Posts above) using a constructor.

What's the reason for this limitation for owned types? If I have nested owned types, does that mean that only innermost owned type will be able to use constructor injection?

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace TestEfCtorInjectionWithOwnedTypes
{
    public class MyEntity
    {
        public int Id { get; set; }
        public OwnedEntity Owned1 { get; private set; }
        public OwnedEntity Owned2 { get; private set; }

        //private MyEntity() { }

        public MyEntity(OwnedEntity owned1, OwnedEntity owned2)
        {
            Owned1 = owned1;
            Owned2 = owned2;
        }
    }

    public class OwnedEntity
    {
        public double MyProperty { get; }
        public OwnedEntity(double myProperty) => MyProperty = myProperty;
    }

    class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions options) : base(options) { }

        public DbSet<MyEntity> MyEntities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>(cb =>
            {
                cb.OwnsOne(seg => seg.Owned1).Property(e => e.MyProperty);
                cb.OwnsOne(seg => seg.Owned2).Property(e => e.MyProperty);
            });
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();

            services.AddDbContext<MyDbContext>(options =>
                options.UseInMemoryDatabase("MyDatabase"));

            using (var sp = services.BuildServiceProvider())
            {
                var dbContext = sp.GetRequiredService<MyDbContext>();

                dbContext.MyEntities.Add(
                    new MyEntity(
                        new OwnedEntity(42),
                        new OwnedEntity(24)));
            }
        }
    }
}
andriysavin commented 6 years ago

Here is an example with nested entities which doesn't work as well with error System.InvalidOperationException: No suitable constructor found for entity type 'OwnedEntity'. The following parameters could not be bound to properties of the entity: 'ownedOwnedProperty'.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace TestEfCtorInjectionWithOwnedTypes
{
    public class MyEntity
    {
        public int Id { get; set; }
        public OwnedEntity Owned { get; private set; }
        private MyEntity() { }
        public MyEntity(OwnedEntity owned) => Owned = owned;
    }

    public class OwnedEntity
    {
        public OwnedOwnedEntity OwnedOwnedProperty { get; }
        public OwnedEntity(OwnedOwnedEntity ownedOwnedProperty)
            => OwnedOwnedProperty = ownedOwnedProperty;
    }

    public class OwnedOwnedEntity
    {
        public double MyProperty { get; }
        public OwnedOwnedEntity(double myProperty) => MyProperty = myProperty;
    }

    class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions options) : base(options) { }

        public DbSet<MyEntity> MyEntities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<MyEntity>(cb =>
            {
                cb.OwnsOne(seg => seg.Owned, b =>
                {
                    b.OwnsOne(e => e.OwnedOwnedProperty).Property(e => e.MyProperty);
                });
            });
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();

            services.AddDbContext<MyDbContext>(options =>
                options.UseInMemoryDatabase("MyDatabase"));

            using (var sp = services.BuildServiceProvider())
            {
                var dbContext = sp.GetRequiredService<MyDbContext>();

                dbContext.MyEntities.Add(
                    new MyEntity(new OwnedEntity(new OwnedOwnedEntity(4242))));
            }
        }
    }
}
ajcvickers commented 6 years ago

Re-opening to discuss in triage.

ajcvickers commented 6 years ago

Triage: moving this to the backlog as a feature request to pass related entity instances to the constructor of a parent instance, which is most useful for aggregates. See also #1985

andriysavin commented 6 years ago

Just to give a more real life example, here are some of my models (simplified). To be accurate, this is how I want them to look like: GeoPosition and GeoSegment being value objects (immutable) and use constructor injection for instance creation, no default ctor (even private), no private setters, and are Owned Types.


 public sealed class GeoPosition
 {
        public double Latitude { get; }
        public double Longitude { get; }

        public GeoPosition(double latitude, double longitude)
        {
            Latitude = latitude;
            Longitude = longitude;
        }
}

public sealed class GeoSegment
{
        public GeoPosition Start { get; }
        public GeoPosition End { get; }

        public GeoSegment(GeoPosition start, GeoPosition end)
        {
            Start = start;
            End = end;
        }
}

public class MyEntity
{
        public int Id { get; set; }
        public GeoSegment Location { get; set; }

        public MyEntity(GeoSegment location)
        {
            Location = location;
        }
}

The same model example can be considered for issue #12118

bicatu commented 5 years ago

Any update or alternative solution for this?

ajcvickers commented 5 years ago

@bicatu What is the specific problem for which are you looking for an alternative solution?

This issue is in the Backlog milestone. This means that it is not going to happen for the 3.0 release. We will re-assess the backlog following the 3.0 release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

bicatu commented 5 years ago

I have an aggregateRoot (A) that has a constructor that receives some Value Objects and an entity (B) that is part of the aggregate.

imagine a = new A(VO, VO, new B())

ajcvickers commented 5 years ago

@bicatu The only alternative solution that EF currently supports is to create a constructor that doesn't take B and then allow EF to set B after creation. The setter for B can be private.

malohr commented 5 years ago

I ran into the same issue even without using related entities. Found a solution for my case. Hope this will help you guys.

Quick example:

 public class Event {
        public Event(string subject, DateTime startDate, DateTime endDate, bool enablePrivate)
        {
            Subject = subject;
            StartDate = startDate;
            PrivateEvent = enablePrivate;
            EndDate = endDate;
        }

        public string Subject { get; private set; }
        public DateTime StartDate { get; private set; }
        public DateTime EndDate { get; private set; }
        public bool PrivateEvent { get; private set; }
}

This gave me the following exception: System.InvalidOperationException : No suitable constructor found for entity type 'Event'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'enablePrivate'

So what I have found in the Microsoft docs (https://docs.microsoft.com/de-de/ef/core/modeling/constructors) : "The parameter types and names must match property types and names, except that properties can be Pascal-cased while the parameters are camel-cased."

In that case the solution was to rename the parameter to "enablePrivate" and the exception was gone.

M-Pixel commented 4 years ago

Adding support for navigation-property via constructor is essential to accommodating C# 8.0's new nullable reference mode.

modelBuilder.Entity<Workstation>()
    .HasOne(w => w.Owner)
    .WithMany()
    .HasForeignKey("OwnerId");
class Workstation
{
    public User Owner { get; set; } // CS8618 Non-nullable property 'Owner' is uninitialized.
    public string Hostname { get; set; }
}
class Workstation 
{
    public User Owner { get; set; }
    public string Hostname { get; set; }

    public Workstation(User owner, string hostname)
    {
        Owner = owner;
        Hostname = hostname;
    }
}
// Exception: No suitable constructor found for entity type 'Workstation'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'owner' in 'Workstation(User owner, string hostname)'
andriysavin commented 4 years ago

Adding support for navigation-property via constructor is essential to accommodating C# 8.0's new nullable reference mode.

@M-Pixel There is a doc page which describes how to work with NRTs in EF Core (and fix the warning): https://docs.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types

msschl commented 4 years ago

Having exactly the issue mentioned in the sample by @andriysavin above in a real world application.

We have a nested LatLngPoint in the LatLngBounds object. Both are value objects used as owned types in an aggregated root.

public sealed class LatLngPoint : ValueObject<LatLngPoint>
{
    private LatLngPoint(double latitude, double longitude)
    {
        // Argument validation...

        this.Latitude = latitude;
        this.Longitude = longitude;
    }

    public double Latitude { get; }

    public double Longitude { get; }
}

public sealed class LatLngBounds : ValueObject<LatLngBounds>
{
    private LatLngBounds(LatLngPoint southwest, LatLngPoint northeast)
    {
        this.Southwest = southwest ?? throw new ArgumentNullException(nameof(southwest), "...");
        this.Northeast = northeast ?? throw new ArgumentNullException(nameof(northeast), "...");
    }

    public LatLngPoint Southwest { get; }

    public LatLngPoint Northeast { get; }
}

public class Map : IShadowUserAuditableEntity, IAggregateRoot
{
    private Map(Guid id)
    {
        // Argument validation...

        this.Id = id;
    }

    public Guid Id { get; }

    public LatLngBounds Viewport { get; }
}

public class MapEntityTypeConfiguration : IEntityTypeConfiguration<Map>
{
    public void Configure(EntityTypeBuilder<Map> builder)
    {
        ...

        builder.OwnsOne(e => e.Viewport, builder =>
        {
            builder.OwnsOne(e => e.Southwest, builder =>
            {
                builder.Property(e => e.Latitude)
                    .IsRequired()
                    .HasColumnName("SouthwestLat");

                builder.Property(e => e.Longitude)
                    .IsRequired()
                    .HasColumnName("SouthwestLng");
            });

            builder.OwnsOne(e => e.Northeast, builder =>
            {
                builder.Property(e => e.Latitude)
                    .IsRequired()
                    .HasColumnName("NortheastLat");

                builder.Property(e => e.Longitude)
                    .IsRequired()
                    .HasColumnName("NortheastLng");
            });
        });

        ...
    }
}

Results in:

System.InvalidOperationException: No suitable constructor found for entity type 'LatLngBounds'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'southwest', 'northeast' in 'LatLngBounds(LatLngPoint southwest, LatLngPoint northeast)'.
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.ConstructorBindingConvention.ProcessModelFinalized(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
   at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, 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.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, 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__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   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.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_Model()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include[TEntity,TProperty](IQueryable`1 source, Expression`1 navigationPropertyPath)

Further adding LatLngBounds as a parameter to the Map aggregate root constructor results in:

   System.InvalidOperationException: No suitable constructor found for entity type 'Map'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'viewport', 'mode' in 'Map(Guid id, LatLngBounds viewport, Nullable<MapMode> mode)'; cannot bind 'viewport' in 'Map(Guid id, LatLngBounds viewport, MapMode mode)'.
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.ConstructorBindingConvention.ProcessModelFinalized(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
   at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, IConventionSetBuilder conventionSetBuilder)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__7_3(IServiceProvider p)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, 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.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite singletonCallSite, 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__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   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.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_Model()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable()
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include[TEntity,TProperty](IQueryable`1 source, Expression`1 navigationPropertyPath)
CRodriguez25 commented 3 years ago

Yeah I appear to be having the same issue as well. Not sure if this is thought to be resolved? I may be doing something wrong.

ardalis commented 3 years ago

This remains an issue in 5.x

InspiringCode commented 3 years ago

This would be a really useful feature, especially for required one-to-to or many-to-one related entities. Without this feature one could not really validate, whether the entity constructed by EF actually has the required related entity property really set (nullability might be violated)!

visschersm commented 2 years ago

My issue about this was indeed a duplicate. Updated my sample to support migration generation with an empty constructor but still using private setters for the properties. Now only the default constructor gives the NRT warning but nearly there.

ErroneousFatality commented 2 years ago

3 years and 7 months later, this remains an issue in v6.0.1. I'm using the #nullable setting in projects and now I have to do this: private Entity() { OwnedProperty = default!; }. Instead of the expected: private Entity(OwnedProperty ownedProperty) { OwnedProperty = ownedProperty; }.

ajcvickers commented 2 years ago

@ErroneousFatality As in all software development, we have limited resources and have to make hard decisions about what to prioritize. Make sure to vote (👍) for this issue if it is important to you. It currently has 18 votes, which puts it in 78th place in the list of top voted issues.

See The Planning Process for more information on how we decide what to work on.

yecril71pl commented 2 years ago

I am trying to have an explicit many-to-many association (because implicit many-to-many associations create unwanted shadow properties):

[Owned]
class PersonWorkplace {
 Person RefPerson { get; } Workplace RefWorkplace { get; }
 public PersonWorkpace (Person refPerson, Workplace refWorkplace) { … }}

It feels like basic stuff (like WMI-style associations) does not work :disappointed:

System.InvalidOperationException: No suitable constructor was found for entity type 'MedicWorkplace'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'refMedic', 'refWorkplace' in 'MedicWorkplace(Medic refMedic, Workplace refWorkplace)'.
         at Microsoft.EntityFrameworkCore.Metadata.Internal.ConstructorBindingFactory.GetBindings(IReadOnlyEntityType entityType, Func`5 bind, InstantiationBinding& constructorBinding, InstantiationBinding& serviceOnlyBinding)
         at Microsoft.EntityFrameworkCore.Metadata.Internal.ConstructorBindingFactory.GetBindings(IMutableEntityType entityType, InstantiationBinding& constructorBinding, InstantiationBinding& serviceOnlyBinding)
         at Microsoft.EntityFrameworkCore.Metadata.Conventions.ConstructorBindingConvention.ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext`1 context)
         at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelFinalizing(IConventionModelBuilder modelBuilder)
         at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnModelFinalizing(IConventionModelBuilder modelBuilder)
         at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FinalizeModel()
         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.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.DbContextActivator.CreateInstance(Type contextType, Assembly startupAssembly, IOperationReportHandler reportHandler, String[] args)
         at Microsoft.EntityFrameworkCore.Design.DbContextActivator.CreateInstance(Type contextType, Assembly startupAssembly, IOperationReportHandler reportHandler)
         at Microsoft.VisualStudio.Web.CodeGeneration.EntityFrameworkCore.EntityFrameworkModelProcessor.TryCreateContextUsingAppCode(Type dbContextType, Type startupType) in /home/dell/prog/Scaffolding/src/Scaffolding/VS.Web.CG.EFCore/EntityFrameworkModelProcessor.cs:line 685
yecril71pl commented 2 years ago

3 years and 7 months later, this remains an issue in v6.0.1. I'm using the #nullable setting in projects and now I have to do this: private Entity() { OwnedProperty = default!; }. Instead of the expected: private Entity(OwnedProperty ownedProperty) { OwnedProperty = ownedProperty; }.

I would even say

[Obsolete ("see <URL: https://github.com/dotnet/efcore/issues/12078 >")]
private Entity() { OwnedProperty = default!; }
InspiringCode commented 1 year ago

Any updates on this? Would be really useful!

ajcvickers commented 1 year ago

This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 8.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you.