1

I have a website that is using ASP.NET MVC 5 with EF6.1 and I'm having a problem with it showing the old data after an Edit procedure. The data is saved to the database properly, but when redirecting to the Index view it still shows old data.

If I go back to the Edit view, it still shows the old data also. The DBContext does not seem to be refreshing the data.

I have a base controller that holds the DBContext if that matters.

Here's the code for the Edit views in my controller:

public ActionResult FeaturedItemEdit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        using (nfmContext localContext = new nfmContext())
        {
            List<FeaturedItem> fi = localContext.FeaturedItems.AsNoTracking().ToList();

            FeaturedItem featuredItem = fi.Find(x => x.ID.Equals(id));

            if (featuredItem == null)
            {
                return HttpNotFound();
            }
            return View(featuredItem);
        }
    }

 [HttpPost]
[ValidateAntiForgeryToken]
public ActionResult FeaturedItemEditPost(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        using (nfmContext db = new nfmContext())
        {
            var item = db.FeaturedItems.Find(id);
            if (TryUpdateModel(item, "", new string[] { "ID", "Title", "ImageAlt", "ImagePath", "Link", "Visible", "Content" }))
            {
                try
                {
                    item.TimeStamp = DateTime.Now;
                    db.Entry(item).State = EntityState.Modified;
                    db.SaveChanges();

                    return RedirectToAction("FeaturedItems");
                }
                catch (DataException ex)
                {
                    Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
                }
            }
            return View(item);
        }
    }

Once the edit is done, it goes back to the Featured Items view which just loads a list of items:

 public ActionResult FeaturedItems()
    {
         using (nfmContext localContext = new nfmContext())
        {
            return View(localContext.FeaturedItems.OrderBy(x => x.Visible).ThenBy(x => x.Title).AsNoTracking().ToList());
        }
    }

Nothing fancy there. At first I thought it was because the calls were all async but in changing it back to non-asynchronous it didn't make much of a difference. I haven't posted the code to create an item, however it has the same issue.

I've even decorated my controller with [OutputCache(Location = System.Web.UI.OutputCacheLocation.None)] to make sure it wasn't a client side caching issue.

So, am I doing something wrong or is there a way to force the DBContext to pull fresh records from the database?

UPDATE 1: Here's the line that initializes my DBContext in the base controller:

        <strike>protected nfmContext db = new nfmContext();</strike>

This is no longer used - see update 2

And here's my context setup:

 public class nfmContext : DbContext
{
    public nfmContext() : base("connectionStringHere")
    {
    }

    public DbSet<FeaturedItem> FeaturedItems { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

        base.OnModelCreating(modelBuilder);

    }
}

UPDATE 2: I've switched the DBContect to now be local to each method. The code above reflects the changes and I've removed the DBContect property from the base controller.

hacker
  • 1,115
  • 1
  • 15
  • 28
  • Does it work if you put this line `db = new YourContextName()` after the modification ? – Antoine Pelletier Feb 17 '17 at 15:00
  • Yes, it is because you are not closing your context. See [here](http://stackoverflow.com/questions/14449308/entity-framework-returning-old-data) I recommend using an IoC container like [Autofac](http://stackoverflow.com/questions/29560294/inject-dbcontext-with-autofac). – Steve Greene Feb 17 '17 at 15:15
  • You should add the code which initializes the dbcontext since it plays an important role in this question and I'm thinking the problem will be there. – BennyM Feb 17 '17 at 15:50
  • @AntoinePelletier - what modification? Do you mean after calling SaveChanges? – hacker Feb 17 '17 at 17:04
  • If you look at your physical db table after a savechanges, does it contain the new data or the old data? If it's the old data, it could be because your not returning the changes made to the form (httpget) and you're calling the existing data from the db in your HttpPost before saving the changes. – nocturns2 Feb 17 '17 at 17:42
  • @Hacker yes just after save changes – Antoine Pelletier Feb 17 '17 at 18:06
  • @AntoinePelletier It won't allow me to re-assign the variable since I've now got it in a `using` block – hacker Feb 17 '17 at 19:00
  • @BennyM I've edited the question to show the code. – hacker Feb 17 '17 at 19:05
  • @nocturns2 The database table does show the changes so it is updating properly. After the `.SaveChanges()` I redirect to the `FeaturedItems` method which is a separate request. Also, if I go back in and re-edit the same item again, it shows the old data and not the new data – hacker Feb 17 '17 at 19:06
  • @SteveGreene If you check the code now, is that what you'd consider closing the context? Sorry, I'm new to EF so I'm not sure if that's what you meant. – hacker Feb 17 '17 at 19:07
  • 1
    I have an idea maybe, replace `return View(localContext.FeaturedItems.OrderBy(x => x.Visible).ThenBy(x => x.Title).AsNoTracking().ToList());` by `var v = localContext.FeaturedItems.OrderBy(x => x.Visible).ThenBy(x => x.Title).AsNoTracking().ToList());` and check into v to see if your datas are ok before sending them to view – Antoine Pelletier Feb 17 '17 at 21:04
  • Also check for lazy loading, be sure it dosen't happen here – Antoine Pelletier Feb 17 '17 at 21:05

2 Answers2

0

Always use your dbContext only as long as required. Usually use a using statement within a method. Reopening a connection to the database doesn't take long due to connection pooling.

  • 1
    I've made the DBContext local within each method and wrapped it with `using` statements but that didn't change anything unfortunately. – hacker Feb 17 '17 at 17:03
0

Found out the issue after digging through and searching the project for various caching implementations.

At some point, EntityFramework.Cache was setup in the project (or EFCache as it's referred). That was causing the caching issue even though it was set up based on their documentation.

SaveChanges is supposed to be done in a transaction (which I assume is the case) and EFCache is supposed to watch for transactions and refresh the cache. Somewhere with the two there is a break down where the cache is not getting expired.

For reference, here's what I was using to implement the Secondary caching for the framework:

    public class Configuration : DbConfiguration
{
    public Configuration()
    {
        var transactionHandler = new CacheTransactionHandler(new InMemoryCache());
        AddInterceptor(transactionHandler);

        var cachingPolicy = new CachingPolicy();

        Loaded +=
            (sender, args) => args.ReplaceService<DbProviderServices>(
                (s, _) => new CachingProviderServices(s, transactionHandler,
                cachingPolicy));
    }


}

Once I've removed this code from the project, the system worked perfectly as expected, but that also removed the feature of having extra caching.

In order to get it to work and give me the ability to purge the DbSet on demand, I switched the declaration of the CacheTransactionHandler to use a static InMemoryCache. Then, once it was set that way, I could use InvalidateDbSets to remove the item from the Memory Cache.

Here's what I did specifically:

  • Added this to my DbContext:

public static readonly EFCache.InMemoryCache Cache = new EFCache.InMemoryCache();

  • Changed the transactionHandler declaration in the Configuration sub to:

var transactionHandler = new CacheTransactionHandler(nfmContext.Cache);

  • After any .SaveChanges calls, I added:

nfmContext.Cache.InvalidateSets(new List<string>() { "[insert entity name here]" });

Everything works now and it uses the cache whenever needed. After any time I make a change, I clear that cache item and it reloads the next time in. Works great.

hacker
  • 1,115
  • 1
  • 15
  • 28