mikeyy
mikeyy

Reputation: 933

Navigation property does not reset to null

I have a simple application, that registers a collection of money from batches of sold tickets from drivers (which domain expert calls settlements). I try to use DDD approach and EF Core to handle this small app (call it a playground for using EF Core in DDD). I have basically 3 tables in SQL Server (simplified to the absolute minimum):

Table: Collection
-------------+---------
   Column    | Type    
-------------+---------
CollectionId | int (PK)    
IsCancelled  | bit     
Table: Settlement
-------------+---------
   Column    | Type    
-------------+---------
SettlementId | int (PK)
CollectionId | int (FK)
Number       | int     
Table: CollectionSettlementJoin
-------------+---------
   Column    | Type    
-------------+---------
SettlementId | int (PK)(FK)
CollectionId | int (PK)(FK)

I know there seems to be a redundant join table (since I have the CollectionId on the Settlement table), but it seems to be a design requirement, that I will explain in a moment. So each collection has at least 1 or more settlements. I have 2 domain entities which actually correspond with the tables - my aggregate root Collection and attached to it Settlements property that contains list of Settlement entities. The extra table is used for auditing purposes as actually does not take real part in the domain. It is populated by a trigger on Settlement.CollectionId update (for non-nulls). Each collection can be cancelled within 5 minutes of its creation by the creator or anytime by a superuser. When a collection is cancelled, I want to reset Settlement.CollectionId to null (when that happens data in CollectionSettlementJoin stays and I can always get back what settlements were cancelled).

My current setup is working fine when comes to creating a collection. The selected settlements are added, saved and successfully persisted in my database. The problem starts when I want to cancel a collection. I get from the database the collection with attached settlements . But when I remove the settlements from my aggregate root, dbContext.SaveChanges() does not persist the changes (does not set Settlement.CollectionId to null).

Here is my setup:

public class Collection : EntityBase, IAggregateRoot
{
    private Collection() { }
    public Collection(List<Settlement> settlements)
        {
            _settlements = settlements;
        }
    private bool _isCancelled;
    public bool IsCancelled => _isCancelled;

    private List<Settlement> _settlements;
    public IReadOnlyCollection<Settlement> Settlements => _settlements.AsReadOnly();

    public void CancelCollection()
    {
        if (_isCancelled != true)
        {
            _isCancelled = true;
            _settlements.Clear();
        }
    }
}
public class Settlement : EntityBase
{
    private Settlement() { }
    public Collection? Collection { get;set; }
    public int? CollectionId { get; internal set; }
}
public class CollectionEntityTypeConfiguration : IEntityTypeConfiguration<Collection>
{
    public void Configure(EntityTypeBuilder<Collection> builder)
    {
        builder.ToTable("Collection");
        builder.HasKey(s => s.Id);
        builder.Property(s => s.Id).HasColumnName("CollectionId").UseIdentityColumn();
        builder.Property(s => s.IsCancelled).HasDefaultValueSql("(0)");
        builder.HasMany(s => s.Settlements)
            .WithOne(c => c.Collection)
            .HasForeignKey(s => s.CollectionId);
    }
}
public class SettlementEntityTypeConfiguration : IEntityTypeConfiguration<Settlement>
{
    public void Configure(EntityTypeBuilder<Settlement> builder)
    {            
        builder.ToTable("Settlement");
        builder.HasKey(s => s.Id);
        builder.Property(s => s.Id).HasColumnName("SettlementId").ValueGeneratedNever();
        builder.Property(s => s.CollectionId);
        builder.HasOne(s => s.Collection)
            .WithMany(c => c.Settlements)
            .HasForeignKey(s => s.CollectionId);                
    }
}

Context (not much here)

public class SettlementCollectionContext: DbContext
{        
    public SettlementCollectionContext(
        DbContextOptions<SettlementCollectionContext> options) : base(options)
    {
        ChangeTracker.StateChanged += ChangeTracker_StateChanged;
    }

    private void ChangeTracker_StateChanged(object sender, EntityStateChangedEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine($"Entity {e.Entry.Entity.GetType().Name} has changed.");
    }

    public DbSet<Collection> Collections { get; set; }
    public DbSet<Settlement> Settlements { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new SettlementEntityTypeConfiguration());
        modelBuilder.ApplyConfiguration(new CollectionEntityTypeConfiguration());
    }
}

And finally my command:

var collection = await _dbContext.Collections
    .Include(c => c.Settlements)
    .Where(c => c.Id == collectionId)
    .SingleOrDefaultAsync();

if (!collection!.IsCancelled)
    {
        collection.CancelCollection();
    }

_dbContext.Update(collection); //without this the change tracker does not register the change
_dbContext.SaveChanges();

I know that the ChangeTracker registeres this change, becuase I added in my context an even handler to ChangeTracker.StateChanged and during debugging I noticed it register that collection has changed (although not the settlement). I also tried to reset Settlement.CollectionId property to null in Collection.CancelCollection() method, but this did not help either. I must be missing something.

Upvotes: 1

Views: 86

Answers (1)

mikeyy
mikeyy

Reputation: 933

So in the end nothing is wrong with the setup. Actually my unit of work was a problem, because it was saving incorrect context, so obviously the changes were never persisted.

Upvotes: 1

Related Questions