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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With