0

I have an issue with Entity framework caching data. Namely I have an administration panel in which u can change user roles and other user data, problem is that my services returns user with old data. To get even trickier it is only happening upon login. I suspect that it has to do something with instantiating DBContext and Unity DI.

Here are my classes: Startup.cs

public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the application for OAuth based flow

        PublicClientId = "self";
        var container = GlobalConfiguration.Configuration.DependencyResolver;

        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/api/users/token"),
            Provider = new ApplicationOAuthProvider(PublicClientId,(IMembershipService)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IMembershipService))),
            AuthorizeEndpointPath = new PathString("/api/users/login"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            // In production mode set AllowInsecureHttp = false
            AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);
    }
public static void Register(HttpConfiguration config)
    {

        // security
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
        // routes
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        var container = new UnityContainer();

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


        IoC = container;

        // SignalR dependency injection
        config.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        var unityHubActivator = new UnityHubActivator(container);
        GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => unityHubActivator);

        // For more information, refer to: http://www.asp.net/web-api
        config.EnableSystemDiagnosticsTracing();
        config.EnsureInitialized();
    }

ApplicationOAuthProvider.cs

public ApplicationOAuthProvider(string publicClientId,IMembershipService membershipService)
    {
        if (publicClientId == null)
        {
            throw new ArgumentNullException("publicClientId");
        }
        _publicClientId = publicClientId;
        this.membershipService = membershipService;
    }
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        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);
    }

UserRepository.cs : EntityBaseRepo

public User GetSingleByUsername(string username)
    {
        return this.GetSingle(x => x.username == username, u => u.Location);
    }

EntityBaseRepo.cs

public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> query = _context.Set<T>();
        foreach (var includeProperty in includeProperties)
        {
            query = query.Include(includeProperty);
        }

        return query.Where(predicate).SingleOrDefault();
    }

UserController.cs

public UserController(IMembershipService membershipService, IUserRepo userRepo,
            ILocationRepo locationRepo, IRoleRepo roleRepo) {
            this.membershipService = membershipService;
            this.userRepo = userRepo;
            this.roleRepo = roleRepo;
            this.locationRepo = locationRepo;
        }

        public IHttpActionResult GetByUsername(string username, string password)
        {
            User user = membershipService.ValidateUser(username, password);
            if (user == null) {
                return BadRequest("Korisnik ne postoji.");
            }
            UserViewAdmin userView = AutoMapper.Mapper.Map<UserViewAdmin>(user);
            return Ok(user);
        }

Note than when I change roles or some other property of user and go through login process I get old data, but when I send request to UserController I get the new data even though I am using the same service to retrieve it. Can someone explain why is this happening and propose some solution? Thanks

EDIT TagModel.cs (DBContext)

public TagModel() : base("name=TagModel")
    {
        Database.Log = x => Debug.WriteLine(x);
    }

    public virtual DbSet<Artikli> artiklis { get; set; }
    public virtual DbSet<Permission> permissions { get; set; }
    public virtual DbSet<Role> roles { get; set; }
    public virtual DbSet<Tag> tags { get; set; }
    public virtual DbSet<User> Users { get; set; }
    public virtual DbSet<Location> Locations { get; set; }
    public virtual DbSet<Reader> Readers { get; set; }
    public virtual DbSet<Order> Orders { get; set; }
    public virtual DbSet<Antenna> Antennas { get; set; }
    public virtual DbSet<Printer> Printers { get; set; }
    public virtual DbSet<TagLocation> TagLocations { get; set; }
    public virtual DbSet<LocationArticle> LocationArticles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Artikli>()
            .HasKey(e => e.Id)
            .Property(e => e.klasa_artikla)
            .IsFixedLength();

        modelBuilder.Entity<Reader>()
            .HasKey(reader => reader.Id)
            .HasMany(reader => reader.Antennas)
            .WithRequired(antenna => antenna.Reader)
            .HasForeignKey(antenna => antenna.ReaderFK)
            .WillCascadeOnDelete(true);
        modelBuilder.Entity<Antenna>()
            .HasKey(pk => new {
                pk.Id,
                pk.ReaderFK
            });
        modelBuilder.Entity<Location>()
            .HasKey(e => e.Id)
            .HasMany(e => e.Readers)
            .WithRequired(reader => reader.Location)
            .HasForeignKey(reader => reader.LocationFK);

        modelBuilder.Entity<Location>()
            .HasKey(e => e.Id)
            .HasMany(e => e.Printers)
            .WithRequired(printer => printer.Location)
            .HasForeignKey(printer => printer.LocationFK);
        /*modelBuilder.Entity<Location>()
            .HasKey(e => e.Id)
            .HasMany(e => e.Tags)
            .WithMany(e => e.Locations)
            .Map(m => m.ToTable("storage_tags", "public"));
        */
        modelBuilder.Entity<Tag>()
            .HasKey(e=> e.Id);

        modelBuilder.Entity<LocationArticle>()
            .HasKey(e => e.Id);
        modelBuilder.Entity<LocationArticle>()
            .HasRequired(e => e.Location)
            .WithMany(t => t.LocationArticles)
            .HasForeignKey(e => e.LocationFK);
        modelBuilder.Entity<LocationArticle>()
            .HasRequired(e => e.Article)
            .WithMany(t => t.LocationArticles)
            .HasForeignKey(e => e.ArticleFK);

        modelBuilder.Entity<TagLocation>()
            .HasKey(e => e.Id);
        modelBuilder.Entity<TagLocation>()
            .HasRequired(e => e.Tag)
            .WithMany(t => t.Locations)
            .HasForeignKey(e => e.TagFK);
        modelBuilder.Entity<TagLocation>()
            .HasRequired(e => e.LocationArticle)
            .WithMany(t => t.TagLocations)
            .HasForeignKey(e => e.LocationArticleFK);


        modelBuilder.Entity<Printer>()
            .HasKey(e => e.Id);


        modelBuilder.Entity<Storage>().Map(m =>
        {
            m.ToTable("storages", "public");
        });

        modelBuilder.Entity<Shop>().Map(m =>
        {
            m.ToTable("shops", "public");
        });

        modelBuilder.Entity<Order>()
            .HasKey(e => e.Id)
            .HasRequired(e => e.Source)
            .WithMany(location => location.SentOrders)
            .HasForeignKey(e => e.SourceFK);

        modelBuilder.Entity<Order>()
            .HasKey(e => e.Id)
            .HasRequired(e => e.Destination)
            .WithMany(location => location.RecievedOrders)
            .HasForeignKey(e => e.DestinationFK);

        modelBuilder.Entity<OrderTag>()
            .HasKey(pk => new { pk.OrderFK, pk.TagFK, pk.ArticleFK })
            .HasRequired(ot => ot.OrderArticle)
            .WithMany(oa => oa.OrderTags)
            .HasForeignKey(fk => new { fk.OrderFK, fk.ArticleFK})
            .WillCascadeOnDelete(true);

        modelBuilder.Entity<OrderTag>()
            .HasKey(pk => new { pk.OrderFK, pk.TagFK, pk.ArticleFK })
            .HasRequired(ot => ot.Tag)
            .WithMany(t => t.Orders)
            .HasForeignKey(ot => ot.TagFK);

        modelBuilder.Entity<OrderArticle>()
            .HasKey(pk => new { pk.OrderFK, pk.ArticleFK })
            .HasRequired(oa => oa.Article)
            .WithMany(a => a.OrderArticles)
            .HasForeignKey(oa => oa.ArticleFK);

        modelBuilder.Entity<OrderArticle>()
            .HasKey(pk => new { pk.OrderFK, pk.ArticleFK })
            .HasRequired(oa => oa.Order)
            .WithMany(o => o.Articles)
            .HasForeignKey(oa => oa.OrderFK);

        modelBuilder.Entity<Artikli>()
            .HasKey(e => e.Id)
            .HasMany(e => e.LocationArticles)
            .WithRequired(e => e.Article)
            .HasForeignKey(e => e.ArticleFK)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Permission>()
            .HasKey(e => e.Id)
            .HasMany(e => e.roles)
            .WithMany(e => e.Permissions)
            .Map(m => m.ToTable("role_permission", "public"));

        modelBuilder.Entity<User>()
            .HasKey(e => e.Id)
            .HasRequired(e => e.Location)
            .WithMany(location => location.Users)
            .HasForeignKey(e => e.LocationFK);

        modelBuilder.Entity<Role>()
            .HasKey(e => e.Id)
            .HasMany(e => e.Users)
            .WithMany(e => e.roles)
            .Map(m =>
            {
                m.MapLeftKey("role_role_id");
                m.MapRightKey("user_user_id");
                m.ToTable("user_role", "public");
            });
    }
Milorad
  • 145
  • 13
  • can you please update the question with DBContext class/implementation? – Mitesh Prajapati May 22 '18 at 12:45
  • https://stackoverflow.com/questions/3653009/entity-framework-and-connection-pooling/3653392#3653392 - please take a look at this. this might help you. – Mitesh Prajapati May 22 '18 at 12:47
  • you can use AsNoTracking() : Returns a new query where the entities returned will not be cached in the DbContext or ObjectContext. – Hany Habib May 22 '18 at 12:47
  • @MiteshPrajapati Edit posted. I've already read that post, but currently I am trying to find a solution without having to change design pattern. – Milorad May 22 '18 at 13:06
  • @HanyHabib That would detach my object from context and as I stated in question it works in every other case except login. – Milorad May 22 '18 at 13:06
  • I too suspect that it has to do something with instantiating DBContext and Unity DI. But you didn't post anything relevant to that. – David Browne - Microsoft May 22 '18 at 13:16
  • @DavidBrowne-Microsoft I posted how I configured unity container and how I resolve services to oauth, what else should I post? – Milorad May 22 '18 at 13:23
  • I see it now. HierarchicalLifetimeManager is probably the wrong one. You should not share DbContext instances across requests. So either request-based lifetime or transient lifetime. – David Browne - Microsoft May 22 '18 at 13:35
  • @DavidBrowne-Microsoft there is no request-based lifetime for WebAPI, only for WebMVC as I know. Correct me if I am wrong. If I use transient than I could not use more than one service in request because it will have different context for example I could not get roles of user with rolesService and add those roles to the user from userService. – Milorad May 22 '18 at 13:46
  • My recommendation would be to break the pattern for your authentication's user repository for the Auth service to consume so that you can utilize .AsNoTracking() or GetDatabaseValues() to bypass any caching. IMO Consistency for consistency's sake is an anti-pattern. When something doesn't fit the "pattern" you either bypass the pattern or spend/waste excess time trying to adapt and perfect said pattern. (Personally, I'll take KISS over expression-based predicates & includes any day of the week. :) ) – Steve Py May 22 '18 at 21:56

0 Answers0