Skip to content

Instantly share code, notes, and snippets.

@mrmyroll2
Last active March 29, 2025 18:54
Show Gist options
  • Save mrmyroll2/94bcd4031576b9417736a7d70579ac2c to your computer and use it in GitHub Desktop.
Save mrmyroll2/94bcd4031576b9417736a7d70579ac2c to your computer and use it in GitHub Desktop.
Error implementing `HybridCache` and `IDistributedCache` for query caching with MediatR
internal sealed class CacheService(
IMemoryCache memoryCache,
HybridCache hybridCache,
IDistributedCache distributedCache
) : ICacheService
{
public async Task<T?> GetOrCreateAsync<T>(
string key,
Func<CancellationToken, Task<T>> factory,
TimeSpan? expiration = null,
CancellationToken cancellationToken = default)
{
// IMemoryCache
T result2 = await memoryCache.GetOrCreateAsync(
key,
entry =>
{
entry.SetAbsoluteExpiration(expiration ?? TimeSpan.FromMinutes(5));
return factory(cancellationToken);
});
// HybridCache
var result = await hybridCache.GetOrCreateAsync(
key,
(cancel) =>
{
return new ValueTask<T>(factory(cancel));
},
cancellationToken: cancellationToken);
// IDistributedCache
var cachedData = await distributedCache.GetStringAsync(key, cancellationToken);
if (!string.IsNullOrEmpty(cachedData))
{
return JsonSerializer.Deserialize<T>(cachedData)!;
}
T result3 = await factory(cancellationToken);
var serializedData = JsonSerializer.Serialize(result);
await distributedCache.SetStringAsync(key, serializedData, cancellationToken);
return result;
}
}
public sealed record GetMemberQuery(Guid Id) : ICachedQuery<GetMemberResponse>
{
public string CacheKey => $"GetMemberQuery-{Id}";
public TimeSpan? Expiration => TimeSpan.FromMinutes(5);
}
public sealed record GetMemberResponse(string MemberName,
string IdentityNumber,
DateOnly EffectiveDate,
DateOnly ExpiryDate);
internal sealed class GetMemberQueryHandler(
IMemberRepository memberRepository)
: IQueryHandler<GetMemberQuery, GetMemberResponse>
{
public async Task<Result<GetMemberResponse>> Handle(GetMemberQuery request,
CancellationToken cancellationToken)
{
var data = await memberRepository.GetMemberById(request.Id, cancellationToken);
if (data is null)
{
return Result.Failure<GetMemberResponse>(MemberError.MemberNotFound(request.Id));
}
return new GetMemberResponse(data.MemberName,
data.IdentityNumber,
data.EffectiveDate,
data.ExpiryDate);
}
}
public class Result
{
public Result(bool isSuccess, Error error)
{
if (isSuccess && error != Error.None ||
!isSuccess && error == Error.None)
{
throw new ArgumentException("Invalid error", nameof(error));
}
IsSuccess = isSuccess;
Error = error;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Error Error { get; }
public static Result Success() => new(true, Error.None);
public static Result<TValue> Success<TValue>(TValue value) =>
new(value, true, Error.None);
public static Result Failure(Error error) => new(false, error);
public static Result<TValue> Failure<TValue>(Error error) =>
new(default, false, error);
}
public class Result<TValue> : Result
{
private readonly TValue? _value;
public Result(TValue? value, bool isSuccess, Error error)
: base(isSuccess, error)
{
_value = value;
}
// The problem is here
// it hit the throw
[NotNull]
public TValue Value => IsSuccess
? _value!
: throw new InvalidOperationException("The value of a failure result can't be accessed.");
public static implicit operator Result<TValue>(TValue? value) =>
value is not null ? Success(value) : Failure<TValue>(Error.NullValue);
public static Result<TValue> ValidationFailure(Error error) =>
new(default, false, error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment