| Severity | Error |
| Reported by | Metalama.Patterns.Caching |
| Message | The [InvalidateCache] aspect applied to '<InvalidatingMethod>' cannot invalidate '<CachedMethod>': the type of the '<ParameterName>' parameter of the cached method is not compatible with the type of the '<ParameterName>' parameter in the invalidating method. |
Cause
To remove a value from the cache, the [InvalidateCache] aspect rebuilds the cache key of the invalidated method from the arguments of the invalidating method. It matches the parameters of both methods by name, then passes the argument of the invalidating method in place of the parameter of the cached method.
Metalama reports LAMA5103 when the type of the parameter <ParameterName> of the invalidating method <InvalidatingMethod> can't be converted to the type of the parameter of the same name in the cached method <CachedMethod>. The check follows the rules of the C# is operator: a parameter of the same type, of a derived type, or of a type that implements the expected interface is compatible. A parameter of a base type or of an unrelated type isn't, and neither is a type that requires a numeric or user-defined conversion, such as int to long.
In the following example, GetPrice takes a string product identifier, but UpdatePrice takes an int.
1using Metalama.Patterns.Caching.Aspects;
2using System.Collections.Generic;
3
4namespace Doc.LAMA5103.Error;
5
6public sealed class ProductCatalogue
7{
8 private readonly Dictionary<string, decimal> _prices = new() { ["42"] = 100 };
9
10 [Cache]
11 public decimal GetPrice( string productId ) => this._prices[productId];
12
13 // Error: an int can't be converted to the string parameter of GetPrice.
14 [InvalidateCache( nameof(GetPrice) )]
Error LAMA5103: The [InvalidateCache] aspect applied to 'ProductCatalogue.UpdatePrice(int, decimal)' cannot invalidate 'ProductCatalogue.GetPrice(string)': the type of the 'productId' parameter of the cached method is not compatible with the type of the 'productId' parameter in the invalidating method.
15 public void UpdatePrice( int productId, decimal price ) => this._prices[productId.ToString()] = price;
16}
17
Resolution
Do one of the following:
- Change the type of the parameter of the invalidating method so that it's the same as the type of the parameter of the cached method, or derived from it, as in the following example.
- If the parameter of the cached method shouldn't be part of the cache key, add the [NotCacheKey] attribute to it.
- If the methods are too different to be matched, invalidate the cache imperatively by calling the Invalidate method of the caching service, converting the arguments yourself.
1using Metalama.Patterns.Caching.Aspects;
2using System.Collections.Generic;
3
4namespace Doc.LAMA5103.Fixed;
5
6public sealed class ProductCatalogue
7{
8 private readonly Dictionary<string, decimal> _prices = new() { ["42"] = 100 };
9
10 [Cache]
11 public decimal GetPrice( string productId ) => this._prices[productId];
12
13 // Fixed: the parameter has the same type as in GetPrice.
14 [InvalidateCache( nameof(GetPrice) )]
15 public void UpdatePrice( string productId, decimal price ) => this._prices[productId] = price;
16}
17
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using System;
4using System.Collections.Generic;
5using System.Reflection;
6
7namespace Doc.LAMA5103.Fixed;
8
9public sealed class ProductCatalogue
10{
11 private readonly Dictionary<string, decimal> _prices = new() { ["42"] = 100 };
12
13 [Cache]
14 public decimal GetPrice(string productId)
15 {
16 static object? Invoke(object? instance, object?[] args)
17 {
18 return ((ProductCatalogue)instance).GetPrice_Source((string)args[0]);
19 }
20
21 return _cachingService.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetPrice, this, new object[] { productId }, Invoke);
22 }
23
24 private decimal GetPrice_Source(string productId) => this._prices[productId];
25
26 // Fixed: the parameter has the same type as in GetPrice.
27 [InvalidateCache(nameof(GetPrice))]
28 public void UpdatePrice(string productId, decimal price)
29 {
30 this._prices[productId] = price;
31 object result = null;
32 _cachingService.Invalidate(_methodsInvalidatedBy_UpdatePrice_D93B4776EBB43C61792296CDF179173E[0], this, new object[] { productId });
33 }
34
35 private static readonly CachedMethodMetadata _cacheRegistration_GetPrice;
36 private ICachingService _cachingService;
37 private static MethodInfo[] _methodsInvalidatedBy_UpdatePrice_D93B4776EBB43C61792296CDF179173E;
38
39 static ProductCatalogue()
40 {
41 _methodsInvalidatedBy_UpdatePrice_D93B4776EBB43C61792296CDF179173E = new MethodInfo[] {
42 typeof(ProductCatalogue).GetMethod("GetPrice", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null) ?? throw new MissingMethodException("The method 'ProductCatalogue.GetPrice(string)' could not be found using reflection.")
43 };
44 _cacheRegistration_GetPrice = CachedMethodMetadata.Register(typeof(ProductCatalogue).GetMethod("GetPrice", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null) ?? throw new MissingMethodException("The method 'ProductCatalogue.GetPrice(string)' could not be found using reflection."), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, false);
45 }
46
47 public ProductCatalogue(ICachingService? cachingService = null)
48 {
49 this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
50 }
51}
52