6

Consider these simple classes. They belong to a simple application with Domain Driven Design (DDD) principles, and as such every Entity and ValueObject receives its property values through the constructor while hiding the default, parameter-less constructor. Properties will also be read-only.

    public class MyClass
    {
       public Guid Id {get;}
       public ValueObject ValueObject1 {get;}
       public ValueObject ValueObject2 {get;}
       public MyClass(ValueObject valueObject1, ValueObject valueObject2) 
       {
          ValueObject1 = valueObject1;
          ValueObject2 = valueObject2;
       }
       private MyClass(){}
    }

    public class ValueObject
    {
       public string Value {get;}
       public ValueObject(string value) 
       {
          Value = value;
       }
       private ValueObject(){}
    }

I want to be able to create a database based on this model, using EntityFramework Core 2.2.6.

Apparently EF Core 2.2.6 can automatically feed property values for these classes through their parametrized constructors, as long as constructor parameters and class properties have the same name (case-insensitive). Great.

Now I want the ValueObjects to be stored in the same table as the MyClass. To make that happen, I am told, I should use modelBuilder.OwnsOne<> in OnModelCreating of the DBContext, instead of modelBuilder.Property<>

The DBContext configuration in OnModelCreating would look like something this:

modelBuilder.Entity<MyClass>(b => b.HasKey(mc => mc.Id));
modelBuilder.Entity<MyClass>(b => b.OwnsOne(mc => mc.ValueObject1,rb =>
        {
            rb.Property(vo => vo.Value);
        }));
modelBuilder.Entity<MyClass>(b => b.OwnsOne(mc => mc.ValueObject2, rb =>
        {
            rb.Property(vo => vo.Value);
        }));

Now it seems modelBuilder.OwnsOne<> and modelBuilder.Property<> are mutually exclusive, meaning you can't use them both together because every time I try to Add-Migration with both of them I get:

'ValueObject' cannot be used as a property on entity type 'MyClass' because it is configured as a navigation.

But if I don't use modelBuilder.Property<> and only use modelBuilder.OwnsOne<>, I get:

No suitable constructor found for entity type 'MyClass'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'valueObject1', 'valueObject2' in 'MyClass(ValueObject valueObject1, ValueObject valueObject2)'.

Which means the constructor to property binding pattern only works only if I use modelBuilder.Property<> to configure the properties on MyClass.

So my question is: how should I configure the DBContext to allow EF Core to both set property values through the parametrized constructor, and store ValueObjects in the same table as the Entity?

TheAgent
  • 1,472
  • 5
  • 22
  • 42
  • I can use your model with `context.Database.EnsureCreated()` alright. The only thing is, I have to add `private set` to the properties, just as is done [here](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects). If I don't, I get an exception that a field "is readonly and so cannot be set" when EF tries to build the model. Which is not the perfect implementation of a value object but seems necessary anyway. – Gert Arnold Aug 12 '19 at 21:24
  • Tried your classes and had the same result as @GertArnold. You need to add private set to your properties to make it work properly, as explained [here](https://learn.microsoft.com/en-us/ef/core/modeling/constructors). Probably owned types as readonly are not supported yet... – Fabio M. Aug 12 '19 at 21:29
  • @GertArnold So did you add private setters to all properties both on MyClass and ValueObject? – TheAgent Aug 12 '19 at 21:51
  • @GertArnold And did you use OwnsOne<> and Property<> together? – TheAgent Aug 12 '19 at 21:54
  • Yep. Just used your code and added a private setter to each get-only property there is. – Gert Arnold Aug 12 '19 at 21:56
  • Private property setters combined with private parameterless constructors is what makes it work - when loading data from the database, EF Core will use these instead of (public) constructors with parameters. – Ivan Stoev Aug 13 '19 at 07:07
  • 1
    @TheAgent I do doubt the usefulness of value objects in an EF class model though. In the end, the class model is just part of the data layer, it's *not* a domain model. It should be optimized for its task and not be torn between two responsibilities. Its task is to channel data and their changes between the database and the application. If you want to modify and save only one property of a `MyClass` object now you have to jump through hoops to get it done, if it can be done at all. An EF class model should be mutable because the whole architecture is based on tracking changed properties. – Gert Arnold Aug 13 '19 at 07:33
  • @IvanStoev After hours of working on it, I figured I hadn't defined a private constructor on the equivalent of MyClass in my own code. If you point that out as an answer I'll accept it. Otherwise I'll answer it myself. – TheAgent Aug 13 '19 at 07:36
  • 1
    You are welcome. Please post self answer, I'm not a fan of direct usage of DDD model as EF Core data (storage) model. – Ivan Stoev Aug 13 '19 at 07:42
  • @GertArnold One of the driving forces of EF Core is to make this framework more and more compatible with DDD. They are trying to avoid forcing you to modify your domain model and use that same model to store data in the database. They specifically point that out in EF Core documentation, including the one you pointed out about implementing value objects. – TheAgent Aug 13 '19 at 07:43
  • @GertArnold Theorethically speaking also, that is the way things should be. The ideal framework is the one that can simply take your model and persist it in any way it knows without the model knowing about it. The fact that we have to translate domain models into other forms to persist them is a technological shortcoming that has to be ultimately remedied. – TheAgent Aug 13 '19 at 07:46
  • You present the model as having a value object as root object. I assume that in the real code this is not the case, which probably explains part of our disagreement. It would be a really painful operation to pull a `MyClass` from the database, modify one property and save it to the same database record. I'm pretty sure your application is based on entity classes (having identity by definition) that are mapped to database tables. – Gert Arnold Aug 13 '19 at 08:02
  • @GertArnold You are correct. My actual model is defined properly, differentiating between ValueObjects and Entities, defining AggregateRoots that inherit from Entity, etc. – TheAgent Aug 13 '19 at 08:04

1 Answers1

4

So here is what happened. As @Gert Arnold pointed out:

1. You need to have private setters on all properties of your domain models. EF Core can't work with read-only properties as of version 2.2.6.

But that was not my problem. It turned out I had forgotten to include a private constructor on the equivalent of MyClass in my own project. I just wish I had seen @Ivan Stoev's comment before I spent hours of work and figured it out. The error message that EF Core gave me was too cryptic, and didn't point out the issue:

No suitable constructor found for entity type 'MyClass'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'valueObject1', 'valueObject2' in 'MyClass(ValueObject valueObject1, ValueObject valueObject2)'.

When in reality, there is no problem with that particular constructor.

2. You just have to have a private, parameter-less constructor if you want EF Core to properly use constructor binding and feed values to your properties through constructor parameters. This is not the case. EF Core simply can't inject entities into other entities using constructor binding. It is basically telling us that particular constructor can't be used, and because it can't find a suitable constructor to use, at all, by providing a parameter-less constructor you are giving it a way to create objects without constructor binding.

3. You should use modelBuilder.OwnsOne<> in your DbContext.OnModelCreating and NOT modelBuilder.Property<> to configure Value Objects for an Entity (in DDD) to be stored in the same database table as the Entity.

I think EF Core needs to give you a clearer message about how it is confused as to which constructor it should use when you don't have a private, parameter-less constructor. I'll bring it up with the EF Core team.

TheAgent
  • 1,472
  • 5
  • 22
  • 42