Dhouha Ben Achour
Dhouha Ben Achour

Reputation: 23

Error on update with Entity Framework 5: data may have been modified

This is my repository with Entity Framework:

public class MetricRepository : IMetricRepository
{
    private readonly AccountStatsContext _context;

    public MetricRepository(AccountStatsContext context)
    {
        _context = context;
    }

    public async Task<bool> Add(MetricEntity metric)
    {
            metric.UpdatedOnUtc = DateTime.UtcNow;
            await _context.Metrics.AddAsync(metric).ConfigureAwait(false);
            await _context.SaveChangesAsync().ConfigureAwait(false);
            return true;
    }

    public async Task<bool> Update(MetricEntity metric)
    {
            metric.UpdatedOnUtc = DateTime.UtcNow;
            await _context.SaveChangesAsync();
            return true;
    }

    public async Task<MetricEntity> FindAsync(Expression<Func<MetricEntity, bool>> predicate)
    {
        return await _context.Metrics.SingleOrDefaultAsync(predicate);
    }
}

And in my table I have a composite primary key with 4 columns like this:

CREATE TABLE public.metrics
(
    category character (30) COLLATE pg_catalog."default" NOT NULL,
    code character (30) COLLATE pg_catalog."default" NOT NULL,
    repartition_key character (30) COLLATE pg_catalog."default" NOT NULL,
    date timestamp without time zone NOT NULL,
    value numeric(18,2),
    type character (5) COLLATE pg_catalog."default" NOT NULL,
    updated_on_utc timestamp without time zone NOT NULL,

    CONSTRAINT metrics_pkey PRIMARY KEY (category, code, repartition_key, date)
);

And the primary key is set in my Entity Framework repository

private void BuildEntity(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<MetricEntity>()
               .ToTable("metrics")
               .HasKey(c => new { c.Category, c.Code, c.RepartitionKey, c.Date });
}

When I make an insert it's ok but when I try to update a value, I get this error:

Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded

Here is my code for the update:

public async Task<BaseResponse> Handle(MetricsCommand request, CancellationToken cancellationToken)
{
    var requestMapped = _mapper.Map<MetricEntity>(request);
    var result = await _metricRepository.FindAsync(m => m.Category == requestMapped.Category && m.Code == requestMapped.Code && m.RepartitionKey == requestMapped.RepartitionKey && m.Date == requestMapped.Date);

    bool response = false;

    if (result == null)
        response = await _metricRepository.Add(requestMapped);
    else
    {
        result.Value = requestMapped.Value;
        result.Type = requestMapped.Type;
        response = await _metricRepository.Update(result);
    }

    return new BaseResponse(response);
}

Upvotes: 0

Views: 228

Answers (3)

Serge
Serge

Reputation: 43850

I always use this algoritm to update only several properties, it has never failed

  var metric = _mapper.Map<MetricEntity>(request); //I hope your code works properly

   var existedMetric = await _context.FindAsync(m => m.Category == requestMapped.Category && m.Code == requestMapped.Code && m.RepartitionKey == requestMapped.RepartitionKey && m.Date == requestMapped.Date);
  
if (existedMetric!=null)
{
 context.Entry(existedMetric).Property(m=> m.Value ).CurrentValue = metric.Value;
 context.Entry(existedMetric).Property(m=> m.Type).CurrentValue =  metric.Type;
 context.Entry(existedMetric).Property(m=> m.UpdatedOnUtc).CurrentValue =  DateTime.UtcNow;

 await _context.SaveChangesAsync();
}

Upvotes: 1

Matt Ghafouri
Matt Ghafouri

Reputation: 1577

Change your Generic Update method like this. You should set the State of your Entity to Modified

 public async Task<bool> Update(MetricEntity metric)
 {
        metric.UpdatedOnUtc = DateTime.UtcNow;
        _context.Entry(metric).State = EntityState.Modified; 
        await _context.SaveChangesAsync();
        return true;
 }

Upvotes: -1

Resurrectiontent
Resurrectiontent

Reputation: 38

The best way to use a database context is to create and dispose it right before and after it is used. But since you are already using another approach, I may only suppose that the following code can help you. Insert the provided line after metric.UpdatedOnUtc = DateTime.UtcNow; in your Update method of MetricRepository class:

_context.Entry(metric).State = EntityState.Modified;

UPD: If it doesn't help, there is another way of modifying entities in a dbcontext. Insert it in same place as the previous hint:

_context.Update(metric);

Upvotes: 1

Related Questions