Open sandboxFocusImprove this doc

LAMA5101: The invalidating method can't provide the instance of the cached method

Severity Error
Reported by Metalama.Patterns.Caching
Message The [InvalidateCache] aspect applied to '<InvalidatingMethod>' cannot invalidate '<CachedMethod>': the 'this' parameter cannot be mapped because '<CachedMethod>' is an instance method, the IgnoreThisParameter option is not enabled for '<CachedMethod>', and '<InvalidatingMethod>' is either static or declared in the type '<InvalidatingMethodDeclaringType>', which is neither '<CachedMethodDeclaringType>' nor derived from '<CachedMethodDeclaringType>'.

Cause

By default, the cache key of an instance method cached with the [Cache] aspect includes the instance on which the method is called, that is, the this parameter. To remove a value from the cache, the [InvalidateCache] aspect must rebuild the same key, so it passes its own this as the instance of the cached method.

This is only possible when the invalidating method is an instance method of the type that declares the cached method, or of a type derived from it. Metalama reports LAMA5101 when all of the following conditions are true:

  • The cached method <CachedMethod> is an instance method.
  • The IgnoreThisParameter option isn't enabled for the cached method.
  • The invalidating method <InvalidatingMethod> is static, or its declaring type <InvalidatingMethodDeclaringType> is neither the declaring type of the cached method, <CachedMethodDeclaringType>, nor derived from it.

In the following example, the PriceUpdater class invalidates a method of the unrelated ProductCatalogue class, whose cache key includes the ProductCatalogue instance.

1using Metalama.Patterns.Caching.Aspects;
2using System.Collections.Generic;
3
4namespace Doc.LAMA5101.Error;
5
6public sealed class ProductCatalogue
7{
8    internal static readonly Dictionary<string, decimal> Prices = new() { ["corn"] = 100 };
9
10    // The cache key of GetPrice includes the ProductCatalogue instance.
11    [Cache]
12    public decimal GetPrice( string productId ) => Prices[productId];
13}
14
15public sealed class PriceUpdater
16{
17    // Error: PriceUpdater doesn't derive from ProductCatalogue, so 'this' can't be mapped.
18    [InvalidateCache( typeof(ProductCatalogue), nameof(ProductCatalogue.GetPrice) )]
    Error LAMA5101: The [InvalidateCache] aspect applied to 'PriceUpdater.UpdatePrice(string, decimal)' cannot invalidate 'ProductCatalogue.GetPrice(string)': the 'this' parameter cannot be mapped because 'ProductCatalogue.GetPrice(string)' is an instance method, the IgnoreThisParameter option is not enabled for 'ProductCatalogue.GetPrice(string)', and 'PriceUpdater.UpdatePrice(string, decimal)' is either static or declared in the type 'PriceUpdater', which is neither 'ProductCatalogue' nor derived from 'ProductCatalogue'.

19    public void UpdatePrice( string productId, decimal price ) => ProductCatalogue.Prices[productId] = price;
20}
21

Resolution

Do one of the following:

  • If the result of the cached method doesn't depend on the instance, for instance because the type is a singleton service, exclude the instance from the cache key by enabling IgnoreThisParameter. You can set it on the [Cache] attribute, as in the following example, with the [CachingConfiguration] attribute on the type or the assembly, or with a fabric.
  • Move the invalidating method to the type that declares the cached method, or to a derived type, and make it an instance method.
  • Invalidate the cache imperatively by calling the Invalidate method of the caching service with the right instance, instead of using the [InvalidateCache] aspect.

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

3

4namespace Doc.LAMA5101.Fixed;

5
6public sealed class ProductCatalogue
7{
8    internal static readonly Dictionary<string, decimal> Prices = new() { ["corn"] = 100 };
9
10    // Fixed: the cache key of GetPrice no longer includes the ProductCatalogue instance.
11    [Cache( IgnoreThisParameter = true )]
12    public decimal GetPrice( string productId ) => Prices[productId];
13}
14























15public sealed class PriceUpdater
16{
17    [InvalidateCache( typeof(ProductCatalogue), nameof(ProductCatalogue.GetPrice) )]
18    public void UpdatePrice( string productId, decimal price ) => ProductCatalogue.Prices[productId] = price;




















19}
20
Transformed Code
1using Metalama.Patterns.Caching;
2using Metalama.Patterns.Caching.Aspects;
3using System;
4using System.Collections.Generic;
5using System.Reflection;
6
7namespace Doc.LAMA5101.Fixed;
8
9public sealed class ProductCatalogue
10{
11    internal static readonly Dictionary<string, decimal> Prices = new() { ["corn"] = 100 };
12
13    // Fixed: the cache key of GetPrice no longer includes the ProductCatalogue instance.
14    [Cache(IgnoreThisParameter = true)]
15    public decimal GetPrice(string productId)
16    {
17        static object? Invoke(object? instance, object?[] args)
18        {
19            return ((ProductCatalogue)instance).GetPrice_Source((string)args[0]);
20        }
21
22        return _cachingService.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetPrice, this, new object[] { productId }, Invoke);
23    }
24
25    private decimal GetPrice_Source(string productId) => Prices[productId];
26
27    private static readonly CachedMethodMetadata _cacheRegistration_GetPrice;
28    private ICachingService _cachingService;
29
30    static ProductCatalogue()
31    {
32        _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 = true, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, false);
33    }
34
35    public ProductCatalogue(ICachingService? cachingService = null)
36    {
37        this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
38    }
39}
40
41public sealed class PriceUpdater
42{
43    [InvalidateCache(typeof(ProductCatalogue), nameof(ProductCatalogue.GetPrice))]
44    public void UpdatePrice(string productId, decimal price)
45    {
46        ProductCatalogue.Prices[productId] = price;
47        object result = null;
48        _cachingService.Invalidate(_methodsInvalidatedBy_UpdatePrice_8158D76B6E0BFEAA70B5CB758CE36A82[0], this, new object[] { productId });
49    }
50
51    private ICachingService _cachingService;
52    private static MethodInfo[] _methodsInvalidatedBy_UpdatePrice_8158D76B6E0BFEAA70B5CB758CE36A82;
53
54    static PriceUpdater()
55    {
56        _methodsInvalidatedBy_UpdatePrice_8158D76B6E0BFEAA70B5CB758CE36A82 = new MethodInfo[] {
57            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.")
58        };
59    }
60
61    public PriceUpdater(ICachingService? cachingService = null)
62    {
63        this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
64    }
65}
66

See also