Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 677
Lucas Casagrande Avatar asked Oct 29 '22 02:10

Lucas Casagrande


1 Answers

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.

like image 170
Felipe Rpo N Avatar answered Nov 14 '22 18:11

Felipe Rpo N