Open sandboxFocusImprove this doc

LAMA5102: The invalidating method lacks a parameter of the cached method

Severity Error
Reported by Metalama.Patterns.Caching
Message The [InvalidateCache] aspect applied to '<InvalidatingMethod>' cannot invalidate '<CachedMethod>': the invalidating method does not contain a parameter named '<ParameterName>'. Make sure '<InvalidatingMethod>' contains a parameter named '<ParameterName>' or add the [NotCacheKey] attribute to the '<ParameterName>' parameter in '<CachedMethod>'.

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, regardless of their order. Every parameter of the cached method that's part of the cache key must have a parameter of the same name in the invalidating method.

Metalama reports LAMA5102 when a parameter <ParameterName> of the cached method <CachedMethod> has no parameter of the same name in the invalidating method <InvalidatingMethod>, and the parameter isn't excluded from the cache key with the [NotCacheKey] attribute.

In the following example, GetPrice has a productId parameter, but the corresponding parameter of UpdatePrice is named product.

1using Metalama.Patterns.Caching.Aspects;
2using System.Collections.Generic;
3
4namespace Doc.LAMA5102.Error;
5
6public sealed class ProductCatalogue
7{
8    private readonly Dictionary<string, decimal> _prices = new() { ["corn"] = 100 };
9
10    [Cache]
11    public decimal GetPrice( string productId ) => this._prices[productId];
12
13    // Error: UpdatePrice has no parameter named 'productId'.
14    [InvalidateCache( nameof(GetPrice) )]
    Error LAMA5102: The [InvalidateCache] aspect applied to 'ProductCatalogue.UpdatePrice(string, decimal)' cannot invalidate 'ProductCatalogue.GetPrice(string)': the invalidating method does not contain a parameter named 'productId'. Make sure 'ProductCatalogue.UpdatePrice(string, decimal)' contains a parameter named 'productId' or add the [NotCacheKey] attribute to the 'productId' parameter in 'ProductCatalogue.GetPrice(string)'.

15    public void UpdatePrice( string product, decimal price ) => this._prices[product] = price;
16}
17

Resolution

Do one of the following:

  • Add the missing parameter to the invalidating method, or rename the existing one so that it has the same name as in the cached method, 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. This changes the caching behavior: calls that differ only by this parameter share the same cache entry.
  • If the methods are too different to be matched, invalidate the cache imperatively by calling the Invalidate method of the caching service.

Source Code
1using Metalama.Patterns.Caching.Aspects;
2using System.Collections.Generic;

3

4namespace Doc.LAMA5102.Fixed;

5
6public sealed class ProductCatalogue
7{
8    private readonly Dictionary<string, decimal> _prices = new() { ["corn"] = 100 };
9
10    [Cache]
11    public decimal GetPrice( string productId ) => this._prices[productId];
12
13    // Fixed: the parameter has the same name as in GetPrice.










14    [InvalidateCache( nameof(GetPrice) )]
15    public void UpdatePrice( string productId, decimal price ) => this._prices[productId] = price;
16}
17
Transformed Code
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using System;
4using System.Collections.Generic;
5using System.Reflection;
6
7namespace Doc.LAMA5102.Fixed;
8
9public sealed class ProductCatalogue
10{
11    private readonly Dictionary<string, decimal> _prices = new() { ["corn"] = 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 name 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_356CCE52C3A0531D2B3AADAFC8C24F36[0], this, new object[] { productId });
33    }
34
35    private static readonly CachedMethodMetadata _cacheRegistration_GetPrice;
36    private ICachingService _cachingService;
37    private static MethodInfo[] _methodsInvalidatedBy_UpdatePrice_356CCE52C3A0531D2B3AADAFC8C24F36;
38
39    static ProductCatalogue()
40    {
41        _methodsInvalidatedBy_UpdatePrice_356CCE52C3A0531D2B3AADAFC8C24F36 = 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

See also