Entity Framework Core 2.1 - owned types and nested value objects
Asked Answered
B

1

8

I'm learning DDD and the tutorial I'm currently following is implemented using NHibernate, but since my lack of experience with it I've decided to go through the course using EF Core 2.1.

However, I'm currently a bit stuck with the following: I have three classes Customer which is an entity and two value objects (CustomerStatus and inside of it value object ExpirationDate) - like this:

public class Customer : Entity
{
    //... constructor, other properties and behavior omitted for the sake of simplicity
    public CustomerStatus Status { get; set; }
}

public class CustomerStatus : ValueObject<CustomerStatus>
{
    // customer status is enum containing two values (Regular,Advanced)
    public CustomerStatusType Type { get; }
    public ExpirationDate ExpirationDate { get; }
}

public class ExpirationDate : ValueObject<ExpirationDate>
{
    //... constructor, other properties and behavior omitted for the sake of simplicity
    public DateTime? Date { get; private set; }
}

When I try to do the following inside of my DbContext:

modelBuilder.Entity<Customer>(table =>
{      
   table.OwnsOne(x => x.Status,
     name =>
     {
        name.Property(x => x.ExpirationDate.Date).HasColumnName("StatusExpirationDate");
        name.Property(x => x.Type).HasColumnName("Status");
     });
});

I'm getting the following error:

The expression 'x => x.ExpirationDate.Date' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'.
Parameter name: propertyAccessExpression'

Beside this I've tried doing things like:

table.OwnsOne(x => x.Status.ExpirationDate,
      name =>
      {
         name.Property(x => x.Date).HasColumnName("StatusExpirationDate");
      });
 table.OwnsOne(x => x.Status,
      name =>
      {
          name.Property(x => x.Type).HasColumnName("Status");
      });

But it also leads to:

The expression 'x => x.Status.ExpirationDate' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'.

I've also tried:

modelBuilder.Entity<Customer>()
                    .OwnsOne(p => p.Status, 
              cb => cb.OwnsOne(c => c.ExpirationDate));

But with no luck as well... Anyways, any help would be greatly appreciated, also if possible it would be really great if someone could explain why none of my tries are not working? Thanks in advance!

UPDATE

After doing as stated in Ivan's comment first I was getting error about CustomerStatus class constructor so I've added default protected one.

After that I started getting error:

Field 'k__BackingField' of entity type 'CustomerStatus' is readonly and so cannot be set.

Here's inner of my CustomerStatus class if it helps:

public class CustomerStatus : ValueObject<CustomerStatus>
{
    public CustomerStatusType Type { get; }
    public ExpirationDate ExpirationDate { get; }

    public static readonly CustomerStatus Regular =
        new CustomerStatus(CustomerStatusType.Regular, ExpirationDate.Infinite);
    public bool IsAdvanced => Type == CustomerStatusType.Advanced && !ExpirationDate.IsExpired;

    private CustomerStatus(CustomerStatusType type, ExpirationDate expirationDate)
    {
        Type = type;
        ExpirationDate = expirationDate;
    }

    protected CustomerStatus()
    {

    }
    public static CustomerStatus Create(CustomerStatusType type, ExpirationDate expirationDate)
    {
        return new CustomerStatus(type, expirationDate);
    }

    public CustomerStatus Promote()
    {
        return new CustomerStatus(CustomerStatusType.Advanced, ExpirationDate.Create(DateTime.UtcNow.AddYears(1)).Value);
    }

    protected override bool EqualsCore(CustomerStatus other)
    {
        return Type == other.Type && ExpirationDate == other.ExpirationDate;

    }

    protected override int GetHashCodeCore()
    {
        return Type.GetHashCode() ^ ExpirationDate.GetHashCode();
    }
}

UPDATE

All it took was adding private setters on Type and ExpirationDate properties inside of CustomerStatus class and in combination with Ivan's answer it works like a charm. Thanks a lot!

Brunk answered 6/12, 2018 at 13:5 Comment(0)
Q
15

Your attempts are not working because owned types can only be configured through their owner entity, and more specifically, through their own builder returned by the OwnsOne method or provided as an argument of the Action<T> argument of the OwnsOne method of the owner entity builder.

So the configuration should be something like this (note the nested OwnsOne):

modelBuilder.Entity<Customer>(customer =>
{      
    customer.OwnsOne(e => e.Status, status =>
    {
        status.Property(e => e.Type).HasColumnName("Status");
        status.OwnsOne(e => e.ExpirationDate, expirationDate =>
        {
            expirationDate.Property(e => e.Date).HasColumnName("StatusExpirationDate");
        });
    });
});
Quoin answered 6/12, 2018 at 13:23 Comment(3)
Thank you for your quick reply and explanation, however when I do as stated I'm getting new error which states following: : 'Field '<Type>k__BackingField' of entity type 'CustomerStatus' is readonly and so cannot be set.' Ill update my question with new problem and add properties of CustomerStatus class...Brunk
Darn, all it took was adding private setters on both properties...Sorry for my dumbness -.- I'm marking your answer as correct one, thanks a ton once again! CheersBrunk
I was going to add comment that EF (Core) doesn't play well with DDD and value objects because EF Core has it's own requirements for "entity" types, their constructors, properties, fields etc. which are usually different from DDD. But I decided not to, because it wasn't part of the question.Quoin

© 2022 - 2024 — McMap. All rights reserved.