简体   繁体   中英

ASP.NET Core - Update 1 Element of a cached List<Element> in IMemoryCache using EF Core

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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM