How to update EF Core 2.0 Owned Entities following a DDD aproach?

.net-2.0 c# domain-driven-design entity-framework-core

Question

I've a project where I'm using Asp.Net Core 2.0 following a DDD approach. So, I have aggregate roots that owns entities and value objects which are configured as owned types, a new feature in EF Core 2.0. I've added an example of such configuration to better clarify.

Domain:

public class Address : ValueObject 
{
    public Address(string street, int number){
        Street = street;
        Number = number;
    }

    public string Street {get; private set;}
    public int Number {get; private set;}
}  
public class Company : AggregateRoot
{
    public Address Address {get; private set;}
    // Other properties...

    // Value objects are immutables so I'm only able to replace it with a new object.
    public void UpdateAddress(string street, int number){
        Address = new Address(street, number);
    }
    // Other methods...
}

EF Core Entity Configuration:

internal class CompanyEntityTypeConfiguration : IEntityTypeConfiguration<Company>
{
    public void Configure(EntityTypeBuilder<Company> builder)
    {
        builder.HasKey(x => x.Id);
        builder.OwnsOne(x => x.Address);
    }
}

I'm retrieving a tracking entity from the database and when I try to update the AggregateRoot replacing a value object with a new one I get an exception saying that "another entity of the same type is already being tracked". The address is the entity being tracked.

Therefore, the solution was to retrieve an untracked but I don't want to use this approach because it will make a full update in the database. So, how can I update a value object(owned entity) from an entity being tracked by Entity Framework 2.0?

1
4
9/12/2017 5:38:27 PM

Popular Answer

I found a workaround when I was working with ef core 1.0.0. I could not find the reference again to put the link here and give the credits to the author but, in your case, the workaround would be like this:

DOMAIN:

public class Address : ValueObject
    {
        public Address(string street, int number)
        {
            Street = street;
            Number = number;
        }

        public string Street { get; private set; }
        public int Number { get; private set; }


        #region Value object workaround
        public Address WithStreet(string value) => new Address(value, Number);

        public Address WithNumber(int value) => new Address(Street, value);
        #endregion
    }
    public class Company : AggregateRoot
    {

        #region public public Address Address { get; set; }
        [NotMapped]
        public Address Address { get; set; }

        //In the DbContext this property needs to be mapped with a column in a database
        private string Address_Street
        {
            get { return Address.Street; }
            set { Address = Address.WithStreet(value); }
        }

        //In the DbContext this property needs to be mapped with a column in a database
        private int Address_Number
        {
            get { return Address.Number; }
            set { Address = Address.WithNumber(value); }
        }
        #endregion


        // Other properties...

        // Value objects are immutables so I'm only able to replace it with a new object.
        public void UpdateAddress(string street, int number)
        {
            Address = new Address(street, number);
        }
        // Other methods...
    }

EF Core Entity Configuration:

internal class CompanyEntityTypeConfiguration : IEntityTypeConfiguration<Company>
    {
        public void Configure(EntityTypeBuilder<Company> builder)
        {
            #region Address value object workaround
            builder.Property(typeof(string), "Address_Street").HasColumnName("Address_Street");
            builder.Property(typeof(int), "Address_Number").HasColumnName("Address_Number");
            #endregion
        }
    }

It is needed to override the SaveChanges() in your DbContext

DbContext:

public class YourDbContext : DbContext
    {
        //yours DbSet<>

        protected override void OnModelCreating(ModelBuilder builder)
        {
            //yours EntityTypeConfiguration
        }

        /// <summary>
        /// Overridden for value object workaround
        /// </summary>
        /// <returns></returns>
        public override int SaveChanges()
        {
            foreach (var entry in ChangeTracker.Entries())
            {
                foreach (var pi in entry.Entity.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic))
                {
                    entry.Property(pi.Name).CurrentValue = pi.GetValue(entry.Entity);
                }
            }
            return base.SaveChanges();
        }
    }

It would be nice if ef core 2.0 owned entities works like EF6.X complex types in order to implements DDD value objects. This workaround will demand some monkey job code but you will be able to apply DDD.

1
9/14/2017 9:13:55 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow