Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework Core 2.1 - owned types and nested value objects

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!

like image 794
Exerlol Avatar asked Dec 06 '18 13:12

Exerlol


1 Answers

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");
        });
    });
});
like image 193
Ivan Stoev Avatar answered Sep 28 '22 17:09

Ivan Stoev