Home > Software design >  HasOne relationship cannot be set to null inside ForEach
HasOne relationship cannot be set to null inside ForEach

Time:12-01

Say there is a model that has entries. Each entry has a currency. I want to update the currency like so:

var model = await _dbContext.Models
    .Include(x => x.Entries) 
    .FirstOrDefaultAsync(x => x.Id.ToString().Equals(command.Id), cancellationToken);

model.Name = command.Name;

command.Entries.ForEach(x =>
{
    var entry = _dbContext.Entries
        .Find(Guid.Parse(x.Id));

    var currency = _dbContext.Currencies
        .FirstOrDefault(y => y.Id.Equals(x.CurrencyId));

    entry.Currency = currency;
});

await _dbContext.SaveChangesAsync(cancellationToken);

Here is how the currency relationship is defiend:

modelBuilder.Entity<Entry>()
    .HasOne<Currency>(x => x.Currency)
    .WithMany();

I am debugging a problem where currency can be assigned a value but not null. E.g.

command.Entries.ForEach(x =>
{
    var entry = _dbContext.Entries
        .Find(Guid.Parse(x.Id));

    entry.Currency = null;
});

won't set the currency to null but this works:

command.Entries.ForEach(x =>
{
    var entry = _dbContext.Entries
        .Find(Guid.Parse(x.Id));

    var currency = ...

    entry.Currency = currency;
});

Currency is updated correctly. Now when I do the update outside of ForEach it works:

var entry = _dbContext.Entries.Find(Guid.Parse(".."));

entry.Currency = null;

await _dbContext.SaveChangesAsync(cancellationToken);

So ForEach seems to be the issue. Any ideas why? It's strange that the currency can be updated to a value but cannot be set to null inside ForEach.

CodePudding user response:

The main cause of the issue is that the Currency is navigation property, and is not loaded automatically (see Loading Related Data), thus has null value and setting it to null has no effect, since the change tracker does not see a change in that property.

It can be fixed with simple ThenInclude to eager load it when retrieving the main data:

.Include(x => x.Entries)
    .ThenInclude(e => e.Currency)

Everything else could stay as is.

  • Related