ASP.NET Core中MemoryCache获取对象副本的实现方案求助
Got it, I totally get why this is frustrating—when you pull data from MemoryCache, you're getting a direct reference to the cached object, so any changes you make (like toggling that Active status) end up messing with the original cached materialized view data. Let's walk through the best solutions tailored to your business scenario, where you need to keep the cached source intact while modifying copies for API requests.
1. Return a Deep Copy Every Time You Fetch from Cache
Since your cached data is a collection of reference-type objects (your product parameter entities), a shallow copy (like calling ToList() or ToArray()) won't cut it—it only copies the collection's reference, not the individual objects inside. You need a deep copy to fully isolate the cached original from the working copy you modify.
Implementation Options:
Option A: Serialization/Deserialization (Quick & Universal)
This works for almost any object type without modifying your entity classes:
// Fetch the original cached data var cachedMvData = _memoryCache.Get<List<ProductParameter>>("MaterializedView_Products"); // Create a deep copy using System.Text.Json var workingCopy = JsonSerializer.Deserialize<List<ProductParameter>>( JsonSerializer.Serialize(cachedMvData) ); // Now you can safely filter and update the Active status without touching the cached original var processedData = workingCopy .Where(p => p.link == "/product_group1/product_group_2") .Select(p => { // Apply your filter rules here bool meetsFilter = p.FieldName switch { "money" => p.Value == "1200", "rating" => double.Parse(p.Value) >= 1.5 && double.Parse(p.Value) <= 3, _ => true }; return p with { Active = meetsFilter }; }) .ToList();
If you prefer Newtonsoft.Json, the logic is nearly identical—swap out JsonSerializer with JsonConvert.
Option B: Custom Clone Method (Better Performance for Complex Objects)
If serialization feels too slow for your data size, add a clone method to your entity class:
public class ProductParameter { public int Id { get; set; } public string Name { get; set; } public string Value { get; set; } public string Type { get; set; } public string FieldName { get; set; } public bool Active { get; set; } = true; // Default to active as per your scenario // Deep clone all properties (recurse if you have nested reference types) public ProductParameter Clone() { return new ProductParameter { Id = this.Id, Name = this.Name, Value = this.Value, Type = this.Type, FieldName = this.FieldName, Active = this.Active // Preserve original active state from cache }; } } // Use it to create a working copy var cachedMvData = _memoryCache.Get<List<ProductParameter>>("MaterializedView_Products"); var workingCopy = cachedMvData.Select(p => p.Clone()).ToList();
2. Use Immutable Objects to Avoid Accidental Modification
If your workflow allows, design your entity as an immutable type (like a C# record). With immutability, you can't modify the original cached object—any changes create a new instance instead:
// Define your parameter as an immutable record public record ProductParameter( int Id, string Name, string Value, string Type, string FieldName, bool Active = true ); // When processing, create new instances instead of modifying the cached ones var cachedMvData = _memoryCache.Get<List<ProductParameter>>("MaterializedView_Products"); var processedData = cachedMvData .Where(p => p.link == "/product_group1/product_group_2") .Select(p => { bool meetsFilter = /* your filter logic here */; return p with { Active = meetsFilter }; // Creates a new record instance }) .ToList();
This eliminates the need for copying entirely, as the cached data remains untouched by design.
3. Wrap Cache Access in a Service (Clean & Reusable)
To avoid repeating copy logic across your codebase, create a dedicated service that handles fetching the cached materialized view and returning a copy automatically:
public class MaterializedViewCacheService { private readonly IMemoryCache _memoryCache; private readonly YourDbContext _dbContext; public MaterializedViewCacheService(IMemoryCache memoryCache, YourDbContext dbContext) { _memoryCache = memoryCache; _dbContext = dbContext; } public async Task<List<ProductParameter>> GetFreshCopyAsync() { const string cacheKey = "MaterializedView_Products"; // Check cache first if (_memoryCache.TryGetValue(cacheKey, out List<ProductParameter> cachedData)) { // Return a deep copy of the cached data return JsonSerializer.Deserialize<List<ProductParameter>>( JsonSerializer.Serialize(cachedData) ); } // Load from database if cache is empty var dbData = await _dbContext.ProductParameters .FromSqlRaw("SELECT * FROM Your_Materialized_View") .ToListAsync(); // Cache the original, unmodified data _memoryCache.Set(cacheKey, dbData, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) // Adjust expiry as needed }); // Return a copy of the fresh database data return JsonSerializer.Deserialize<List<ProductParameter>>( JsonSerializer.Serialize(dbData) ); } }
Now any part of your app can call GetFreshCopyAsync() and get a clean, unmodified copy of the materialized view every time.
Do You Need a Different Cache Library?
Nope—Microsoft.Extensions.Caching.Memory is already the official, recommended in-memory cache for ASP.NET Core. The issue isn't the cache itself, but how you handle object references. The solutions above will fix your problem without switching libraries.
内容的提问来源于stack exchange,提问作者Jan Sršeň

