Кэширование Entity Framework в удостоверении aspnet

Я создаю проект в EF6 и aspnet Identity.

Я столкнулся со следующей проблемой:

если я позвоню

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, и у меня нет контроля над этими вещами, что я могу сделать?

Спасибо!

РЕДАКТИРОВАТЬ: добавлена ​​информация 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;
}

Мой 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;
    }
}

ОБНОВЛЕНИЕ:

Я обнаружил еще одну странную вещь. Когда я устанавливаю поле даты последнего входа в систему, это изменение подхватывается, но когда я устанавливаю свое поле isConfirmed, оно не подхватывается.. (изменение БД фактически перезаписывается кэшированными данными!

Таким образом, это подтверждает, что данные, введенные с помощью кода, сохраняются, но ручные изменения в БД игнорируются.

ОБНОВЛЕНИЕ 2 На случай, если у кого-то возникнет эта проблема: проблема была не в удостоверении aspnet, а в EF.

Что я сделал, так это реализовал свой собственный пользовательский магазин и вручную получил доступ к EF и использовал .AsNoTracking(), чтобы избежать кэширования.


person Team-JoKi    schedule 10.01.2014    source источник
comment
Как выглядит ваш FindByNameAsync?   -  person Akash Kava    schedule 10.01.2014
comment
@AkashKava Это функция идентификатора aspnet (=> UserManager‹IUser›)   -  person Team-JoKi    schedule 10.01.2014
comment
Вы используете один экземпляр для каждого запроса или вы используете один экземпляр DbContext для всего приложения? Метод Find DbSet кэширует значение в локальном объекте, если вы не очистите его явно. Это проблема с Entity Framework.   -  person Akash Kava    schedule 10.01.2014
comment
Я использую один dbContext на время жизни одного запроса (MVC)   -  person Team-JoKi    schedule 10.01.2014
comment
Можете ли вы опубликовать код этого? Вы должны знать, что HttpContext и некоторые кэшированные элементы плохо работают с асинхронным кодом, поскольку несколько асинхронных методов выполняются в одном потоке.   -  person Akash Kava    schedule 10.01.2014


Ответы (1)


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 для каждого запроса, но это просто пустая трата цикла ЦП. На мой взгляд, использование Unity в MVC для более простых задач — это просто программирование. Если что-то, что легко сделать с абстрактными классами. В асинхронном программировании много нового, Unity для этого не создавался.

person Akash Kava    schedule 10.01.2014
comment
Итак, мой контейнер Unity должен выглядеть так: container.RegisterType‹IUnitOfWork, UserManagementContext›(new HttpContextLifetimeManager‹IUnitOfWork›(HttpContext.Current)); ? Потому что я все еще получаю кешированный результат с этим новым HttpContextLifeTimeManager :/ - person Team-JoKi; 10.01.2014
comment
У меня нет доступа к controllerContext (настройка контейнера My Unity — это статический класс, который вызывается в моем global.asax), поэтому, насколько я знаю, HttpContext.Current — моя единственная точка доступа для получения HttpContext? - person Team-JoKi; 10.01.2014
comment
См. простой старый способ наследования, вы все еще можете использовать Unity, но для этого потребуется больше работы. - person Akash Kava; 10.01.2014
comment
Я нашел свою ошибку. container.RegisterType‹IUserStore‹Account›, UserStore‹Account››(new InjectionConstructor(container.Resolve‹IUnitOfWork›())); ‹-- это не использует IUnitOfwork, потому что он принимает только DbContext в качестве параметра, поэтому он использовал параметр по умолчанию, который создает новый экземпляр. Вот из-за этого все мои проблемы! поэтому я просто привожу IUnitOfWork к DbContext в конструкторе - person Team-JoKi; 21.01.2014