From d7360b4781183cc135b5bd089e847bdd6b95ba5b Mon Sep 17 00:00:00 2001 From: Ian Robertson Date: Mon, 11 Mar 2019 11:37:32 +0000 Subject: [PATCH 1/4] Ignore nupkg files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 40a3b62..8736ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,4 @@ $RECYCLE.BIN/ /Outputs/4.5.1/EFCache.Redis.dll /EFCache.Redis.Tests/lib/dump.rdb .vs/ +*.nupkg From 8eb3e5f0800821dc94f07bc72d01f79d8c9eb54c Mon Sep 17 00:00:00 2001 From: Ian Robertson Date: Wed, 27 Mar 2019 11:27:42 +0000 Subject: [PATCH 2/4] Allow the use of Redis in a K8s cluster, rather than a stale version in the lib folder. Added instructions in comments of how to install a dev version of redis using helm and port-forwarding. This is a good way of testing newer Redis versions and keeping access to an old version. No change to library, so no new release. --- EFCache.Redis.Tests/RedisCacheLazyTests.cs | 22 ++++++++-- EFCache.Redis.Tests/RedisCacheTests.cs | 49 +++++++++++++++------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/EFCache.Redis.Tests/RedisCacheLazyTests.cs b/EFCache.Redis.Tests/RedisCacheLazyTests.cs index 4b2751c..01d50b9 100644 --- a/EFCache.Redis.Tests/RedisCacheLazyTests.cs +++ b/EFCache.Redis.Tests/RedisCacheLazyTests.cs @@ -9,13 +9,29 @@ public class RedisCacheLazyTests { private static readonly Lazy LazyConnection = new Lazy(() => - ConnectionMultiplexer.Connect("localhost:6379")); + ConnectionMultiplexer.Connect(RegularConnectionString)); + private static string RegularConnectionString; public RedisCacheLazyTests() { - RedisStorageEmulatorManager.Instance.StartProcess(false); - } + try + { + // See if we have a running copy of redis in a K8s Cluster + // helm install --name redis-dev --set password=secretpassword --set master.disableCommands= stable/redis + // kubectl get secret --namespace default redis-dev -o jsonpath="{.data.redis-password}" | base64 --decode + // kubectl port-forward --namespace default svc/redis-dev-master 6379:6379 + var connString = "localhost:6379,password=secretpassword"; + + var cache = new RedisCache(connString); + RegularConnectionString = connString; + } + catch (Exception) + { + // Could not connect to redis above, so start a local copy + RedisStorageEmulatorManager.Instance.StartProcess(false); + } + } [TestMethod] public void Item_cached_with_lazy() diff --git a/EFCache.Redis.Tests/RedisCacheTests.cs b/EFCache.Redis.Tests/RedisCacheTests.cs index dbbd613..8fe1e4d 100644 --- a/EFCache.Redis.Tests/RedisCacheTests.cs +++ b/EFCache.Redis.Tests/RedisCacheTests.cs @@ -12,19 +12,40 @@ public class TestObject public string Message { get; set; } } + [TestClass] [UsedImplicitly] public class RedisCacheTests { + private readonly string RegularConnectionString = "localhost:6379"; + private readonly string AdminConnectionString = "localhost:6379,allowAdmin=true"; public RedisCacheTests() { - RedisStorageEmulatorManager.Instance.StartProcess(false); + try + { + // See if we have a running copy of redis in a K8s Cluster + // helm install --name redis-dev --set password=secretpassword --set master.disableCommands= stable/redis + // kubectl get secret --namespace default redis-dev -o jsonpath="{.data.redis-password}" | base64 --decode + // kubectl port-forward --namespace default svc/redis-dev-master 6379:6379 + var connString = "localhost:6379,password=secretpassword"; + + var cache = new RedisCache(connString); + RegularConnectionString = connString; + AdminConnectionString = string.Join(",", connString, "allowAdmin=true"); + + } + catch (Exception) + { + // Could not connect to redis above, so start a local copy + RedisStorageEmulatorManager.Instance.StartProcess(false); + } + } [TestMethod] public void Item_cached() { - var cache = new RedisCache("localhost:6379"); + var cache = new RedisCache(RegularConnectionString); var item = new TestObject { Message = "OK" }; cache.PutItem("key", item, new string[0], TimeSpan.MaxValue, DateTimeOffset.MaxValue); @@ -39,7 +60,7 @@ public void Item_cached() [TestMethod] public void Item_not_returned_after_absolute_expiration_expired() { - var cache = new RedisCache("localhost:6379"); + var cache = new RedisCache(RegularConnectionString); var item = new TestObject { Message = "OK" }; cache.PutItem("key", item, new string[0], TimeSpan.MaxValue, DateTimeOffset.Now.AddMinutes(-10)); @@ -51,7 +72,7 @@ public void Item_not_returned_after_absolute_expiration_expired() [TestMethod] public void Item_not_returned_after_sliding_expiration_expired() { - var cache = new RedisCache("localhost:6379"); + var cache = new RedisCache(RegularConnectionString); var item = new TestObject { Message = "OK" }; cache.PutItem("key", item, new string[0], TimeSpan.Zero.Subtract(new TimeSpan(10000)), DateTimeOffset.MaxValue); @@ -63,7 +84,7 @@ public void Item_not_returned_after_sliding_expiration_expired() [TestMethod] public void Item_still_returned_after_sliding_expiration_period() { - var cache = new RedisCache("localhost:6379"); + var cache = new RedisCache(RegularConnectionString); var item = new TestObject { Message = "OK" }; // Cache the item with a sliding expiration of 10 seconds @@ -84,7 +105,7 @@ public void Item_still_returned_after_sliding_expiration_period() [TestMethod] public void InvalidateSets_invalidate_items_with_given_sets() { - var cache = new RedisCache("localhost:6379"); + var cache = new RedisCache(RegularConnectionString); cache.PutItem("1", new object(), new[] { "ES1", "ES2" }, TimeSpan.MaxValue, DateTimeOffset.MaxValue); cache.PutItem("2", new object(), new[] { "ES2", "ES3" }, TimeSpan.MaxValue, DateTimeOffset.MaxValue); @@ -102,7 +123,7 @@ public void InvalidateSets_invalidate_items_with_given_sets() [TestMethod] public void InvalidateItem_invalidates_item() { - var cache = new RedisCache("localhost:6379"); + var cache = new RedisCache(RegularConnectionString); cache.PutItem("1", new object(), new[] { "ES1", "ES2" }, TimeSpan.MaxValue, DateTimeOffset.MaxValue); cache.InvalidateItem("1"); @@ -113,7 +134,7 @@ public void InvalidateItem_invalidates_item() [TestMethod] public void Count_returns_numers_of_cached_entries() { - var cache = new RedisCache("localhost:6379,allowAdmin=true"); + var cache = new RedisCache(AdminConnectionString); cache.Purge(); @@ -131,7 +152,7 @@ public void Count_returns_numers_of_cached_entries() [TestMethod] public void Purge_removes_stale_items_from_cache() { - var cache = new RedisCache("localhost:6379,allowAdmin=true"); + var cache = new RedisCache(AdminConnectionString); cache.Purge(); @@ -152,35 +173,35 @@ public void Purge_removes_stale_items_from_cache() [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetItem_validates_parameters() { - var unused = new RedisCache("localhost:6379").GetItem(null, out _); + var unused = new RedisCache(RegularConnectionString).GetItem(null, out _); } [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void PutItem_validates_key_parameter() { - new RedisCache("localhost:6379").PutItem(null, 42, new string[0], TimeSpan.Zero, DateTimeOffset.Now); + new RedisCache(RegularConnectionString).PutItem(null, 42, new string[0], TimeSpan.Zero, DateTimeOffset.Now); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void PutItem_validates_dependentEntitySets_parameter() { - new RedisCache("localhost:6379").PutItem("1", 42, null, TimeSpan.Zero, DateTimeOffset.Now); + new RedisCache(RegularConnectionString).PutItem("1", 42, null, TimeSpan.Zero, DateTimeOffset.Now); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void InvalidateSets_validates_parameters() { - new RedisCache("localhost:6379").InvalidateSets(null); + new RedisCache(RegularConnectionString).InvalidateSets(null); } [TestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void InvalidateItem_validates_parameters() { - new RedisCache("localhost:6379").InvalidateItem(null); + new RedisCache(RegularConnectionString).InvalidateItem(null); } [TestMethod] From 12e505b736e060c2047457c993c4219c078b44a4 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 18 Nov 2019 15:34:35 +0100 Subject: [PATCH 3/4] PR40: Use Redis expiration (#40) --- EFCache.Redis.Tests/RedisCacheTests.cs | 32 ++++++++++++++++--- .../StackExchangeExtensionsTests.cs | 5 +-- EFCache.Redis/RedisCache.cs | 15 ++++++--- EFCache.Redis/StackExchangeRedisExtensions.cs | 7 ++-- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/EFCache.Redis.Tests/RedisCacheTests.cs b/EFCache.Redis.Tests/RedisCacheTests.cs index 8fe1e4d..cc1d9d2 100644 --- a/EFCache.Redis.Tests/RedisCacheTests.cs +++ b/EFCache.Redis.Tests/RedisCacheTests.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using EFCache.Redis.Tests.Annotations; using Microsoft.VisualStudio.TestTools.UnitTesting; using StackExchange.Redis; @@ -63,14 +64,16 @@ public void Item_not_returned_after_absolute_expiration_expired() var cache = new RedisCache(RegularConnectionString); var item = new TestObject { Message = "OK" }; - cache.PutItem("key", item, new string[0], TimeSpan.MaxValue, DateTimeOffset.Now.AddMinutes(-10)); + cache.PutItem("key", item, new string[0], TimeSpan.MaxValue, DateTimeOffset.Now.AddSeconds(1)); + + Thread.Sleep(1000); Assert.IsFalse(cache.GetItem("key", out var fromCache)); Assert.IsNull(fromCache); } [TestMethod] - public void Item_not_returned_after_sliding_expiration_expired() + public void Item_not_returned_when_slidingexpiration_has_passed() { var cache = new RedisCache(RegularConnectionString); var item = new TestObject { Message = "OK" }; @@ -96,7 +99,7 @@ public void Item_still_returned_after_sliding_expiration_period() { Thread.Sleep(5000); // Wait 5 seconds // Retrieve item again. This should update LastAccess and as such keep the item 'alive' - // Break when item cannot be retrieved + // Throw if item cannot be retrieved Assert.IsTrue(cache.GetItem("key", out fromCache)); } Assert.IsNotNull(fromCache); @@ -149,6 +152,25 @@ public void Count_returns_numers_of_cached_entries() Assert.AreEqual(0, cache.Count); } + + [TestMethod] + public void Count_does_not_return_expired_entries() + { + var cache = new RedisCache(AdminConnectionString); + + cache.Purge(); + + Assert.AreEqual(0, cache.Count); + + cache.PutItem("1", new object(), new string[0], TimeSpan.MaxValue, DateTimeOffset.Now.AddSeconds(1)); + + Assert.AreEqual(1, cache.Count); + + Thread.Sleep(1000); + + Assert.AreEqual(0, cache.Count); + } + [TestMethod] public void Purge_removes_stale_items_from_cache() { @@ -156,11 +178,13 @@ public void Purge_removes_stale_items_from_cache() cache.Purge(); - cache.PutItem("1", new object(), new[] { "ES1", "ES2" }, TimeSpan.MaxValue, DateTimeOffset.Now.AddMinutes(-1)); + cache.PutItem("1", new object(), new[] { "ES1", "ES2" }, TimeSpan.MaxValue, DateTimeOffset.Now.AddSeconds(1)); cache.PutItem("2", new object(), new[] { "ES1", "ES2" }, TimeSpan.MaxValue, DateTimeOffset.MaxValue); Assert.AreEqual(4, cache.Count); // "1", "2", "ES1", "ES2" + Thread.Sleep(1000); + cache.Purge(); Assert.AreEqual(0, cache.Count); diff --git a/EFCache.Redis.Tests/StackExchangeExtensionsTests.cs b/EFCache.Redis.Tests/StackExchangeExtensionsTests.cs index 6d7b4c1..f7cf9f5 100644 --- a/EFCache.Redis.Tests/StackExchangeExtensionsTests.cs +++ b/EFCache.Redis.Tests/StackExchangeExtensionsTests.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using StackExchange.Redis; @@ -11,7 +12,7 @@ public class StackExchangeExtensionsTests public void Set_should_not_throw_when_caching_null_object() { var cache = Mock.Of(); - cache.Set("key", (object)null); + cache.Set("key", (object)null, TimeSpan.FromMinutes(1)); } } } diff --git a/EFCache.Redis/RedisCache.cs b/EFCache.Redis/RedisCache.cs index d01a40d..e3b278e 100644 --- a/EFCache.Redis/RedisCache.cs +++ b/EFCache.Redis/RedisCache.cs @@ -106,7 +106,7 @@ public bool GetItem(string key, out object value) { try { - _database.Set(key, entry); + _database.Set(key, entry, GetTimeSpanExpiration(entry.AbsoluteExpiration)); } catch (Exception e) { @@ -144,22 +144,27 @@ public void PutItem(string key, object value, IEnumerable dependentEntit lock (_lock) { - try + try { foreach (var entitySet in entitySets) { _database.SetAdd(AddCacheQualifier(entitySet), key, CommandFlags.FireAndForget); } - _database.Set(key, new CacheEntry(value, entitySets, slidingExpiration, absoluteExpiration)); - } - catch (Exception e) + _database.Set(key, new CacheEntry(value, entitySets, slidingExpiration, absoluteExpiration), GetTimeSpanExpiration(absoluteExpiration)); + } + catch (Exception e) { OnCachingFailed(e); } } } + private TimeSpan GetTimeSpanExpiration(DateTimeOffset expiration) + { + return TimeSpan.FromTicks(expiration.UtcTicks - DateTime.UtcNow.Ticks); + } + private RedisKey AddCacheQualifier(string entitySet) => string.Concat(_cacheIdentifier, ".", entitySet); private static string HashKey(string key) diff --git a/EFCache.Redis/StackExchangeRedisExtensions.cs b/EFCache.Redis/StackExchangeRedisExtensions.cs index 04d384a..3c4a105 100644 --- a/EFCache.Redis/StackExchangeRedisExtensions.cs +++ b/EFCache.Redis/StackExchangeRedisExtensions.cs @@ -1,4 +1,5 @@ -using StackExchange.Redis; +using System; +using StackExchange.Redis; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; @@ -13,9 +14,9 @@ public static T Get(this IDatabase cache, string key) return Deserialize(item); } - public static void Set(this IDatabase cache, string key, T value) where T : class + public static void Set(this IDatabase cache, string key, T value, TimeSpan expiry) where T : class { - cache.StringSet(key, Serialize(value)); + cache.StringSet(key, Serialize(value), expiry); } static byte[] Serialize(T o) where T : class From d32bdcb583c3f5dce5858da961e2fc29ff576e4a Mon Sep 17 00:00:00 2001 From: Ian Robertson Date: Mon, 6 Jan 2020 12:27:06 +0000 Subject: [PATCH 4/4] Build release - add support for dot net framework 4.8 --- EFCache.Redis/App.config | 2 +- EFCache.Redis/EFCache.Redis.csproj | 5 ++++- EFCache.Redis/EFCache.Redis.nuspec | 3 +++ EFCache.Redis/Properties/AssemblyInfo.cs | 6 +++--- EFCache.Redis/packages.config | 26 ++++++++++++------------ 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/EFCache.Redis/App.config b/EFCache.Redis/App.config index e735df9..8465434 100644 --- a/EFCache.Redis/App.config +++ b/EFCache.Redis/App.config @@ -14,7 +14,7 @@ - + diff --git a/EFCache.Redis/EFCache.Redis.csproj b/EFCache.Redis/EFCache.Redis.csproj index e0234e5..0948116 100644 --- a/EFCache.Redis/EFCache.Redis.csproj +++ b/EFCache.Redis/EFCache.Redis.csproj @@ -9,7 +9,7 @@ Properties EFCache.Redis EFCache.Redis - v4.7.2 + v4.8 512 ..\ true @@ -32,6 +32,9 @@ prompt 4 + + true + ..\packages\EntityFramework.Cache.1.1.3\lib\net45\EFCache.dll diff --git a/EFCache.Redis/EFCache.Redis.nuspec b/EFCache.Redis/EFCache.Redis.nuspec index ebe6d2f..1f91280 100644 --- a/EFCache.Redis/EFCache.Redis.nuspec +++ b/EFCache.Redis/EFCache.Redis.nuspec @@ -17,6 +17,7 @@ + @@ -32,5 +33,7 @@ + + \ No newline at end of file diff --git a/EFCache.Redis/Properties/AssemblyInfo.cs b/EFCache.Redis/Properties/AssemblyInfo.cs index c093cf2..990c390 100644 --- a/EFCache.Redis/Properties/AssemblyInfo.cs +++ b/EFCache.Redis/Properties/AssemblyInfo.cs @@ -11,7 +11,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("EFCache.Redis")] -[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("Ian Robertson")] [assembly: AssemblyCulture("")] //[assembly: log4net.Config.XmlConfigurator(Watch = true)] @@ -31,6 +31,6 @@ // Build Number // Revision // -[assembly: AssemblyVersion("2019.3.11.1")] -[assembly: AssemblyFileVersion("2019.3.11.1")] +[assembly: AssemblyVersion("2020.1.6.1")] +[assembly: AssemblyFileVersion("2020.1.6.1")] diff --git a/EFCache.Redis/packages.config b/EFCache.Redis/packages.config index e192646..92cf61a 100644 --- a/EFCache.Redis/packages.config +++ b/EFCache.Redis/packages.config @@ -1,16 +1,16 @@  - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file