Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EntityFramework update fails randomly on SaveChanges / ASP.NET MVC / postbacks

I'm doing an entity update from a Postback in MVC:

ControlChartPeriod period = _controlChartPeriodService.Get(request.PeriodId);

if (period != null)
{
    SerializableStringDictionary dict = new SerializableStringDictionary();

    dict.Add("lambda", request.Lambda.ToString());
    dict.Add("h", request.h.ToString());
    dict.Add("k", request.k.ToString());

    period.Parameters = dict.ToXmlString();

    // ToDo : fails on save every now and then when one of the values changes; fails on Foreign Key being null
    try
    {
        _controlChartPeriodService.Update(period, request.PeriodId);
        return Ok(request);
    }

The update method looks like this:

public TObject Update(TObject updated, TKey key)
{
    if (updated == null)
        return null;

    TObject existing = _context.Set<TObject>().Find(key);
    if (existing != null)
    {
        _context.Entry(existing).CurrentValues.SetValues(updated);
        _context.SaveChanges();
    }
    return existing;
}

public TObject Get(TKey id)
{
    return _context.Set<TObject>().Find(id);
}

The weird thing is first time I run it it usually works fine; if i do a second post back it does not work and fails on an EntityValidation error of a foreign key; however examining the entity the foreign key looks fine and is untouched.

Do I need to somehow synchronize the Context somewhere?

I've been trying to find the differences in when it succeeds or when it does not succeed.

I'm using Injection for the repositories:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped((_) => new DataContext(ConfigSettings.Database.ConnectionString));
    services.AddScoped<ControlChartPeriodService, ControlChartPeriodService>();
}

-- update:

[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual ICollection<ControlChartPoint> ControlChartPoints { get; set; }

[Required]
public virtual ControlChart ControlChart { get; set; }
public string Parameters { get; set; }

In the ControlChartMap we have the following:

HasMany(x => x.Periods)
    .WithRequired(c => c.ControlChart);
like image 620
Martin Avatar asked Sep 26 '19 08:09

Martin


1 Answers

If you are expecting that the loading of the TObject includes the relationship as well, seems to me your problem is in this line:

TObject existing = _context.Set<TObject>().Find(key);

Find will only load the TObject record but not its relationship(s) , and as there is no explicit reference in code to the ControlChart property it doesn't get lazy loaded - which seems to be the behavior that you are expecting , so I'm assuming you have lazy-loading enabled

The fact that the relationship has been configured as

HasMany(x => x.Periods).WithRequired(c => c.ControlChart);

indicates that TObject's ControlChart property is meant to be a related set of Periods but that doesn't mean that it will force the lazy-load of the relationship when loading a TObject.

(I personally try to avoid the reliance on lazy loading due to things like this, and prefer to actually disable it and to eagerly load the relationships via Include, or alternatively in a less measure, an explicit load , by using Load. It may be a matter of personal taste but I've found this way saves you from many headaches)

So If you have lazy loading enabled and like the way it works, make a reference to the ControlChart property at any point between the load and before saving, like

var chart= existing.ControlChart;

or something similar

Or for non-lazy-loading scenarios, although it can be used in any case:

 TObject existing = _context.Set<TObject>().Where(x=>x.Id==key).Include(x=> x.ControlChart).FirstOrDefault();
like image 176
Renincuente Avatar answered Sep 19 '22 17:09

Renincuente