You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework缓存异常:登录时返回旧用户数据,疑与DI相关

解决Entity Framework缓存导致登录返回旧用户数据的问题

你遇到的核心问题是**EF一级缓存(DbContext级)**在作祟,再加上Unity DI的生命周期配置不当,尤其是DbContext和OAuth Provider的依赖注入方式有问题,导致修改用户数据后,登录时还是拿到旧数据。下面一步步给你解决:

1. 先搞定DbContext的生命周期(最关键)

EF的DbContext默认会缓存它查询到的所有实体,如果你把DbContext注册成了Singleton或者生命周期太长的模式,那它会一直复用旧数据。Web API里最合适的DbContext生命周期是每个请求一个实例,也就是用Unity的PerRequestLifetimeManager。

首先在你的Register方法里添加DbContext的注册(假设你的DbContext叫AppDbContext):

// 替换成你的实际DbContext类名
container.RegisterType<AppDbContext>(new PerRequestLifetimeManager());

同时,把所有依赖DbContext的Repo和服务也改成PerRequest,避免跨请求复用:

container.RegisterType<IRoleRepo, RoleRepo>(new PerRequestLifetimeManager());
container.RegisterType<IUserRepo, UserRepo>(new PerRequestLifetimeManager());
container.RegisterType<IMembershipService, MembershipService>(new PerRequestLifetimeManager());
container.RegisterType<IEncryptionService, EncryptionService>(new PerRequestLifetimeManager());

2. 修复OAuth Provider的依赖注入坑

你现在在ConfigureAuth里直接把IMembershipService注入到ApplicationOAuthProvider的构造函数里,但OAuthAuthorizationServerOptions.Provider是个单例(启动时创建后一直复用),这就导致MembershipService和它依赖的DbContext也变成了单例,永远用第一次创建的实例,自然一直返回旧数据。

解决方法是不在构造函数注入,而是在每次请求时从当前Owin上下文获取服务:

修改ApplicationOAuthProvider

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider {
    private readonly string _publicClientId;

    // 只保留publicClientId参数,去掉IMembershipService注入
    public ApplicationOAuthProvider(string publicClientId) {
        _publicClientId = publicClientId ?? throw new ArgumentNullException(nameof(publicClientId));
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) {
        // 从当前请求的Owin上下文获取MembershipService
        var membershipService = context.Request.GetOwinContext().Get<IMembershipService>();
        if (membershipService == null) {
            context.SetError("server_error", "无法加载用户服务");
            return;
        }

        User user = membershipService.ValidateUser(context.UserName, context.Password);
        if (user == null) {
            context.SetError("invalid_grant", "Korisničko ime ili lozinka nisu ispravni.");
            return;
        }

        // 下面的身份凭证代码保持不变...
        ClaimsIdentity oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        foreach (var role in user.roles) {
            oAuthIdentity.AddClaim(new Claim(ClaimTypes.Role, role.role_name));
        }
        oAuthIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.username));
        ClaimsIdentity cookiesIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
        AuthenticationProperties properties = CreateProperties(context.UserName, Newtonsoft.Json.JsonConvert.SerializeObject(user.roles.Select(x => x.role_name)), user.Location.Naziv);
        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        ticket.Properties.AllowRefresh = true;
        ticket.Properties.ExpiresUtc = DateTimeOffset.Now.AddMinutes(30);
        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(cookiesIdentity);
    }

    // 其他方法不变...
}

修改Startup里的ConfigureAuth

把Provider的实例化改成只传PublicClientId:

OAuthOptions = new OAuthAuthorizationServerOptions {
    TokenEndpointPath = new PathString("/api/users/token"),
    // 这里只传PublicClientId,不再注入IMembershipService
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AuthorizeEndpointPath = new PathString("/api/users/login"),
    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
    AllowInsecureHttp = true
};

还要确保Unity能在Owin上下文里解析服务,在Startup的Configuration方法里添加Unity中间件的配置:

public void Configuration(IAppBuilder app) {
    // 先添加这行,让Owin使用Unity解析服务
    app.UseUnityMiddleware(IoC);
    // 然后再配置Auth
    ConfigureAuth(app);
    // 其他配置...
}

3. 登录时强制获取最新数据(可选但保险)

如果上面的配置还不放心,可以在查询用户时强制绕过EF缓存,用AsNoTracking()让EF不缓存查询结果,每次都去数据库取最新数据:

修改EntityBaseRepo的GetSingle方法

添加一个参数控制是否禁用跟踪:

public T GetSingle(Expression<Func<T, bool>> predicate, bool asNoTracking = false, params Expression<Func<T, object>>[] includeProperties) {
    IQueryable<T> query = _context.Set<T>();
    // 如果需要禁用跟踪,添加AsNoTracking
    if (asNoTracking) {
        query = query.AsNoTracking();
    }
    foreach (var includeProperty in includeProperties) {
        query = query.Include(includeProperty);
    }
    return query.Where(predicate).SingleOrDefault();
}

在MembershipService的ValidateUser里调用时启用asNoTracking

public User ValidateUser(string username, string password) {
    // 第二个参数传true,禁用跟踪,强制取最新数据
    var user = _userRepo.GetSingle(x => x.username == username, true, u => u.Location);
    // 密码验证逻辑...
    return user;
}

这样一套操作下来,就能彻底解决登录时返回旧用户数据的问题了。

内容的提问来源于stack exchange,提问作者Milorad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:43:02