-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Migration error when use owned type for value object #13202
Comments
If I add a empty constructor to ComputedStampDuty like, "private ComputedStampDuty() { }" System.InvalidOperationException: Field 'k__BackingField' of entity type 'ComputedStampDuty' is readonly and so cannot be set. |
@Bo-Yee-Woods This is because the owned type is still an entity type, and using a constructor to set related entities is not yet supported--see #12078 The error about the backing field being read-only means what it says. EF can't write to a read-only field, and hence can't set the value for the related entity. |
@ajcvickers but how to explain my case one can work? |
@Bo-Yee-Woods The way to make it work is:
|
@ajcvickers Thank you for explaining. As I try following DDD pattern, the value object (owned type) must be immutable (readonly). |
@ajcvickers yes, you are correct. Does it mean the Microsoft documentation here is wrong? https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects#value-object-implementation-in-c |
@Bo-Yee-Woods Which part of that documentation specifically? |
@ajcvickers The fields in "Address" are readonly.
|
@ajcvickers As it is 2am now per Singapore time, I will read your update tomorrow. |
@Bo-Yee-Woods That's fine because non of the property types are entity types--they are simple scalar properties. In your case, |
@ajcvickers wait, the owner entity of address value object is order entity. The order entity has property Address and also parameter in constructor. Pls refer to here https://github.com/dotnet-architecture/eShopOnAzure/blob/master/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs |
@Bo-Yee-Woods I'm running out of different ways of explaining this, so let me show the difference using code: public class EntityTypeOne
{
public int Id { get; set; }
// OwnedOne can't be read-only or set via constructor because OwnedEntityTypeOne is an entity type
public OwnedEntityTypeOne OwnedOne { get; set; }
}
public class OwnedEntityTypeOne
{
public OwnedEntityTypeOne(string a, string b)
{
A = a;
B = b;
}
// A and B are not entity types and hence can be read-only and set in constructor
public string A { get; }
public string B { get; }
// OwnedTwo can't be read-only or set via constructor because OwnedEntityTypeTwo is an entity type
public OwnedEntityTypeTwo OwnedTwo { get; set; }
}
public class OwnedEntityTypeTwo
{
public OwnedEntityTypeTwo(string c, string d)
{
C = c;
D = d;
}
// C and D are not entity types and hence can be read-only and set in constructor
public string C { get; }
public string D { get; }
}
public class BloggingContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<EntityTypeOne>(b =>
{
b.OwnsOne(
e => e.OwnedOne,
b2 =>
{
b2.Property(e => e.A);
b2.Property(e => e.B);
b2.OwnsOne(
e => e.OwnedTwo,
b3 =>
{
b3.Property(e => e.C);
b3.Property(e => e.D);
});
});
});
}
} |
@ajcvickers Thank you very much! |
@ajcvickers Hi, I make it works finally in my Case Three. Moreover, I have some other findings. I summarize the points we need to notice.
|
@Bo-Yee-Woods Just to be accurate, for 2) The setter could be internal or private, or it could have no setter and a writable backing field. For 3) Owned type can have an empty constructor, but if it does, then EF will use it instead of the parameterized constructor. This may change in 3.0--see #10865. 4) if the properties are read-only, then they must be set from the constructor. |
https://github.com/Bo-Yee-Woods/TestOwnedType/tree/master/TestOwnedType
See my sample project, I attempt to use owned type to configure the relationship between my entity and value object (DDD concept). It works fine in my Case one, but I got an migration error (see below) for Case Two. I think the way of implementation of Case Two is the same as Case One, why case two cannot work?
Stack trace:
System.InvalidOperationException: No suitable constructor found for entity type 'ComputedStampDuty'. The following parameters could not be bound to properties of the entity: 'propertyType'.
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.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.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.b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
No suitable constructor found for entity type 'ComputedStampDuty'. The following parameters could not be bound to properties of the entity: 'propertyType'.
Steps to reproduce
Include a complete code listing (or project/solution) that we can run to reproduce the issue.
Run "Add-Migration AddCaseTwo" at package manager console
Further technical details
EF Core version: 2.1.1.0
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: IDE: Visual Studio 2017 15.4
The text was updated successfully, but these errors were encountered: