4

I'm building a project in EF6 and aspnet Identity.

I'm facing the following problem:

if I call

var account = await FindByNameAsync(userName); // account.IsConfirmed = true

I get the account I'm looking for (example: isConfirmed = true).

When I manually change a value in my database (isConfirmed = true -> isConfirmed = false) and I run my query again, I still get my old account object (isConfirmed = true)

var account = await FindByNameAsync(userName); // Should be account.IsConfirmed = false, but still gives me IsConfirmed = true

I've tried adding the following in my DbContext constructor

> this.Configuration.ProxyCreationEnabled = false;
> this.Configuration.LazyLoadingEnabled = false;

But this didn't change anything.

What can I do about this? How long does the cached data remain? All the posts I've seen, require you to run the query (from .. in ..), but seeing how I'm using aspnet Identity and I have no control over these things, what can I do?

Thanks!

EDIT: added dbContext info

My IoC (Unity)

container.RegisterType<IUnitOfWork, UserManagementContext>(new HttpContextLifetimeManager<IUnitOfWork>());
container.RegisterType<IUserStore<Account>, UserStore<Account>>(new InjectionConstructor(container.Resolve<IUnitOfWork>()));

HttpContextLifeTimeManager:

public class HttpContextLifetimeManager<T> : LifetimeManager, IDisposable
{
    public override object GetValue()
    {
        return HttpContext.Current.Items[typeof(T).AssemblyQualifiedName];
    }

    public override void SetValue(object newValue)
    {
        HttpContext.Current.Items[typeof(T).AssemblyQualifiedName] = newValue;
    }

    public override void RemoveValue()
    {
        HttpContext.Current.Items.Remove(typeof(T).AssemblyQualifiedName);
    }

    public void Dispose()
    {
        RemoveValue();
    }
}

My IUnitOfWork

public interface IUnitOfWork : IDisposable
{
    void Save();
    Task SaveAsync();
    DbSet<TEntity> EntitySet<TEntity>() where TEntity : class;
    void MarkAsModified<TEntity>(TEntity entity) where TEntity : class;
}

My UserManagementContext

public class UserManagementContext : IdentityDbContext<Account>, IUnitOfWork
{
    static UserManagementContext()
    {
        //Database.SetInitializer<UserManagementContext>(new RecreateDatabase());
        Database.SetInitializer<UserManagementContext>(null);
    }

    public UserManagementContext()
        : base("Name=UserManagementConnection")
    {
        this.Configuration.ProxyCreationEnabled = false;
        this.Configuration.LazyLoadingEnabled = false;
    }

    // ... (my Dbsets)

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // configuration ..
    }

    public void Save()
    {
        SaveChanges();
    }

    public async Task SaveAsync()
    {
        await SaveChangesAsync();
    }

    public DbSet<TEntity> EntitySet<TEntity>() where TEntity : class
    {
        return this.Set<TEntity>();
    }

    public void MarkAsModified<TEntity>(TEntity entity) where TEntity : class
    {
        this.Entry(entity).State = EntityState.Modified;
    }
}

UPDATE:

I discovered another strange thing. When I set my last login date field, that change gets picked up, but when I set my isConfirmed field, that doesn't get picked up.. (the DB change actually gets overwriten by the cached data!

So this confirms that data entered via code get persisted, but manual changes in the DB get ignored.

UPDATE 2 In case anyone has this problem as well: the problem was not aspnet Identity, it's EF.

What I did was implemented my own userstore and manually accessed EF and used .AsNoTracking() to avoid the caching.

Team-JoKi
  • 1,766
  • 3
  • 15
  • 23
  • How does your FindByNameAsync look like? – Akash Kava Jan 10 '14 at 11:31
  • @AkashKava It's a function from aspnet identity (=> UserManager) – Team-JoKi Jan 10 '14 at 11:32
  • Are you using one instance per request or you are using one instance of DbContext for entire app? DbSet's Find method caches value in local object unless you explicitly clear it. This is problem with Entity Framework. – Akash Kava Jan 10 '14 at 11:39
  • I'm using one dbContext for the lifespan of one request (MVC) – Team-JoKi Jan 10 '14 at 11:45
  • Can you post some code of that? You must know that HttpContext and some of cached members do not work well with async code as multiple async methods execute on same thread. – Akash Kava Jan 10 '14 at 11:54

1 Answers1

3

HttpContext.Current is evil in Async programming.

Synchronous or earlier code would only execute methods of one context and one controller per thread. So there was no conflict.

In asynchronous programming, methods of multiple controller instances are executed on same thread. So value of HttpContext.Current is not same as you think, it is dirty !!!

Instead you should preserve your HttpContext and use it inside your async code as shown below.

public class HttpContextLifetimeManager<T> : LifetimeManager, IDisposable
{

    private HttpContext Context; 

    public HttpContextLifetimeManager(HttpContext context){
        this.Context = context;
    }

    public override object GetValue()
    {
        return Context.Items[typeof(T).AssemblyQualifiedName];
    }

    public override void SetValue(object newValue)
    {
        Context.Items[typeof(T).AssemblyQualifiedName] = newValue;
    }

    public override void RemoveValue()
    {
        Context.Items.Remove(typeof(T).AssemblyQualifiedName);
    }

    public void Dispose()
    {
        RemoveValue();
    }
}


container.RegisterType<IUnitOfWork, UserManagementContext>(
   new HttpContextLifetimeManager<IUnitOfWork>(this.ControllerContext.HttpContext)); 

Plain Old Inheritance

I would recommend using abstract entity controller pattern, which is easy to use in async mode.

public abstract class EntityController<TDbContext> : Controller
   where TDbContext: DbContext
{

    protected TDbContext DB { get; private set;}

    public EntityController(){
        DB = Activator.CreateInstance<TDbContext>();
    }

    protected override void OnDispose(){
        DB.Dispose();
    }
}

Derive your controller accordingly like,

public class UserController : EntityController<UserManagementContext>
{


    public async Task<ActionResult> SomeMethod(){
        ......
        var user = await DB.FindByNameAsync(userName);
        ......
    }

}

If you still want to use Unity then you will have to create new unity instance per request, but that is just waste of CPU cycle. In my opinion using Unity in MVC for simpler task is just over programming. If something that is easily done with abstract classes. Asynchronous programming has lot of new things, Unity was not designed for that.

Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • So my unity container should look like: container.RegisterType(new HttpContextLifetimeManager(HttpContext.Current)); ? Because I still get a cached result with that new HttpContextLifeTimeManager :/ – Team-JoKi Jan 10 '14 at 12:24
  • I don't have access to the controllerContext (My unity container setup is a static class that gets called in my global.asax), so as far as I know, HttpContext.Current is my only access point to get the HttpContext? – Team-JoKi Jan 10 '14 at 12:29
  • See plain old inheritance way, you can still use Unity but will require more work. – Akash Kava Jan 10 '14 at 14:50
  • I found my mistake. container.RegisterType, UserStore>(new InjectionConstructor(container.Resolve())); <-- this doesn't use the IUnitOfwork, because it only accepts DbContext as a parameter, so it used the default parameter, which creates a new instance. This is what caused all my problems! so I just cast the IUnitOfWork to DbContext in the constructor – Team-JoKi Jan 21 '14 at 09:53