ASP.NET Identity中的Entity Framework缓存

4
我是一位有用的助手,可以为你进行文本翻译。以下为您需要翻译的内容:

我正在使用EF6和Asp.Net身份验证构建一个项目。

我遇到了以下问题:

如果我调用

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

我获取了我要查找的账户(例如:isConfirmed=true)。

当我手动修改数据库中的值(isConfirmed=true -> isConfirmed=false),再次运行查询时,我仍然得到旧的账户对象(isConfirmed=true)。

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

我尝试在我的DbContext构造函数中添加以下内容
> this.Configuration.ProxyCreationEnabled = false;
> this.Configuration.LazyLoadingEnabled = false;

但这并没有改变任何事情。

我该怎么办呢?缓存数据的保留时间有多长? 所有我看到的帖子都要求你运行查询(从..中..),但是考虑到我正在使用aspnet Identity,并且我无法控制这些内容,我该怎么办呢?

谢谢!

编辑:添加了dbContext信息

我的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();
    }
}

我的 IUnitOfWork

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

我的用户管理上下文

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;
    }
}

更新:

我发现了另一件奇怪的事情。当我设置我的最后登录日期字段时,该更改被捕捉到了,但是当我设置我的isConfirmed字段时,却没有被捕捉到..(实际上,DB更改被缓存数据覆盖了!)

因此,这证实了通过代码输入的数据会被持久化,但是在数据库中进行的手动更改会被忽略。

更新2:

如果有人也遇到这个问题:问题不在于aspnet Identity,而是EF。

我的做法是实现自己的userstore,并手动访问EF并使用.AsNoTracking()来避免缓存。


你的 FindByNameAsync 是什么样子的? - Akash Kava
@AkashKava 这是来自 Asp.Net Identity 的函数 (=> UserManager<IUser>)。 - Team-JoKi
你是每个请求使用一个实例还是整个应用程序使用一个DbContext实例?DbSet的Find方法会在本地对象中缓存值,除非你明确清除它。这是Entity Framework的一个问题。 - Akash Kava
我在一个请求的生命周期(MVC)中使用一个dbContext。 - Team-JoKi
你能贴一些代码吗?你必须知道HttpContext和一些缓存成员与异步代码不兼容,因为多个异步方法在同一个线程上执行。 - Akash Kava
1个回答

3

HttpContext.Current在异步编程中是有缺陷的。

同步或更早期的代码只会在一个上下文和一个控制器线程中执行方法。因此不会发生冲突。

在异步编程中,多个控制器实例的方法在同一线程上执行。因此HttpContext.Current的值并不像您想象的那样相同,它是脏数据!

相反,您应该保留您的HttpContext,并在您的异步代码中使用它,如下所示。

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)); 

传统继承

我建议使用抽象实体控制器模式,这种模式在异步模式下很容易使用。

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();
    }
}

根据需要,相应地调整您的控制器,例如:
public class UserController : EntityController<UserManagementContext>
{


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

}

如果您仍然想使用Unity,则需要每个请求创建一个新的Unity实例,但这只是对CPU循环的浪费。在我看来,对于较简单的任务,在MVC中使用Unity就是过度编程。如果有些事情可以使用抽象类轻松完成,则不建议使用Unity。异步编程有很多新的东西,而Unity并没有为此而设计。


那么我的Unity容器应该是这样的:container.RegisterType<IUnitOfWork,UserManagementContext>(new HttpContextLifetimeManager<IUnitOfWork>(HttpContext.Current));?因为我仍然会得到一个缓存结果,即使使用了新的HttpContextLifeTimeManager :/ - Team-JoKi
我无法访问controllerContext(我的Unity容器设置是在global.asax中调用的静态类),因此据我所知,HttpContext.Current是我获取HttpContext的唯一访问点? - Team-JoKi
看看老套的继承方式,你仍然可以使用Unity,但需要更多的工作。 - Akash Kava
我找到了我的错误。container.RegisterType<IUserStore<Account>, UserStore<Account>>(new InjectionConstructor(container.Resolve<IUnitOfWork>())); <-- 这里没有使用 IUnitOfwork,因为它只接受 DbContext 作为参数,所以它使用了默认参数,创建了一个新实例。这就是导致所有问题的原因!所以我只需在构造函数中将 IUnitOfWork 强制转换为 DbContext 即可。 - Team-JoKi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接