1

I have ASP.NET MVC 4 appliation using EF 4.3 with migrations. I use a WebGrid Helper to display details for system users:

@grid.GetHtml(
headerStyle: "gridHeader",
footerStyle: "gridFooter",
firstText: "<< First",
previousText: "< Previous", 
nextText: "Next >",
lastText: "Last >>",
alternatingRowStyle: "gridAlternativeRow",
columns: new[] {
   grid.Column("Login", header: "User Login", canSort: true),
   grid.Column("FullName", header: "User Name", canSort: true),
   grid.Column("Email", header: "User Email", canSort: true),
   grid.Column("Category", header: "User Category", canSort: true),
   grid.Column(
    "", 
    header: "", 
    format: @<text>
            @Html.ActionLink("Edit",   "Edit",   new { id=item.Id} )
            </text>
    )                                                
})

As you can see Edit action method is responsible for editing user details. This is how it passes the View Model to the view:

    public ActionResult Edit(int Id)
    {
        User user = repo.GetUser(Id);

        RegisterModel rm = new RegisterModel();
        rm.Id = user.Id;
        rm.Name = user.FullName;
        rm.UserName = user.Login;
        rm.Email = user.Email;
        rm.UserCategory = user.Category;
        rm.Categories = new List<SelectListItem>();

        List<Category> categories = repo.GetAllCategories();
        foreach (var item in categories)
        {
            SelectListItem sli = new SelectListItem();
            sli.Value = null;
            sli.Text = item.Title;
            if (user.Category == item.Title) sli.Selected = true;
            rm.Categories.Add(sli);
        }

        return View(rm);
    }

And this is how its saves the details:

    [HttpPost]
    public ActionResult Edit(RegisterModel rm, string NewPassword, string OldLogin)
    {
        if (NewPassword != "")
        {
            var token = WebSecurity.GeneratePasswordResetToken(OldLogin);
            WebSecurity.ResetPassword(token, NewPassword);
        }

        User user = new User();
        user.Id = Convert.ToInt32(rm.Id);
        user.FullName = rm.Name;
        user.Email = rm.Email;
        user.Category = rm.UserCategory;
        user.Login = rm.UserName;

        string result = repo.UpdateUserDetails(user);

        return RedirectToAction("Index");
    }

Then it redirects to Index anction method which sources list of users and passes it back to the View with WebGrid Helper.

Every single time I hit the repository I source the most recent values for users from DbContext object:

    public List<User> GetAllUsersWithoutAdmin()
    {
        return context.Users.Where(x => x.Id != 1).OrderBy(x => x.FullName).ToList();
    }

    public User GetUser(int userId)
    {
        return context.Users.FirstOrDefault(x => x.Id == userId);
    }

    public string UpdateUserDetails(User user)
    {
        string info;

        try
        {
            User uUser = context.Users.FirstOrDefault(x => x.Id == user.Id);
            uUser.Category = user.Category;
            uUser.Email = user.Email;
            uUser.FullName = user.FullName;
            uUser.Login = user.Login;

            context.SaveChanges();
            info = "success";
        }
        catch (Exception err)
        {
            info = err.Message;
        }

        return info;
    }

Also I use UoW pattern to solve the problem of using different repositories in the same controller:

public interface IUnitOfWork
{
    int SaveChanges();
}

Each repository then implements this interface:

    private ActivityLogContext context;

    public UserRepository(IUnitOfWork _context)
    {
        context = _context as ActivityLogContext;
    }

And shares it in the same context in the scope of the thread which is implemented in the Ninject Controller Factory by AddBindings() method:

    private void AddBindings()
    {
        ninjectKernel.Bind<IActivityRepository>().To<ActivityRepository>();
        ninjectKernel.Bind<IUserRepository>().To<UserRepository>();
        ninjectKernel.Bind<ICategoryRepository>().To<CategoryRepository>();
        ninjectKernel.Bind<IUnitOfWork>().To<ActivityLogContext>().InThreadScope();
    }

A PROBLEM: For some strange reason, once every so often, when user object is being edited, context object presents wrong values for the user properties. It is happening on the level of EF somewhere between the DbContext and actual data. Especially, that data in SQL server is always right. It looks like EF is caching previous values for the properties and takes those instead of sourcing them from database. I have not observed when this behaviour exactly occurs, but it happens rather often - every second or third time when object is edited. Sometimes it happens couple of times in the row.

I have used the same setup in previous application and everything was fine. The only difference this time is that I'm using WebGrid Helper, and only pages with WebGrid Helper seem to cause this problem in my application???

Bartosz
  • 4,542
  • 11
  • 43
  • 69

2 Answers2

1

Does your data render correctly if you try this?

Load Entities with AsNoTracking:

public List<User> GetAllUsersWithoutAdmin()
{
    return context.Users.AsNoTracking().Where(x => x.Id != 1)
        .OrderBy(x => x.FullName).ToList();
}

public User GetUser(int userId)
{
    return context.Users.AsNoTracking().FirstOrDefault(x => x.Id == userId);
}

Detach Entities after saving:

public string UpdateUserDetails(User user)
{
    string info;

    try
    {
        User uUser = context.Users.FirstOrDefault(x => x.Id == user.Id);
        uUser.Category = user.Category;
        uUser.Email = user.Email;
        uUser.FullName = user.FullName;
        uUser.Login = user.Login;

        context.SaveChanges();

        // detach the entity after saving it
        Context.Entry(uUser).State = System.Data.EntityState.Detached;

        info = "success";
    }
    catch (Exception err)
    {
        info = err.Message;
    }

    return info;
}

This will give you detached entities which aren't tracked in the EF context, an approach that may or not be acceptable depending on your application. Even if you can't use it in the long run, give it a try to see if the problem is really the EF caching.

Jan Van Herck
  • 2,254
  • 17
  • 15
0

I have noticed this behavior also. To demonstrate, Open View, make the change directly in the dB. BANG you will see the change is not reflected in in your view.

EF does cache data and will only "refresh" (bad choice of word here) this data if the context you read from the dB is also the context you had written to the dB with.

Hope this points you int he right direction.

KevDevMan
  • 808
  • 11
  • 23
  • Thanks for help. Still nothing comes up to my mind in how to resolve this issue, since I AM making the change via the context in Edit action method, so it should be reflected – Bartosz Jan 24 '13 at 11:38