elsa-workflows/elsa-core · error · InvalidOperationException

Factory returned null for cache key

Error message

Factory returned null for cache key: {key}.

What it means

CacheManager.GetOrCreateAsync wraps IMemoryCache.GetOrCreateAsync and guarantees a non-null result: if the factory returns null, it throws InvalidOperationException naming the cache key. The contract is that a cached value must always be materializable; a null factory result is treated as a programming error.

Solutions

  1. Make the factory return a non-null value (empty collection, default record, or sentinel) on miss.
  2. Return the error/throw a domain-specific exception inside the factory instead of returning null.
  3. If null is legitimate, use IMemoryCache directly rather than CacheManager.GetOrCreateAsync.

Example fix

// before
var user = await cache.GetOrCreateAsync<User>(key, async e => await repo.FindAsync(id)); // may return null
// after
var user = await cache.GetOrCreateAsync<User?>(key, async e => await repo.FindAsync(id))
           ?? await repo.FindAsync(id); // or have the factory throw/return a sentinel
Defensive patterns

Strategy: try-catch

Validate before calling

var existing = memoryCache.Get(key);
// ensure your factory cannot return null:
TItem Load() => repo.Find(id) ?? throw new KeyNotFoundException($"{key} not found");

Try / catch

try { item = await cache.GetOrCreateAsync<TItem>(key, factory); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Factory returned null")) { item = fallbackValue; }

Prevention

When it happens

Trigger: Calling GetOrCreateAsync<TItem> with a factory whose async body returns null (e.g. an entity lookup that finds nothing, or a TryGetValue-style factory).

Common situations: Caching a record lookup whose miss path returns null instead of a sentinel/empty value; refactoring a sync factory to async where the miss branch changed shape.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/25b65f8da4fab74d. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Caching/Services/CacheManager.cs:42

    }
    
    /// <inheritdoc />
    public async Task<TItem?> FindOrCreateAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory)
    {
        return await memoryCache.GetOrCreateAsync(key, async entry => await factory(entry));
    }

    /// <summary>
    /// Retrieves a cached item by the specified key or creates a new one using the provided factory function.
    /// </summary>
    /// <param name="key">The key used to identify the cached item.</param>
    /// <param name="factory">A factory function that provides the value to be cached if it does not already exist.</param>
    /// <typeparam name="TItem">The type of the item to retrieve or create.</typeparam>
    /// <returns>The cached or newly created item.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the factory function returns null.</exception>
    public async Task<TItem> GetOrCreateAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory)
    {
        return await memoryCache.GetOrCreateAsync(key, async entry => await factory(entry)) ?? throw new InvalidOperationException($"Factory returned null for cache key: {key}.");
    }
}

View on GitHub (pinned to fe9217bdfa)