diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs b/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs index c3d1057b..e59be1e8 100644 --- a/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs +++ b/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs @@ -24,13 +24,13 @@ // Configure RCommon services.AddRCommon() .WithJsonSerialization() // Distributed memory caching requires serialization - .WithMemoryCaching(cache => + .WithMemoryCaching(cache => { cache.Configure(x => { x.ExpirationScanFrequency = TimeSpan.FromMinutes(1); - }) - .CacheDynamicallyCompiledExpressions(); + }); + cache.CacheDynamicallyCompiledExpressions(); }) .WithDistributedCaching(cache => { diff --git a/Src/RCommon.ApplicationServices/Commands/CommandBus.cs b/Src/RCommon.ApplicationServices/Commands/CommandBus.cs index 9ef36c4c..704db4ba 100644 --- a/Src/RCommon.ApplicationServices/Commands/CommandBus.cs +++ b/Src/RCommon.ApplicationServices/Commands/CommandBus.cs @@ -28,14 +28,13 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using RCommon.ApplicationServices.Caching; using RCommon.ApplicationServices.Commands; using RCommon.ApplicationServices.ExecutionResults; using RCommon.ApplicationServices.Validation; +using RCommon.Caching; using RCommon.Reflection; namespace RCommon.ApplicationServices.Commands @@ -44,19 +43,19 @@ public class CommandBus : ICommandBus { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; - private readonly IMemoryCache _memoryCache; private readonly IValidationService _validationService; private readonly IOptions _validationOptions; + private readonly ICacheService _cacheService; private readonly CachingOptions _cachingOptions; - public CommandBus(ILogger logger, IServiceProvider serviceProvider, IMemoryCache memoryCache, IValidationService validationService, - IOptions validationOptions, IOptions cachingOptions) + public CommandBus(ILogger logger, IServiceProvider serviceProvider, IValidationService validationService, + IOptions validationOptions, IOptions cachingOptions, ICommonFactory cacheFactory) { _logger = logger; _serviceProvider = serviceProvider; - _memoryCache = memoryCache; _validationService = validationService; _validationOptions = validationOptions; + _cacheService = cacheFactory.Create(ExpressionCachingStrategy.Default); _cachingOptions = cachingOptions.Value; } @@ -135,16 +134,7 @@ private CommandExecutionDetails GetCommandExecutionDetails(Type commandType) { if (_cachingOptions.CachingEnabled && _cachingOptions.CacheDynamicallyCompiledExpressions) { - var memoryCache = this._serviceProvider.GetService(); - Guard.IsNotNull(memoryCache, nameof(memoryCache)); - - return memoryCache.GetOrCreate( - CacheKey.With(GetType(), commandType.GetCacheKey()), - e => - { - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); - return this.BuildCommandDetails(commandType); - }); + return _cacheService.GetOrCreate(CacheKey.With(GetType(), commandType.GetCacheKey()), this.BuildCommandDetails(commandType)); } return this.BuildCommandDetails(commandType); } diff --git a/Src/RCommon.ApplicationServices/CqrsBuilder.cs b/Src/RCommon.ApplicationServices/CqrsBuilder.cs index 2a6482e0..0af33a80 100644 --- a/Src/RCommon.ApplicationServices/CqrsBuilder.cs +++ b/Src/RCommon.ApplicationServices/CqrsBuilder.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using RCommon.ApplicationServices.Commands; using RCommon.ApplicationServices.Queries; using RCommon.EventHandling; diff --git a/Src/RCommon.ApplicationServices/Queries/QueryBus.cs b/Src/RCommon.ApplicationServices/Queries/QueryBus.cs index 56a35bb8..ee953266 100644 --- a/Src/RCommon.ApplicationServices/Queries/QueryBus.cs +++ b/Src/RCommon.ApplicationServices/Queries/QueryBus.cs @@ -26,12 +26,11 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using RCommon.ApplicationServices.Caching; using RCommon.ApplicationServices.Validation; +using RCommon.Caching; using RCommon.Reflection; namespace RCommon.ApplicationServices.Queries @@ -49,15 +48,17 @@ private class HandlerFuncMapping private readonly IValidationService _validationService; private readonly IOptions _validationOptions; private readonly CachingOptions _cachingOptions; + private readonly ICacheService _cacheService; public QueryBus(ILogger logger, IServiceProvider serviceProvider, IValidationService validationService, - IOptions validationOptions, IOptions cachingOptions) + IOptions validationOptions, IOptions cachingOptions, ICommonFactory cacheFactory) { _logger = logger; _serviceProvider = serviceProvider; _validationService = validationService; _validationOptions = validationOptions; _cachingOptions = cachingOptions.Value; + _cacheService = cacheFactory.Create(ExpressionCachingStrategy.Default); } public async Task DispatchQueryAsync(IQuery query, CancellationToken cancellationToken = default) @@ -90,16 +91,8 @@ private HandlerFuncMapping GetHandlerFuncMapping(Type queryType) { if (_cachingOptions.CachingEnabled && _cachingOptions.CacheDynamicallyCompiledExpressions) { - var memoryCache = this._serviceProvider.GetService(); - Guard.IsNotNull(memoryCache, nameof(memoryCache)); - - return memoryCache.GetOrCreate( - CacheKey.With(GetType(), queryType.GetCacheKey()), - e => - { - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); - return this.BuildHandlerFuncMapping(queryType); - }); + return _cacheService.GetOrCreate(CacheKey.With(GetType(), queryType.GetCacheKey()), + this.BuildHandlerFuncMapping(queryType)); } return this.BuildHandlerFuncMapping(queryType); diff --git a/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj b/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj index 2301b638..fcc722dc 100644 --- a/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj +++ b/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj @@ -4,10 +4,6 @@ net6.0;net8.0; - - - - @@ -20,5 +16,8 @@ + + + diff --git a/Src/RCommon.ApplicationServices/Caching/CacheKey.cs b/Src/RCommon.Caching/CacheKey.cs similarity index 87% rename from Src/RCommon.ApplicationServices/Caching/CacheKey.cs rename to Src/RCommon.Caching/CacheKey.cs index 2ce5f288..666a793c 100644 --- a/Src/RCommon.ApplicationServices/Caching/CacheKey.cs +++ b/Src/RCommon.Caching/CacheKey.cs @@ -22,16 +22,21 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; -using Newtonsoft.Json; -using RCommon.Entities.ValueObjects; -namespace RCommon.ApplicationServices.Caching +namespace RCommon.Caching { - [JsonConverter(typeof(SingleValueObjectConverter))] - public class CacheKey : SingleValueObject + public class CacheKey { public const int MaxLength = 256; + public CacheKey(string value) + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentNullException(nameof(value)); + if (value.Length > MaxLength) + throw new ArgumentOutOfRangeException(nameof(value), value, $"Cache keys can maximum be '{MaxLength}' in length"); + } + public static CacheKey With(params string[] keys) { return new CacheKey(string.Join("-", keys)); @@ -41,13 +46,5 @@ public static CacheKey With(Type ownerType, params string[] keys) { return With($"{ownerType.GetCacheKey()}:{string.Join("-", keys)}"); } - - public CacheKey(string value) : base(value) - { - if (string.IsNullOrEmpty(value)) - throw new ArgumentNullException(nameof(value)); - if (value.Length > MaxLength) - throw new ArgumentOutOfRangeException(nameof(value), value, $"Cache keys can maximum be '{MaxLength}' in length"); - } } } diff --git a/Src/RCommon.Caching/CachingBuilderExtensions.cs b/Src/RCommon.Caching/CachingBuilderExtensions.cs index 4d37cc1a..0e0ede17 100644 --- a/Src/RCommon.Caching/CachingBuilderExtensions.cs +++ b/Src/RCommon.Caching/CachingBuilderExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using RCommon.Json; using System; using System.Collections.Generic; diff --git a/Src/RCommon.Caching/ExpressionCachingStrategy.cs b/Src/RCommon.Caching/ExpressionCachingStrategy.cs new file mode 100644 index 00000000..fb708bfe --- /dev/null +++ b/Src/RCommon.Caching/ExpressionCachingStrategy.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public enum ExpressionCachingStrategy + { + Default + } +} diff --git a/Src/RCommon.Caching/ICacheService.cs b/Src/RCommon.Caching/ICacheService.cs new file mode 100644 index 00000000..e4eebe4e --- /dev/null +++ b/Src/RCommon.Caching/ICacheService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public interface ICacheService + { + TData GetOrCreate(object key, TData data); + Task GetOrCreateAsync(object key, TData data); + } +} diff --git a/Src/RCommon.Core/CommonFactory.cs b/Src/RCommon.Core/CommonFactory.cs index b63b8504..a568b07c 100644 --- a/Src/RCommon.Core/CommonFactory.cs +++ b/Src/RCommon.Core/CommonFactory.cs @@ -6,21 +6,21 @@ namespace RCommon { - public class CommonFactory : ICommonFactory + public class CommonFactory : ICommonFactory { - private readonly Func _initFunc; + private readonly Func _initFunc; - public CommonFactory(Func initFunc) + public CommonFactory(Func initFunc) { _initFunc = initFunc; } - public T Create() + public TResult Create() { return _initFunc(); } - public T Create(Action customize) + public TResult Create(Action customize) { var concreteObject = _initFunc(); customize(concreteObject); diff --git a/Src/RCommon.Entities/ValueObjects/ISingleValueObject.cs b/Src/RCommon.Entities/ValueObjects/ISingleValueObject.cs deleted file mode 100644 index e71efacb..00000000 --- a/Src/RCommon.Entities/ValueObjects/ISingleValueObject.cs +++ /dev/null @@ -1,30 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -namespace RCommon.Entities.ValueObjects -{ - public interface ISingleValueObject - { - object GetValue(); - } -} diff --git a/Src/RCommon.Entities/ValueObjects/SingleValueObject.cs b/Src/RCommon.Entities/ValueObjects/SingleValueObject.cs deleted file mode 100644 index d0791432..00000000 --- a/Src/RCommon.Entities/ValueObjects/SingleValueObject.cs +++ /dev/null @@ -1,81 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace RCommon.Entities.ValueObjects -{ - public abstract class SingleValueObject : ValueObject, IComparable, ISingleValueObject - where T : IComparable - { - private static readonly Type Type = typeof(T); - private static readonly TypeInfo TypeInfo = typeof(T).GetTypeInfo(); - - public T Value { get; } - - protected SingleValueObject(T value) - { - if (TypeInfo.IsEnum && !Enum.IsDefined(Type, value)) - { - throw new ArgumentException($"The value '{value}' isn't defined in enum '{Type}'"); - } - - Value = value; - } - - public int CompareTo(object obj) - { - if (ReferenceEquals(null, obj)) - { - throw new ArgumentNullException(nameof(obj)); - } - - var other = obj as SingleValueObject; - if (other == null) - { - throw new ArgumentException($"Cannot compare '{GetType()}' and '{obj.GetType()}'"); - } - - return Value.CompareTo(other.Value); - } - - protected override IEnumerable GetEqualityComponents() - { - yield return Value; - } - - public override string ToString() - { - return ReferenceEquals(Value, null) - ? string.Empty - : Value.ToString(); - } - - public object GetValue() - { - return Value; - } - } -} diff --git a/Src/RCommon.Entities/ValueObjects/SingleValueObjectConverter.cs b/Src/RCommon.Entities/ValueObjects/SingleValueObjectConverter.cs deleted file mode 100644 index ede4ed1f..00000000 --- a/Src/RCommon.Entities/ValueObjects/SingleValueObjectConverter.cs +++ /dev/null @@ -1,72 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; -using Newtonsoft.Json; - -namespace RCommon.Entities.ValueObjects -{ - public class SingleValueObjectConverter : JsonConverter - { - private static readonly ConcurrentDictionary ConstructorArgumentTypes = new ConcurrentDictionary(); - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (!(value is ISingleValueObject singleValueObject)) - { - return; - } - - serializer.Serialize(writer, singleValueObject.GetValue()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.Value == null) - { - return null; - } - - var parameterType = ConstructorArgumentTypes.GetOrAdd( - objectType, - t => - { - var constructorInfo = objectType.GetTypeInfo().GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); - var parameterInfo = constructorInfo.GetParameters().Single(); - return parameterInfo.ParameterType; - }); - - var value = serializer.Deserialize(reader, parameterType); - - return Activator.CreateInstance(objectType, value); - } - - public override bool CanConvert(Type objectType) - { - return typeof(ISingleValueObject).GetTypeInfo().IsAssignableFrom(objectType); - } - } -} diff --git a/Src/RCommon.Entities/ValueObjects/ValueObject.cs b/Src/RCommon.Entities/ValueObjects/ValueObject.cs deleted file mode 100644 index 1ec26e14..00000000 --- a/Src/RCommon.Entities/ValueObjects/ValueObject.cs +++ /dev/null @@ -1,84 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace RCommon.Entities.ValueObjects -{ - public abstract class ValueObject - { - private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) return true; - if (ReferenceEquals(null, obj)) return false; - if (GetType() != obj.GetType()) return false; - var other = obj as ValueObject; - return other != null && GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); - } - - public override int GetHashCode() - { - unchecked - { - return GetEqualityComponents().Aggregate(17, (current, obj) => current * 23 + (obj?.GetHashCode() ?? 0)); - } - } - - public static bool operator ==(ValueObject left, ValueObject right) - { - return Equals(left, right); - } - - public static bool operator !=(ValueObject left, ValueObject right) - { - return !Equals(left, right); - } - - public override string ToString() - { - return $"{{{string.Join(", ", GetProperties().Select(f => $"{f.Name}: {f.GetValue(this)}"))}}}"; - } - - protected virtual IEnumerable GetEqualityComponents() - { - return GetProperties().Select(x => x.GetValue(this)); - } - - protected virtual IEnumerable GetProperties() - { - return TypeProperties.GetOrAdd( - GetType(), - t => t - .GetTypeInfo() - .GetProperties(BindingFlags.Instance | BindingFlags.Public) - .OrderBy(p => p.Name) - .ToList()); - } - } -} diff --git a/Src/RCommon.MemoryCache/DistributedMemoryCacheService.cs b/Src/RCommon.MemoryCache/DistributedMemoryCacheService.cs new file mode 100644 index 00000000..e1889438 --- /dev/null +++ b/Src/RCommon.MemoryCache/DistributedMemoryCacheService.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Caching.Distributed; +using RCommon.Caching; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + /// + /// Just a proxy for Distributed memory caching implemented through caching abstractions + /// + /// This gives us a uniform way for getting/setting cache no matter the caching strategy + public class DistributedMemoryCacheService : ICacheService + { + private readonly IDistributedCache _distributedCache; + private readonly IJsonSerializer _jsonSerializer; + + public DistributedMemoryCacheService(IDistributedCache distributedCache, IJsonSerializer jsonSerializer) + { + _distributedCache = distributedCache; + _jsonSerializer = jsonSerializer; + } + + public TData GetOrCreate(object key, TData data) + { + var json = _distributedCache.GetString(key.ToString()); + + if (json == null) + { + _distributedCache.SetString(key.ToString(), _jsonSerializer.Serialize(data)); + return data; + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + + public async Task GetOrCreateAsync(object key, TData data) + { + var json = await _distributedCache.GetStringAsync(key.ToString()).ConfigureAwait(false); + + if (json == null) + { + await _distributedCache.SetStringAsync(key.ToString(), _jsonSerializer.Serialize(data)).ConfigureAwait(false); + return data; + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + } +} diff --git a/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs b/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs index 2e1ef588..5139cb42 100644 --- a/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs +++ b/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using RCommon.Caching; using System; using System.Collections.Generic; @@ -16,5 +17,34 @@ public static IDistributedMemoryCachingBuilder Configure(this IDistributedMemory builder.Services.AddDistributedMemoryCache(actions); return builder; } + + /// + /// This greatly improves performance across various areas of RCommon which use generics and reflection heavily + /// to compile expressions and lambdas + /// + /// Builder + /// Same builder to allow chaining + /// The most performant way to do this is through InMemoryCache but this works fine + public static IDistributedMemoryCachingBuilder CacheDynamicallyCompiledExpressions(this IDistributedMemoryCachingBuilder builder) + { + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case ExpressionCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + return builder; + } } } diff --git a/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs b/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs index 8fd0a6f6..88e6b995 100644 --- a/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs +++ b/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; using System; using System.Collections.Generic; using System.Linq; @@ -20,14 +22,27 @@ public static IInMemoryCachingBuilder Configure(this IInMemoryCachingBuilder bui /// This greatly improves performance across various areas of RCommon which use generics and reflection heavily /// to compile expressions and lambdas /// - /// - /// + /// Builder + /// Same builder to allow chaining + /// This is the most performant way to cache expressions! public static IInMemoryCachingBuilder CacheDynamicallyCompiledExpressions(this IInMemoryCachingBuilder builder) { - builder.Services.Configure(x => - { - x.CachingEnabled = true; - x.CacheDynamicallyCompiledExpressions = true; + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case ExpressionCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; }); return builder; } diff --git a/Src/RCommon.MemoryCache/InMemoryCacheService.cs b/Src/RCommon.MemoryCache/InMemoryCacheService.cs new file mode 100644 index 00000000..1b34b503 --- /dev/null +++ b/Src/RCommon.MemoryCache/InMemoryCacheService.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Caching.Memory; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + /// + /// Just a proxy for memory caching implemented through caching abstractions + /// + /// This gives us a uniform way for getting/setting cache no matter the caching strategy + public class InMemoryCacheService : ICacheService + { + private readonly IMemoryCache _memoryCache; + + public InMemoryCacheService(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public TData GetOrCreate(object key, TData data) + { + _memoryCache.GetOrCreate(key, cacheEntry => + { + return data; + }); + return data; + } + + public async Task GetOrCreateAsync(object key, TData data) + { + await _memoryCache.GetOrCreateAsync(key, async cacheEntry => + { + return await Task.FromResult(data); + }).ConfigureAwait(false); + return data; + } + } +} diff --git a/Src/RCommon.MemoryCache/MemoryCachingBuilder.cs b/Src/RCommon.MemoryCache/InMemoryCachingBuilder.cs similarity index 79% rename from Src/RCommon.MemoryCache/MemoryCachingBuilder.cs rename to Src/RCommon.MemoryCache/InMemoryCachingBuilder.cs index 66b285fa..9053b2d2 100644 --- a/Src/RCommon.MemoryCache/MemoryCachingBuilder.cs +++ b/Src/RCommon.MemoryCache/InMemoryCachingBuilder.cs @@ -8,9 +8,9 @@ namespace RCommon.MemoryCache { - public class MemoryCachingBuilder : IInMemoryCachingBuilder + public class InMemoryCachingBuilder : IInMemoryCachingBuilder { - public MemoryCachingBuilder(IRCommonBuilder builder) + public InMemoryCachingBuilder(IRCommonBuilder builder) { Services = builder.Services; this.RegisterServices(Services); diff --git a/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs b/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs index 091cf831..820f9e4b 100644 --- a/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs +++ b/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs @@ -16,132 +16,134 @@ namespace RCommon.Persistence.Caching.Crud public class CachingGraphRepository : IGraphRepository where TEntity : class, IBusinessEntity { - private readonly IGraphRepository _graphRepository; + private readonly IGraphRepository _repository; + private readonly ICacheService _cacheService; - public CachingGraphRepository(IGraphRepository graphRepository) + public CachingGraphRepository(IGraphRepository repository, ICommonFactory cacheFactory) { - _graphRepository = graphRepository; + _repository = repository; + _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default); } - public bool Tracking { get => _graphRepository.Tracking; set => _graphRepository.Tracking = value; } + public bool Tracking { get => _repository.Tracking; set => _repository.Tracking = value; } - public Type ElementType => _graphRepository.ElementType; + public Type ElementType => _repository.ElementType; - public Expression Expression => _graphRepository.Expression; + public Expression Expression => _repository.Expression; - public IQueryProvider Provider => _graphRepository.Provider; + public IQueryProvider Provider => _repository.Provider; - public string DataStoreName { get => _graphRepository.DataStoreName; set => _graphRepository.DataStoreName = value; } + public string DataStoreName { get => _repository.DataStoreName; set => _repository.DataStoreName = value; } public async Task AddAsync(TEntity entity, CancellationToken token = default) { - await _graphRepository.AddAsync(entity, token); + await _repository.AddAsync(entity, token); } public async Task AnyAsync(Expression> expression, CancellationToken token = default) { - return await _graphRepository.AnyAsync(expression, token); + return await _repository.AnyAsync(expression, token); } public async Task AnyAsync(ISpecification specification, CancellationToken token = default) { - return await _graphRepository.AnyAsync(specification, token); + return await _repository.AnyAsync(specification, token); } public async Task DeleteAsync(TEntity entity, CancellationToken token = default) { - await _graphRepository.DeleteAsync(entity, token); + await _repository.DeleteAsync(entity, token); } public async Task FindAsync(object primaryKey, CancellationToken token = default) { - return await _graphRepository.FindAsync(primaryKey, token); + return await _repository.FindAsync(primaryKey, token); } public IQueryable FindQuery(ISpecification specification) { - return _graphRepository.FindQuery(specification); + return _repository.FindQuery(specification); } public IQueryable FindQuery(Expression> expression) { - return _graphRepository.FindQuery(expression); + return _repository.FindQuery(expression); } public IQueryable FindQuery(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0) { - return _graphRepository.FindQuery(expression, orderByExpression, orderByAscending, pageNumber, pageSize); + return _repository.FindQuery(expression, orderByExpression, orderByAscending, pageNumber, pageSize); } public IQueryable FindQuery(IPagedSpecification specification) { - return _graphRepository.FindQuery(specification); + return _repository.FindQuery(specification); } public async Task FindSingleOrDefaultAsync(Expression> expression, CancellationToken token = default) { - return await _graphRepository.FindSingleOrDefaultAsync(expression, token); + return await _repository.FindSingleOrDefaultAsync(expression, token); } public async Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) { - return await _graphRepository.FindSingleOrDefaultAsync(specification, token); + return await _repository.FindSingleOrDefaultAsync(specification, token); } public async Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) { - return await _graphRepository.GetCountAsync(selectSpec, token); + return await _repository.GetCountAsync(selectSpec, token); } public async Task GetCountAsync(Expression> expression, CancellationToken token = default) { - return await _graphRepository.GetCountAsync(expression, token); + return await _repository.GetCountAsync(expression, token); } public IEnumerator GetEnumerator() { - return _graphRepository.GetEnumerator(); + return _repository.GetEnumerator(); } public IEagerLoadableQueryable Include(Expression> path) { - return _graphRepository.Include(path); + return _repository.Include(path); } public IEagerLoadableQueryable ThenInclude(Expression> path) { - return _graphRepository.ThenInclude(path); + return _repository.ThenInclude(path); } public async Task UpdateAsync(TEntity entity, CancellationToken token = default) { - await _graphRepository.UpdateAsync(entity, token); + await _repository.UpdateAsync(entity, token); } IEnumerator IEnumerable.GetEnumerator() { - return _graphRepository.GetEnumerator(); + return _repository.GetEnumerator(); } public async Task> FindAsync(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) { - return await _graphRepository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); + return await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); } public async Task> FindAsync(IPagedSpecification specification, CancellationToken token = default) { - return await _graphRepository.FindAsync(specification, token); + return await _repository.FindAsync(specification, token); } public async Task> FindAsync(ISpecification specification, CancellationToken token = default) { - return await _graphRepository.FindAsync(specification, token); + return await _repository.FindAsync(specification, token); } public async Task> FindAsync(Expression> expression, CancellationToken token = default) { - return await _graphRepository.FindAsync(expression, token); + return await _repository.FindAsync(expression, token); } // Cached items @@ -150,22 +152,22 @@ public async Task> FindAsync(Expression> orderByExpression, bool orderByAscending, string cacheKey, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) { - return await _graphRepository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); + return await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); } public async Task> FindAsync(IPagedSpecification specification, string cacheKey, CancellationToken token = default) { - return await _graphRepository.FindAsync(specification, token); + return await _repository.FindAsync(specification, token); } public async Task> FindAsync(ISpecification specification, string cacheKey, CancellationToken token = default) { - return await _graphRepository.FindAsync(specification, token); + return await _repository.FindAsync(specification, token); } public async Task> FindAsync(Expression> expression, string cacheKey, CancellationToken token = default) { - return await _graphRepository.FindAsync(expression, token); + return await _repository.FindAsync(expression, token); } } } diff --git a/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs b/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs index 33dd6d46..4ba5bf4b 100644 --- a/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs +++ b/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs @@ -1,4 +1,5 @@ -using RCommon.Collections; +using RCommon.Caching; +using RCommon.Collections; using RCommon.Entities; using RCommon.Persistence.Crud; using System; @@ -15,122 +16,156 @@ namespace RCommon.Persistence.Caching.Crud public class CachingLinqRepository : ILinqRepository where TEntity : class, IBusinessEntity { - public Type ElementType => throw new NotImplementedException(); + private readonly IGraphRepository _repository; + private readonly ICacheService _cacheService; - public Expression Expression => throw new NotImplementedException(); + public CachingLinqRepository(IGraphRepository repository, ICommonFactory cacheFactory) + { + _repository = repository; + _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default); + } + + public Type ElementType => _repository.ElementType; - public IQueryProvider Provider => throw new NotImplementedException(); + public Expression Expression => _repository.Expression; - public string DataStoreName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public IQueryProvider Provider => _repository.Provider; - public Task AddAsync(TEntity entity, CancellationToken token = default) + public string DataStoreName { get => _repository.DataStoreName; set => _repository.DataStoreName = value; } + + public async Task AddAsync(TEntity entity, CancellationToken token = default) { - throw new NotImplementedException(); + await _repository.AddAsync(entity, token); } - public Task AnyAsync(Expression> expression, CancellationToken token = default) + public async Task AnyAsync(Expression> expression, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.AnyAsync(expression, token); } - public Task AnyAsync(ISpecification specification, CancellationToken token = default) + public async Task AnyAsync(ISpecification specification, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.AnyAsync(specification, token); } - public Task DeleteAsync(TEntity entity, CancellationToken token = default) + public async Task DeleteAsync(TEntity entity, CancellationToken token = default) { - throw new NotImplementedException(); + await _repository.DeleteAsync(entity, token); } - public Task> FindAsync(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) + public async Task FindAsync(object primaryKey, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindAsync(primaryKey, token); } - public Task> FindAsync(IPagedSpecification specification, CancellationToken token = default) + public IQueryable FindQuery(ISpecification specification) { - throw new NotImplementedException(); + return _repository.FindQuery(specification); } - public Task> FindAsync(ISpecification specification, CancellationToken token = default) + public IQueryable FindQuery(Expression> expression) { - throw new NotImplementedException(); + return _repository.FindQuery(expression); } - public Task> FindAsync(Expression> expression, CancellationToken token = default) + public IQueryable FindQuery(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0) { - throw new NotImplementedException(); + return _repository.FindQuery(expression, orderByExpression, orderByAscending, pageNumber, pageSize); } - public Task FindAsync(object primaryKey, CancellationToken token = default) + public IQueryable FindQuery(IPagedSpecification specification) { - throw new NotImplementedException(); + return _repository.FindQuery(specification); } - public IQueryable FindQuery(ISpecification specification) + public async Task FindSingleOrDefaultAsync(Expression> expression, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindSingleOrDefaultAsync(expression, token); } - public IQueryable FindQuery(Expression> expression) + public async Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindSingleOrDefaultAsync(specification, token); } - public IQueryable FindQuery(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0) + public async Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.GetCountAsync(selectSpec, token); } - public IQueryable FindQuery(IPagedSpecification specification) + public async Task GetCountAsync(Expression> expression, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.GetCountAsync(expression, token); } - public Task FindSingleOrDefaultAsync(Expression> expression, CancellationToken token = default) + public IEnumerator GetEnumerator() { - throw new NotImplementedException(); + return _repository.GetEnumerator(); } - public Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) + public IEagerLoadableQueryable Include(Expression> path) { - throw new NotImplementedException(); + return _repository.Include(path); } - public Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) + public IEagerLoadableQueryable ThenInclude(Expression> path) { - throw new NotImplementedException(); + return _repository.ThenInclude(path); } - public Task GetCountAsync(Expression> expression, CancellationToken token = default) + public async Task UpdateAsync(TEntity entity, CancellationToken token = default) { - throw new NotImplementedException(); + await _repository.UpdateAsync(entity, token); } - public IEnumerator GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { - throw new NotImplementedException(); + return _repository.GetEnumerator(); } - public IEagerLoadableQueryable Include(Expression> path) + public async Task> FindAsync(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); } - public IEagerLoadableQueryable ThenInclude(Expression> path) + public async Task> FindAsync(IPagedSpecification specification, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindAsync(specification, token); } - public Task UpdateAsync(TEntity entity, CancellationToken token = default) + public async Task> FindAsync(ISpecification specification, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindAsync(specification, token); } - IEnumerator IEnumerable.GetEnumerator() + public async Task> FindAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.FindAsync(expression, token); + } + + // Cached items + + public async Task> FindAsync(Expression> expression, Expression> orderByExpression, bool orderByAscending, string cacheKey, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) + { + + return await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); + } + + public async Task> FindAsync(IPagedSpecification specification, string cacheKey, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(ISpecification specification, string cacheKey, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(Expression> expression, string cacheKey, CancellationToken token = default) { - throw new NotImplementedException(); + return await _repository.FindAsync(expression, token); } } } diff --git a/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs b/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs index d21ce93a..c2256db6 100644 --- a/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs +++ b/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs @@ -1,12 +1,88 @@ -using System; +using RCommon.Caching; +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace RCommon.Persistence.Caching.Crud { - public class CachingSqlMapperRepository + public class CachingSqlMapperRepository : ISqlMapperRepository + where TEntity : class, IBusinessEntity { + private readonly IGraphRepository _repository; + private readonly ICacheService _cacheService; + + public CachingSqlMapperRepository(IGraphRepository repository, ICommonFactory cacheFactory) + { + _repository = repository; + _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default); + } + + public string TableName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public string DataStoreName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public Task AddAsync(TEntity entity, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task AnyAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task AnyAsync(ISpecification specification, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task DeleteAsync(TEntity entity, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task> FindAsync(ISpecification specification, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task> FindAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task FindAsync(object primaryKey, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task FindSingleOrDefaultAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task GetCountAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + throw new NotImplementedException(); + } + + public Task UpdateAsync(TEntity entity, CancellationToken token = default) + { + throw new NotImplementedException(); + } } } diff --git a/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilder.cs b/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilder.cs index f7f78577..8150ecef 100644 --- a/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilder.cs +++ b/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilder.cs @@ -1,4 +1,5 @@ -using RCommon.Caching; +using Microsoft.Extensions.DependencyInjection; +using RCommon.Caching; using System; using System.Collections.Generic; using System.Linq; @@ -9,5 +10,6 @@ namespace RCommon.Persistence.Caching { public interface IPersistenceCachingBuilder { + public IServiceCollection Services { get; } } } diff --git a/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilderExtensions.cs b/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilderExtensions.cs index 7ec732c5..e866d4d1 100644 --- a/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilderExtensions.cs +++ b/Src/RCommon.Persistence.Caching/IPersistenceCachingBuilderExtensions.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; using System; using System.Collections.Generic; using System.Linq; @@ -9,9 +11,16 @@ namespace RCommon.Persistence.Caching { public static class IPersistenceCachingBuilderExtensions { - public static IPersistenceCachingBuilder Configure(this IPersistenceCachingBuilder builder) + public static IPersistenceCachingBuilder Configure(this IPersistenceCachingBuilder builder, Func> cacheFactory) { - + builder.Services.TryAddTransient>(cacheFactory); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); return builder; } } diff --git a/Src/RCommon.Persistence.Caching/PersistenceCachingStrategy.cs b/Src/RCommon.Persistence.Caching/PersistenceCachingStrategy.cs new file mode 100644 index 00000000..0897394d --- /dev/null +++ b/Src/RCommon.Persistence.Caching/PersistenceCachingStrategy.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching +{ + public enum PersistenceCachingStrategy + { + Default + } +} diff --git a/Src/RCommon.Persistence/Crud/IGraphRepository.cs b/Src/RCommon.Persistence/Crud/IGraphRepository.cs index 54b763b8..2791f0e4 100644 --- a/Src/RCommon.Persistence/Crud/IGraphRepository.cs +++ b/Src/RCommon.Persistence/Crud/IGraphRepository.cs @@ -10,7 +10,7 @@ namespace RCommon.Persistence.Crud { - public interface IGraphRepository : ILinqRepository, IEagerLoadableQueryable + public interface IGraphRepository : ILinqRepository where TEntity : class, IBusinessEntity { diff --git a/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs b/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs index 63f1d223..fc16522f 100644 --- a/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs +++ b/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; using System; using System.Collections.Generic; using System.Linq; @@ -15,5 +17,34 @@ public static IRedisCachingBuilder Configure(this IRedisCachingBuilder builder, builder.Services.AddStackExchangeRedisCache(actions); return builder; } + + /// + /// This greatly improves performance across various areas of RCommon which use generics and reflection heavily + /// to compile expressions and lambdas + /// + /// Builder + /// Same builder to allow chaining + /// The most performant way to do this is through InMemoryCache but this works fine + public static IRedisCachingBuilder CacheDynamicallyCompiledExpressions(this IRedisCachingBuilder builder) + { + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case ExpressionCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + return builder; + } } } diff --git a/Src/RCommon.RedisCache/RedisCacheService.cs b/Src/RCommon.RedisCache/RedisCacheService.cs new file mode 100644 index 00000000..156f0d98 --- /dev/null +++ b/Src/RCommon.RedisCache/RedisCacheService.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Caching.Distributed; +using RCommon.Caching; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.RedisCache +{ + /// + /// Just a wrapper for Redis data caching implemented through caching abstractions + /// + /// This gives us a uniform way for getting/setting cache no matter the caching strategy + public class RedisCacheService : ICacheService + { + private readonly IDistributedCache _distributedCache; + private readonly IJsonSerializer _jsonSerializer; + + public RedisCacheService(IDistributedCache distributedCache, IJsonSerializer jsonSerializer) + { + _distributedCache = distributedCache; + _jsonSerializer = jsonSerializer; + } + + public TData GetOrCreate(object key, TData data) + { + var json = _distributedCache.GetString(key.ToString()); + + if (json == null) + { + _distributedCache.SetString(key.ToString(), _jsonSerializer.Serialize(data)); + return data; + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + + public async Task GetOrCreateAsync(object key, TData data) + { + var json = await _distributedCache.GetStringAsync(key.ToString()).ConfigureAwait(false); + + if (json == null) + { + await _distributedCache.SetStringAsync(key.ToString(), _jsonSerializer.Serialize(data)).ConfigureAwait(false); + return data; + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + } +}