Files
Umbraco-CMS/src/Umbraco.Infrastructure/Services/Implement/UserIdKeyResolver.cs

101 lines
3.2 KiB
C#

using System.Collections.Concurrent;
using NPoco;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Services.Implement;
// It's okay that we never clear this, since you can never change a user's key/id
// and it'll be caught by the services if it doesn't exist.
internal sealed class UserIdKeyResolver : IUserIdKeyResolver
{
private readonly IScopeProvider _scopeProvider;
private readonly ConcurrentDictionary<Guid, int> _keyToId = new();
private readonly ConcurrentDictionary<int, Guid> _idToKey = new();
private readonly SemaphoreSlim _keytToIdLock = new(1, 1);
private readonly SemaphoreSlim _idToKeyLock = new(1, 1);
public UserIdKeyResolver(IScopeProvider scopeProvider) => _scopeProvider = scopeProvider;
/// <inheritdoc/>
public async Task<int> GetAsync(Guid key)
{
if (_keyToId.TryGetValue(key, out int id))
{
return id;
}
// We don't have it in the cache, so we'll need to look it up in the database
// We'll lock, and then recheck, just to make sure the value wasn't added between the initial check and now.
await _keytToIdLock.WaitAsync();
try
{
if (_keyToId.TryGetValue(key, out int recheckedId))
{
// It was added while we were waiting, so we'll just return it
return recheckedId;
}
// Still not here, so actually fetch it now
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
ISqlContext sqlContext = scope.SqlContext;
Sql<ISqlContext> query = sqlContext.Sql()
.Select<UserDto>(x => x.Id)
.From<UserDto>()
.Where<UserDto>(x => x.Key == key);
int fetchedId = (await scope.Database.ExecuteScalarAsync<int?>(query))
?? throw new InvalidOperationException("No user found with the specified key");
_keyToId[key] = fetchedId;
return fetchedId;
}
finally
{
_keytToIdLock.Release();
}
}
/// <inheritdoc/>
public async Task<Guid> GetAsync(int id)
{
if (_idToKey.TryGetValue(id, out Guid key))
{
return key;
}
await _idToKeyLock.WaitAsync();
try
{
if (_idToKey.TryGetValue(id, out Guid recheckedKey))
{
return recheckedKey;
}
using IScope scope = _scopeProvider.CreateScope(autoComplete: true);
ISqlContext sqlContext = scope.SqlContext;
Sql<ISqlContext> query = sqlContext.Sql()
.Select<UserDto>(x => x.Key)
.From<UserDto>()
.Where<UserDto>(x => x.Id == id);
Guid fetchedKey = scope.Database.ExecuteScalar<Guid?>(query)
?? throw new InvalidOperationException("No user found with the specified id");
_idToKey[id] = fetchedKey;
return fetchedKey;
}
finally
{
_idToKeyLock.Release();
}
}
}