0

I'm building a webshop using ASP.Net Core (Razor Pages) with EF Core. To speed up a lot of calculation, I cache all the products (~100.000). The information is coming from our db.

Models

class ProductInformation{
    // some information about how to display a product
    public string productnumber {get; set;}
    public Product product {get; set;}
}

class Product{
    public string productnumber {get; set;}
    public decimal price {get; set;}
    public Category category {get; set}
    // and a lot more...
}

In my DBContext I define that every PoductInformation has exact one Product.

The caching method

private readonly IMemoryCache _cache; // is set in the constructor
private readonly DBContext _dbContext; // is set in the constructor
public List<ProductInformation> GetProductInformationList(){
    List<ProductInformation> = _cache.GetOrCreate("ProductInformations", entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24);
        List<ProductInformation> list =_dbContext.ProductInformation.ToList();
        // do a lot of calculations on the list-elements
        return list;
    });
}

The whole caching method takes 100-120 seconds. Now another system updates my price in the DB for one product and of course I want to show the correct price of that product on my webshop. Let's assume I want to check the price every 15 minutes.

Solutions - tried but not satisfying

Method 1

I can set caching of the whole list to 15 minutes, that will work, but it's not what I want. Refreshing the whole cache is slow and not necessary. 99.9% of the data has not changed.

Method 2

Inside my model ProductInformation I make a method that updates the Product:

class ProductInformation{
    // some information about how to display a product
    public string productnumber {get; set;}
    public Product product {get; set;}

    pubic DateTime ProductTimeStamp {get; set;}
    public UpdateProduct(){
        // If ProductTimeStamp is more than 15 minutes in the past
        // get Product from the DB
        // and update the timestamp
    }
}

Everywhere I display a ProductInformation, I call UpdateProduct(). Then I only re-check the 'old' prices from products that I display. This is much more efficiënt than recalculate the whole cache. But now I need a DB connection inside my ProductInformation (that is cached). I can't get this to work.

Method 3

Since the problem in method 2 is that I don't have a DB connection, I can take the UpdateProduct() method outside the model and put it in my repository where I have a DB connection. Everywhere I display a ProdctInformation, I need to call something like _proudctRepository.UpdateProduct(ref ProductInformation);. This method looks like:

public void UpdateProduct(ref ProductInformation pi){
    pi.Product = _dbContext.Product.Where(p => p.productnumber == pi.productnumber);
   // Of course I also need to do the calculations from the caching-method in GetProductInformationList()
}

But this feels not right. The Entity Framework has organized for me the connection between ProductInformation and Product, is it then possible to redefine Product this way? I think its not the way to go.

Question

I think a lot of people use IMemoryCaching for equal situations (where you want to update just a single element in a cached List or just some elements (price / stock / ...) of a cached list element). How can we handle this?

CribAd
  • 452
  • 6
  • 19
  • If you know when an object changes you just have to call a method afterwards. MyCacheManager.UpdateProduct(DbContext myContext, ProductInformation pi) { ... update cache ...} – Charles Nov 21 '19 at 08:43
  • True. But I don't know when a Product has changed. And then the question of method 3: is it possible to update a cached item this way? Does it affect my 'Entity Framework relation''? – CribAd Nov 21 '19 at 09:11
  • if you dont know when an item has changed, then a cache is probably the wrong solution. Or you have to write some query, set some [LastUpdatedOn] flags on your db records. You Should set a different key for every product tho. so if you have 1m products, you should have 1m cache keys, and then you can also easily delete / update the cache – Charles Nov 21 '19 at 09:44

1 Answers1

1

1) Add LastChanged (datetime[offset]) column to your Product database and ask "another system" update it too when it updates your price. With this, you can easily save Max(LastChanged) in your cache and query only for changed Products, reducing time and size of updates, and you can do it more frequently.

2) Add AsNoTracking when putting data into cache. You will not update them back to DB (I guess), so no-tracking will speed up everything a little. Also, this will give you no-worry about ProductInformation - Product EF relations, because EF will not be tracking them anyway and pi.Product will be a usual object-holding property without any hidden magic.

3) It's not good to check for price updates on every page render. Updates should run in other thread / background. Setup some background task that will check for updated prices and reload updated objects into cache - with (1) you can run updates every 5 minutes or less. Check here DbContext for background tasks via Dependency Injection for obtaining DbContext.

Dmitry
  • 16,110
  • 4
  • 61
  • 73
  • Scheduled task works and getting the Products that needs to be updated goed right. But then it looks like I can't update the cached List with some kind of: `GetProductInformationList().FirstOrDefault(pi => pi.ProductNumber == newProductInformation.ProductNumber).Product = newProductInformation.Product ;`. What is the correct way to do this? Do I need to create a cache-entry for every 'ProductInformation'? – CribAd Nov 21 '19 at 14:40
  • Excuse me, my testing-procedure wasn't right: the above method works! Thanks a lot! – CribAd Nov 21 '19 at 15:33