Open sandboxFocusImprove this doc

LAMA5112: The [Cache] aspect can be applied at most once to a method

Severity Error
Reported by Metalama.Patterns.Caching
Message The [Cache] aspect can be applied at most once to a method.

Cause

A method can be cached only once. Metalama reports LAMA5112 on a method when the [Cache] aspect is added to it several times, for instance with a custom attribute and with a fabric, or with two fabrics. The aspect isn't applied.

In the following example, GetPrice has the [Cache] attribute, and a type fabric adds the same aspect to the same method.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Fabrics;
3using Metalama.Patterns.Caching.Aspects;
4using System.Linq;
5
6namespace Doc.LAMA5112.Error;
7
8public partial class PriceCatalog
9{
10    [Cache]
    Error LAMA5112: The [Cache] aspect can be applied at most once to a method.

11    public decimal GetPrice( string productId ) => productId.Length * 10m;
12
13    private sealed class Fabric : TypeFabric
14    {
15        public override void AmendType( ITypeAmender amender )
16        {
17            // The [Cache] aspect is already applied to GetPrice by the custom attribute.
18            amender
19                .Select( t => t.Methods.Single( m => m.Name == nameof(GetPrice) ) )
20                .AddAspect( m => new CacheAttribute() );
21        }
22    }
23}
24

Resolution

Remove the duplicate: keep either the custom attribute or the code that adds the aspect, such as the fabric.

In the following example, the fabric adds the aspect to GetStock only, so each method is cached once.

Source Code
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Fabrics;
3using Metalama.Patterns.Caching.Aspects;
4using System.Linq;


5

6namespace Doc.LAMA5112.Fixed;

7
8public partial class PriceCatalog



9{
10    [Cache]
11    public decimal GetPrice( string productId ) => productId.Length * 10m;
12
13    public decimal GetStock( string productId ) => productId.Length * 3m;























14
15    private sealed class Fabric : TypeFabric
16    {
17        public override void AmendType( ITypeAmender amender )
18        {
19            // GetStock has no custom attribute, so the aspect is applied only once.
20            amender











21                .Select( t => t.Methods.Single( m => m.Name == nameof(GetStock) ) )
22                .AddAspect( m => new CacheAttribute() );
23        }
24    }
25}




26
Transformed Code
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Fabrics;
3using Metalama.Framework.RunTime;
4using Metalama.Patterns.Caching;
5using Metalama.Patterns.Caching.Aspects;
6using System;
7using System.Linq;
8using System.Reflection;
9
10namespace Doc.LAMA5112.Fixed;
11
12
13#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
14
15public partial class PriceCatalog
16{
17    [Cache]
18    public decimal GetPrice(string productId)
19    {
20        static object? Invoke(object? instance, object?[] args)
21        {
22            return ((PriceCatalog)instance).GetPrice_Source((string)args[0]);
23        }
24
25        return _cachingService.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetPrice, this, new object[] { productId }, Invoke);
26    }
27
28    private decimal GetPrice_Source(string productId) => productId.Length * 10m;
29
30    public decimal GetStock(string productId)
31    {
32        static object? Invoke(object? instance, object?[] args)
33        {
34            return ((PriceCatalog)instance).GetStock_Source((string)args[0]);
35        }
36
37        return _cachingService.GetFromCacheOrExecute<decimal>(_cacheRegistration_GetStock, this, new object[] { productId }, Invoke);
38    }
39
40    private decimal GetStock_Source(string productId) => productId.Length * 3m;
41
42
43#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
44
45    private sealed class Fabric : TypeFabric
46    {
47        public override void AmendType(ITypeAmender amender) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
48    }
49
50#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
51
52
53
54    private static readonly CachedMethodMetadata _cacheRegistration_GetPrice;
55    private static readonly CachedMethodMetadata _cacheRegistration_GetStock;
56    private ICachingService _cachingService;
57
58    static PriceCatalog()
59    {
60        _cacheRegistration_GetStock = CachedMethodMetadata.Register(typeof(PriceCatalog).GetMethod("GetStock", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null) ?? throw new MissingMethodException("The method 'PriceCatalog.GetStock(string)' could not be found using reflection."), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, false);
61        _cacheRegistration_GetPrice = CachedMethodMetadata.Register(typeof(PriceCatalog).GetMethod("GetPrice", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null) ?? throw new MissingMethodException("The method 'PriceCatalog.GetPrice(string)' could not be found using reflection."), new CachedMethodConfiguration() { AbsoluteExpiration = null, AutoReload = null, IgnoreThisParameter = null, Priority = null, ProfileName = (string?)null, SlidingExpiration = null }, false);
62    }
63


64    public PriceCatalog([AspectGenerated] ICachingService? cachingService = null)
65    {
66        this._cachingService = cachingService ?? throw new System.ArgumentNullException(nameof(cachingService));
67    }
68}
69
70#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
71
72
73

See also