diff --git a/AsyncGenerator.yml b/AsyncGenerator.yml
index 9b858a37..9489bf4e 100644
--- a/AsyncGenerator.yml
+++ b/AsyncGenerator.yml
@@ -1,4 +1,33 @@
projects:
+- filePath: CoreDistributedCache\NHibernate.Caches.CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.csproj
+ targetFramework: net461
+ concurrentRun: true
+ applyChanges: true
+ analyzation:
+ methodConversion:
+ - conversion: Ignore
+ hasAttributeName: ObsoleteAttribute
+ callForwarding: true
+ cancellationTokens:
+ guards: true
+ methodParameter:
+ - anyInterfaceRule: PubliclyExposedType
+ parameter: Optional
+ - parameter: Optional
+ rule: PubliclyExposedType
+ - parameter: Required
+ scanMethodBody: true
+ scanForMissingAsyncMembers:
+ - all: true
+ transformation:
+ configureAwaitArgument: false
+ localFunctions: true
+ asyncLock:
+ type: NHibernate.Util.AsyncLock
+ methodName: LockAsync
+ registerPlugin:
+ - type: AsyncGenerator.Core.Plugins.EmptyRegionRemover
+ assemblyName: AsyncGenerator.Core
- filePath: CoreMemoryCache\NHibernate.Caches.CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.csproj
targetFramework: net461
concurrentRun: true
@@ -293,6 +322,47 @@
registerPlugin:
- type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder
assemblyName: AsyncGenerator.Core
+- filePath: CoreDistributedCache\NHibernate.Caches.CoreDistributedCache.Tests\NHibernate.Caches.CoreDistributedCache.Tests.csproj
+ targetFramework: net461
+ concurrentRun: true
+ applyChanges: true
+ analyzation:
+ methodConversion:
+ - conversion: Ignore
+ hasAttributeName: OneTimeSetUpAttribute
+ - conversion: Ignore
+ hasAttributeName: OneTimeTearDownAttribute
+ - conversion: Ignore
+ hasAttributeName: SetUpAttribute
+ - conversion: Ignore
+ hasAttributeName: TearDownAttribute
+ - conversion: Smart
+ hasAttributeName: TestAttribute
+ - conversion: Smart
+ hasAttributeName: TheoryAttribute
+ preserveReturnType:
+ - hasAttributeName: TestAttribute
+ - hasAttributeName: TheoryAttribute
+ typeConversion:
+ - conversion: Ignore
+ hasAttributeName: IgnoreAttribute
+ - conversion: Partial
+ hasAttributeName: TestFixtureAttribute
+ - conversion: Partial
+ anyBaseTypeRule: HasTestFixtureAttribute
+ ignoreSearchForMethodReferences:
+ - hasAttributeName: TheoryAttribute
+ - hasAttributeName: TestAttribute
+ cancellationTokens:
+ withoutCancellationToken:
+ - hasAttributeName: TestAttribute
+ - hasAttributeName: TheoryAttribute
+ scanMethodBody: true
+ scanForMissingAsyncMembers:
+ - all: true
+ registerPlugin:
+ - type: AsyncGenerator.Core.Plugins.NUnitAsyncCounterpartsFinder
+ assemblyName: AsyncGenerator.Core
- filePath: CoreMemoryCache\NHibernate.Caches.CoreMemoryCache.Tests\NHibernate.Caches.CoreMemoryCache.Tests.csproj
targetFramework: net461
concurrentRun: true
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs
new file mode 100644
index 00000000..bd9d30e7
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System;
+using System.Reflection;
+
+[assembly: CLSCompliant(true)]
+[assembly: AssemblyDelaySign(false)]
\ No newline at end of file
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs
new file mode 100644
index 00000000..0d18e98f
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/MemcachedFactory.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using Enyim.Caching;
+using Enyim.Caching.Configuration;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+
+namespace NHibernate.Caches.CoreDistributedCache.Memcached
+{
+ ///
+ /// A Memcached distributed cache factory.
+ ///
+ public class MemcachedFactory : IDistributedCacheFactory
+ {
+ private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemcachedFactory));
+ private const string _configuration = "configuration";
+
+ private readonly IDistributedCache _cache;
+
+ ///
+ /// Constructor with configuration properties. It supports configuration, which has to be a JSON string
+ /// structured like the value part of the "enyimMemcached" property in an appsettings.json file.
+ ///
+ /// The configurations properties.
+ public MemcachedFactory(IDictionary properties) : this()
+ {
+ MemcachedClientOptions options;
+ if (properties != null && properties.TryGetValue(_configuration, out var configuration) && !string.IsNullOrWhiteSpace(configuration))
+ {
+ options = JsonConvert.DeserializeObject(configuration);
+ }
+ else
+ {
+ Log.Warn("No {0} property provided", _configuration);
+ options = new MemcachedClientOptions();
+ }
+
+ var loggerFactory = new LoggerFactory();
+
+ _cache = new MemcachedClient(loggerFactory, new MemcachedClientConfiguration(loggerFactory, options));
+ }
+
+ private MemcachedFactory()
+ {
+ Constraints = new CacheConstraints
+ {
+ MaxKeySize = 250,
+ KeySanitizer = SanitizeKey
+ };
+ }
+
+ // According to https://groups.google.com/forum/#!topic/memcached/Tz1RE0FUbNA,
+ // memcached key can't contain space, newline, return, tab, vertical tab or form feed.
+ // Since keys contains entity identifiers which may be anything, purging them all.
+ private static readonly char[] ForbiddenChar = new [] { ' ', '\n', '\r', '\t', '\v', '\f' };
+
+ private static string SanitizeKey(string key)
+ {
+ foreach (var forbidden in ForbiddenChar)
+ {
+ key = key.Replace(forbidden, '-');
+ }
+ return key;
+ }
+
+ ///
+ public CacheConstraints Constraints { get; }
+
+ ///
+ [CLSCompliant(false)]
+ public IDistributedCache BuildCache()
+ {
+ return _cache;
+ }
+
+ private class LoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory
+ {
+ public void Dispose()
+ {
+ }
+
+ public ILogger CreateLogger(string categoryName)
+ {
+ return new LoggerWrapper(NHibernateLogger.For(categoryName));
+ }
+
+ public void AddProvider(ILoggerProvider provider)
+ {
+ }
+ }
+
+ private class LoggerWrapper : ILogger
+ {
+ private readonly INHibernateLogger _logger;
+
+ public LoggerWrapper(INHibernateLogger logger)
+ {
+ _logger = logger;
+ }
+
+ void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception exception,
+ Func formatter)
+ {
+ if (!IsEnabled(logLevel))
+ return;
+
+ if (formatter == null)
+ throw new ArgumentNullException(nameof(formatter));
+
+ _logger.Log(
+ TranslateLevel(logLevel),
+ new NHibernateLogValues("EventId {0}: {1}", new object[] { eventId, formatter(state, exception) }),
+ // Avoid double logging of exception by not providing it to the logger, but only to the formatter.
+ null);
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ => _logger.IsEnabled(TranslateLevel(logLevel));
+
+ public IDisposable BeginScope(TState state)
+ => NoopScope.Instance;
+
+ private NHibernateLogLevel TranslateLevel(LogLevel level)
+ {
+ switch (level)
+ {
+ case LogLevel.None:
+ return NHibernateLogLevel.None;
+ case LogLevel.Trace:
+ return NHibernateLogLevel.Trace;
+ case LogLevel.Debug:
+ return NHibernateLogLevel.Debug;
+ case LogLevel.Information:
+ return NHibernateLogLevel.Info;
+ case LogLevel.Warning:
+ return NHibernateLogLevel.Warn;
+ case LogLevel.Error:
+ return NHibernateLogLevel.Error;
+ case LogLevel.Critical:
+ return NHibernateLogLevel.Fatal;
+ }
+
+ return NHibernateLogLevel.Trace;
+ }
+
+ private class NoopScope : IDisposable
+ {
+ public static readonly NoopScope Instance = new NoopScope();
+
+ public void Dispose()
+ {
+ }
+ }
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj
new file mode 100644
index 00000000..aafefe85
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memcached/NHibernate.Caches.CoreDistributedCache.Memcached.csproj
@@ -0,0 +1,34 @@
+
+
+
+ NHibernate.Caches.CoreDistributedCache.Memcached
+ NHibernate.Caches.CoreDistributedCache.Memcached
+ Memcached cache provider for NHibernate using .Net Core IDistributedCache (EnyimMemcachedCore).
+
+ net461;netstandard2.0
+ False
+ true
+ * New feature
+ * #28 - Add a .Net Core DistributedCache
+
+
+ NETFX;$(DefineConstants)
+
+
+
+
+
+
+
+
+
+ ./NHibernate.Caches.readme.md
+
+
+ ./NHibernate.Caches.license.txt
+
+
+
+
+
+
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs
new file mode 100644
index 00000000..bd9d30e7
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System;
+using System.Reflection;
+
+[assembly: CLSCompliant(true)]
+[assembly: AssemblyDelaySign(false)]
\ No newline at end of file
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs
new file mode 100644
index 00000000..65937ce2
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/MemoryFactory.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+
+namespace NHibernate.Caches.CoreDistributedCache.Memory
+{
+ ///
+ /// A memory "distributed" cache factory. Use for testing purpose. Otherwise consider using CoreMemoryCache
+ /// instead.
+ ///
+ public class MemoryFactory : IDistributedCacheFactory
+ {
+ private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(MemoryFactory));
+ private const string _expirationScanFrequency = "expiration-scan-frequency";
+ private const string _sizeLimit = "size-limit";
+
+ private readonly IDistributedCache _cache;
+
+ ///
+ /// Default constructor.
+ ///
+ public MemoryFactory() : this(null)
+ {
+ }
+
+ ///
+ /// Constructor with explicit configuration properties.
+ ///
+ /// See .
+ /// See .
+ public MemoryFactory(TimeSpan? expirationScanFrequency, long? sizeLimit)
+ {
+ var options = new Options();
+ if (expirationScanFrequency.HasValue)
+ options.ExpirationScanFrequency = expirationScanFrequency.Value;
+ if (sizeLimit.HasValue)
+ options.SizeLimit = sizeLimit.Value;
+
+ _cache = new MemoryDistributedCache(options);
+ }
+
+ ///
+ /// Constructor with configuration properties. It supports expiration-scan-frequency and
+ /// size-limit properties. See and
+ /// .
+ ///
+ /// The configurations properties.
+ ///
+ ///
+ /// If expiration-scan-frequency is provided as an integer, this integer will be used as a number
+ /// of minutes. Otherwise the setting will be parsed as a .
+ ///
+ /// size-limit has to be an integer, expressing the size limit in bytes.
+ ///
+ public MemoryFactory(IDictionary properties)
+ {
+ var options = new Options();
+
+ if (properties != null)
+ {
+ if (properties.TryGetValue(_expirationScanFrequency, out var esf))
+ {
+ if (esf != null)
+ {
+ if (int.TryParse(esf, out var minutes))
+ options.ExpirationScanFrequency = TimeSpan.FromMinutes(minutes);
+ else if (TimeSpan.TryParse(esf, out var expirationScanFrequency))
+ options.ExpirationScanFrequency = expirationScanFrequency;
+ else
+ Log.Warn(
+ "Invalid value '{0}' for {1} setting: it is neither an int nor a TimeSpan. Ignoring.",
+ esf, _expirationScanFrequency);
+ }
+ else
+ {
+ Log.Warn("Invalid property {0}: it lacks a value. Ignoring.", _expirationScanFrequency);
+ }
+ }
+
+ if (properties.TryGetValue(_sizeLimit, out var sl))
+ {
+ if (sl != null)
+ {
+ if (long.TryParse(sl, out var bytes))
+ options.SizeLimit = bytes;
+ else
+ Log.Warn(
+ "Invalid value '{0}' for {1} setting: it is not an integer. Ignoring.",
+ sl, _sizeLimit);
+ }
+ else
+ {
+ Log.Warn("Invalid property {0}: it lacks a value. Ignoring.", _sizeLimit);
+ }
+ }
+ }
+
+ _cache = new MemoryDistributedCache(options);
+ }
+
+ ///
+ [CLSCompliant(false)]
+ public IDistributedCache BuildCache()
+ {
+ // Always yields the same instance: its underlying implementation is a MemoryCache which regularly spawn
+ // a background task for expiring items. This avoids creating many instances, thus avoiding potentially
+ // spawning many such background tasks at once.
+ // This also allows to share the cache between all session factories of a process, thus emulating a
+ // distributed aspect.
+ return _cache;
+ }
+
+ ///
+ public CacheConstraints Constraints => null;
+
+ private class Options : MemoryDistributedCacheOptions, IOptions
+ {
+ MemoryDistributedCacheOptions IOptions.Value => this;
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj
new file mode 100644
index 00000000..c9698ecc
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Memory/NHibernate.Caches.CoreDistributedCache.Memory.csproj
@@ -0,0 +1,36 @@
+
+
+
+ NHibernate.Caches.CoreDistributedCache.Memory
+ NHibernate.Caches.CoreDistributedCache.Memory
+ Memory cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Redis).
+Meant for testing purpose, consider NHibernate.Caches.CoreMemoryCache for other usages.
+
+ net461;netstandard2.0
+ True
+ ..\..\NHibernate.Caches.snk
+ true
+ * New feature
+ * #28 - Add a .Net Core DistributedCache
+
+
+ NETFX;$(DefineConstants)
+
+
+
+
+
+
+
+
+
+ ./NHibernate.Caches.readme.md
+
+
+ ./NHibernate.Caches.license.txt
+
+
+
+
+
+
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs
new file mode 100644
index 00000000..bd9d30e7
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System;
+using System.Reflection;
+
+[assembly: CLSCompliant(true)]
+[assembly: AssemblyDelaySign(false)]
\ No newline at end of file
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj
new file mode 100644
index 00000000..3d945aac
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/NHibernate.Caches.CoreDistributedCache.Redis.csproj
@@ -0,0 +1,35 @@
+
+
+
+ NHibernate.Caches.CoreDistributedCache.Redis
+ NHibernate.Caches.CoreDistributedCache.Redis
+ Redis cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Redis).
+
+ net461;netstandard2.0
+ True
+ ..\..\NHibernate.Caches.snk
+ true
+ * New feature
+ * #28 - Add a .Net Core DistributedCache
+
+
+ NETFX;$(DefineConstants)
+
+
+
+
+
+
+
+
+
+ ./NHibernate.Caches.readme.md
+
+
+ ./NHibernate.Caches.license.txt
+
+
+
+
+
+
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs
new file mode 100644
index 00000000..38418c4c
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Redis/RedisFactory.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Redis;
+
+namespace NHibernate.Caches.CoreDistributedCache.Redis
+{
+ ///
+ /// A Redis distributed cache factory. See .
+ ///
+ public class RedisFactory : IDistributedCacheFactory
+ {
+ private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(RedisFactory));
+ private const string _configuration = "configuration";
+ private const string _instanceName = "instance-name";
+
+ private readonly RedisCacheOptions _options;
+
+ ///
+ /// Constructor with explicit configuration properties.
+ ///
+ /// See .
+ /// See .
+ public RedisFactory(string configuration, string instanceName)
+ {
+ _options = new RedisCacheOptions
+ {
+ Configuration = configuration,
+ InstanceName = instanceName
+ };
+ }
+
+ ///
+ /// Constructor with configuration properties. It supports configuration and
+ /// instance-name properties. See and
+ /// .
+ ///
+ /// The configurations properties.
+ public RedisFactory(IDictionary properties)
+ {
+ _options = new RedisCacheOptions();
+
+ if (properties == null)
+ return;
+
+ if (properties.TryGetValue(_configuration, out var configuration))
+ {
+ _options.Configuration = configuration;
+ Log.Info("Configuration set as '{0}'", configuration);
+ }
+ else
+ {
+ // Configuration is supposed to be mandatory.
+ Log.Warn("No {0} property provided", _configuration);
+ }
+
+ if (properties.TryGetValue(_instanceName, out var instanceName))
+ {
+ _options.InstanceName = instanceName;
+ Log.Info("InstanceName set as '{0}'", instanceName);
+ }
+ else
+ Log.Info("No {0} property provided", _instanceName);
+ }
+
+ ///
+ public CacheConstraints Constraints => null;
+
+ ///
+ [CLSCompliant(false)]
+ public IDistributedCache BuildCache()
+ {
+ // According to https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed#the-idistributedcache-interface
+ // (see its paragraph end note) there is no need for a singleton lifetime.
+ return new RedisCache(_options);
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs
new file mode 100644
index 00000000..bd9d30e7
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System;
+using System.Reflection;
+
+[assembly: CLSCompliant(true)]
+[assembly: AssemblyDelaySign(false)]
\ No newline at end of file
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj
new file mode 100644
index 00000000..4af24545
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/NHibernate.Caches.CoreDistributedCache.SqlServer.csproj
@@ -0,0 +1,35 @@
+
+
+
+ NHibernate.Caches.CoreDistributedCache.SqlServer
+ NHibernate.Caches.CoreDistributedCache.SqlServer
+ SQL Server cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.SqlServer).
+
+ net461;netstandard2.0
+ True
+ ..\..\NHibernate.Caches.snk
+ true
+ * New feature
+ * #28 - Add a .Net Core DistributedCache
+
+
+ NETFX;$(DefineConstants)
+
+
+
+
+
+
+
+
+
+ ./NHibernate.Caches.readme.md
+
+
+ ./NHibernate.Caches.license.txt
+
+
+
+
+
+
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs
new file mode 100644
index 00000000..1d688247
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.SqlServer/SqlServerFactory.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.SqlServer;
+
+namespace NHibernate.Caches.CoreDistributedCache.SqlServer
+{
+ ///
+ /// A Redis distributed cache factory. See .
+ ///
+ public class SqlServerFactory : IDistributedCacheFactory
+ {
+ private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(SqlServerFactory));
+ private const string _connectionString = "connection-string";
+ private const string _schemaName = "schema-name";
+ private const string _tableName = "table-name";
+ private const string _expiredItemsDeletionInterval = "expired-items-deletion-interval";
+
+ private readonly SqlServerCacheOptions _options;
+
+ ///
+ /// Constructor with explicit configuration properties.
+ ///
+ /// See .
+ /// See .
+ /// See .
+ /// See .
+ public SqlServerFactory(
+ string connectionString, string schemaName, string tableName, TimeSpan? expiredItemsDeletionInterval)
+ {
+ _options = new SqlServerCacheOptions
+ {
+ ConnectionString = connectionString,
+ SchemaName = schemaName,
+ TableName = tableName,
+ ExpiredItemsDeletionInterval = expiredItemsDeletionInterval
+ };
+ }
+
+ ///
+ /// Constructor with configuration properties. It supports connection-string, schema-name,
+ /// table-name and expired-items-deletion-interval properties.
+ /// See .
+ ///
+ /// The configurations properties.
+ ///
+ /// If expired-items-deletion-interval is provided as an integer, this integer will be used as a number
+ /// of minutes. Otherwise the setting will be parsed as a .
+ ///
+ public SqlServerFactory(IDictionary properties)
+ {
+ _options = new SqlServerCacheOptions();
+
+ if (properties == null)
+ return;
+
+ if (properties.TryGetValue(_connectionString, out var connectionString))
+ {
+ _options.ConnectionString = connectionString;
+ Log.Info("ConnectionString set as '{0}'", connectionString);
+ }
+ else
+ Log.Warn("No {0} property provided", _connectionString);
+
+ if (properties.TryGetValue(_schemaName, out var schemaName))
+ {
+ _options.SchemaName = schemaName;
+ Log.Info("SchemaName set as '{0}'", schemaName);
+ }
+ else
+ Log.Warn("No {0} property provided", _schemaName);
+
+ if (properties.TryGetValue(_tableName, out var tableName))
+ {
+ _options.TableName = tableName;
+ Log.Info("TableName set as '{0}'", tableName);
+ }
+ else
+ Log.Warn("No {0} property provided", _tableName);
+
+ if (properties.TryGetValue(_expiredItemsDeletionInterval, out var eidi))
+ {
+ if (eidi != null)
+ {
+ if (int.TryParse(eidi, out var minutes))
+ _options.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(minutes);
+ else if (TimeSpan.TryParse(eidi, out var expirationScanFrequency))
+ _options.ExpiredItemsDeletionInterval = expirationScanFrequency;
+ else
+ Log.Warn(
+ "Invalid value '{0}' for {1} setting: it is neither an int nor a TimeSpan. Ignoring.",
+ eidi, _expiredItemsDeletionInterval);
+ }
+ else
+ {
+ Log.Warn("Invalid property {0}: it lacks a value. Ignoring.", _expiredItemsDeletionInterval);
+ }
+ }
+ }
+
+ ///
+ public CacheConstraints Constraints { get; } = new CacheConstraints { MaxKeySize = 449 };
+
+ ///
+ [CLSCompliant(false)]
+ public IDistributedCache BuildCache()
+ {
+ // According to https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed#the-idistributedcache-interface
+ // (see its paragraph end note) there is no need for a singleton lifetime.
+ return new SqlServerCache(_options);
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config
new file mode 100644
index 00000000..d51086a8
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/App.config
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+ 00:10:00
+ 1048576
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NHibernate.Connection.DriverConnectionProvider
+ NHibernate.Dialect.MsSql2005Dialect
+ NHibernate.Driver.SqlClientDriver
+
+ Server=localhost;initial catalog=nhibernate;Integrated Security=SSPI
+
+ NHibernate.Caches.CoreDistributedCache.CoreDistributedCacheProvider,NHibernate.Caches.CoreDistributedCache
+
+
+
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs
new file mode 100644
index 00000000..0da5ea55
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Async/CoreDistributedCacheFixture.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+#region License
+
+//
+// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Distributed;
+using NHibernate.Cache;
+using NHibernate.Caches.Common.Tests;
+using NUnit.Framework;
+using NSubstitute;
+
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ using System.Threading.Tasks;
+ using System.Threading;
+ public partial class CoreDistributedCacheFixture : CacheFixture
+ {
+
+ [Test]
+ public async Task MaxKeySizeAsync()
+ {
+ var distribCache = Substitute.For();
+ const int maxLength = 20;
+ var cache = new CoreDistributedCache(distribCache, new CacheConstraints { MaxKeySize = maxLength }, "foo",
+ new Dictionary());
+ await (cache.PutAsync(new string('k', maxLength * 2), "test", CancellationToken.None));
+ await (distribCache.Received().SetAsync(Arg.Is(k => k.Length <= maxLength), Arg.Any(),
+ Arg.Any()));
+ }
+
+ [Test]
+ public async Task KeySanitizerAsync()
+ {
+ var distribCache = Substitute.For();
+ Func keySanitizer = s => s.Replace('a', 'b');
+ var cache = new CoreDistributedCache(distribCache, new CacheConstraints { KeySanitizer = keySanitizer }, "foo",
+ new Dictionary());
+ await (cache.PutAsync("-abc-", "test", CancellationToken.None));
+ await (distribCache.Received().SetAsync(Arg.Is(k => k.Contains(keySanitizer("-abc-"))), Arg.Any(),
+ Arg.Any()));
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs
new file mode 100644
index 00000000..0ba6ec7e
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheFixture.cs
@@ -0,0 +1,66 @@
+#region License
+
+//
+// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Caching.Distributed;
+using NHibernate.Cache;
+using NHibernate.Caches.Common.Tests;
+using NUnit.Framework;
+using NSubstitute;
+
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ [TestFixture]
+ public partial class CoreDistributedCacheFixture : CacheFixture
+ {
+ protected override bool SupportsSlidingExpiration => true;
+ protected override bool SupportsClear => false;
+
+ protected override Func ProviderBuilder =>
+ () => new CoreDistributedCacheProvider();
+
+ [Test]
+ public void MaxKeySize()
+ {
+ var distribCache = Substitute.For();
+ const int maxLength = 20;
+ var cache = new CoreDistributedCache(distribCache, new CacheConstraints { MaxKeySize = maxLength }, "foo",
+ new Dictionary());
+ cache.Put(new string('k', maxLength * 2), "test");
+ distribCache.Received().Set(Arg.Is(k => k.Length <= maxLength), Arg.Any(),
+ Arg.Any());
+ }
+
+ [Test]
+ public void KeySanitizer()
+ {
+ var distribCache = Substitute.For();
+ Func keySanitizer = s => s.Replace('a', 'b');
+ var cache = new CoreDistributedCache(distribCache, new CacheConstraints { KeySanitizer = keySanitizer }, "foo",
+ new Dictionary());
+ cache.Put("-abc-", "test");
+ distribCache.Received().Set(Arg.Is(k => k.Contains(keySanitizer("-abc-"))), Arg.Any(),
+ Arg.Any());
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs
new file mode 100644
index 00000000..391336e7
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheProviderFixture.cs
@@ -0,0 +1,68 @@
+#region License
+
+//
+// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using NHibernate.Cache;
+using NHibernate.Caches.Common.Tests;
+using NUnit.Framework;
+
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ [TestFixture]
+ public class CoreDistributedCacheProviderFixture : CacheProviderFixture
+ {
+ protected override Func ProviderBuilder =>
+ () => new CoreDistributedCacheProvider();
+
+ [Test]
+ public void TestBuildCacheFromConfig()
+ {
+ var cache = DefaultProvider.BuildCache("foo", null);
+ Assert.That(cache, Is.Not.Null, "pre-configured cache not found");
+ }
+
+ [Test]
+ public void TestExpiration()
+ {
+ var cache = DefaultProvider.BuildCache("foo", null) as CoreDistributedCache;
+ Assert.That(cache, Is.Not.Null, "pre-configured foo cache not found");
+ Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(500)), "Unexpected expiration value for foo region");
+
+ cache = (CoreDistributedCache) DefaultProvider.BuildCache("noExplicitExpiration", null);
+ Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(300)),
+ "Unexpected expiration value for noExplicitExpiration region");
+ Assert.That(cache.UseSlidingExpiration, Is.True, "Unexpected sliding value for noExplicitExpiration region");
+
+ cache = (CoreDistributedCache) DefaultProvider
+ .BuildCache("noExplicitExpiration", new Dictionary { { "expiration", "100" } });
+ Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(100)),
+ "Unexpected expiration value for noExplicitExpiration region with default expiration");
+
+ cache = (CoreDistributedCache) DefaultProvider
+ .BuildCache("noExplicitExpiration",
+ new Dictionary { { Cfg.Environment.CacheDefaultExpiration, "50" } });
+ Assert.That(cache.Expiration, Is.EqualTo(TimeSpan.FromSeconds(50)),
+ "Unexpected expiration value for noExplicitExpiration region with cache.default_expiration");
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs
new file mode 100644
index 00000000..4694ead1
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/CoreDistributedCacheSectionHandlerFixture.cs
@@ -0,0 +1,81 @@
+#region License
+
+//
+// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+
+#endregion
+
+using System.Xml;
+using NUnit.Framework;
+
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ [TestFixture]
+ public class CoreDistributedCacheSectionHandlerFixture
+ {
+ private static XmlNode GetConfigurationSection(string xml)
+ {
+ var doc = new XmlDocument();
+ doc.LoadXml(xml);
+ return doc.DocumentElement;
+ }
+
+ [Test]
+ public void TestGetConfigNullSection()
+ {
+ var handler = new CoreDistributedCacheSectionHandler();
+ var section = new XmlDocument();
+ var result = handler.Create(null, null, section);
+ Assert.That(result, Is.Not.Null, "result");
+ Assert.That(result, Is.InstanceOf());
+ var config = (CacheConfig) result;
+ Assert.That(config.Properties, Is.Not.Null, "Properties");
+ Assert.That(config.Properties.Count, Is.EqualTo(0), "Properties count");
+ Assert.That(config.Regions, Is.Not.Null, "Regions");
+ Assert.That(config.Regions.Length, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void TestGetConfigFromFile()
+ {
+ const string xmlSimple = "Value1";
+
+ var handler = new CoreDistributedCacheSectionHandler();
+ var section = GetConfigurationSection(xmlSimple);
+ var result = handler.Create(null, null, section);
+ Assert.That(result, Is.Not.Null, "result");
+ Assert.That(result, Is.InstanceOf());
+ var config = (CacheConfig) result;
+
+ Assert.That(config.FactoryClass, Is.EqualTo("factory1"));
+
+ Assert.That(config.Properties, Is.Not.Null, "Properties");
+ Assert.That(config.Properties.Count, Is.EqualTo(1), "Properties count");
+ Assert.That(config.Properties, Does.ContainKey("prop1"));
+ Assert.That(config.Properties["prop1"], Is.EqualTo("Value1"));
+
+ Assert.That(config.Regions, Is.Not.Null, "Regions");
+ Assert.That(config.Regions.Length, Is.EqualTo(1), "Regions count");
+ Assert.That(config.Regions[0].Region, Is.EqualTo("foo"));
+ Assert.That(config.Regions[0].Properties, Does.ContainKey("cache.use_sliding_expiration"));
+ Assert.That(config.Regions[0].Properties["cache.use_sliding_expiration"], Is.EqualTo("true"));
+ Assert.That(config.Regions[0].Properties, Does.ContainKey("expiration"));
+ Assert.That(config.Regions[0].Properties["expiration"], Is.EqualTo("500"));
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs
new file mode 100644
index 00000000..adfcecf0
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/DistributedCacheFactoryFixture.cs
@@ -0,0 +1,171 @@
+#region License
+
+//
+// CoreDistributedCache - A cache provider for NHibernate using Microsoft.Extensions.Caching.Distributed.IDistributedCache.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Enyim.Caching;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Caching.Redis;
+using Microsoft.Extensions.Caching.SqlServer;
+using NHibernate.Caches.CoreDistributedCache.Memcached;
+using NHibernate.Caches.CoreDistributedCache.Memory;
+using NHibernate.Caches.CoreDistributedCache.Redis;
+using NHibernate.Caches.CoreDistributedCache.SqlServer;
+using NUnit.Framework;
+
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ [TestFixture]
+ public class DistributedCacheFactoryFixture
+ {
+ [Test]
+ public void MemcachedCacheFactory()
+ {
+ var factory =
+ new MemcachedFactory(new Dictionary
+ {
+ {
+ "configuration", @"{
+ ""Servers"": [
+ {
+ ""Address"": ""memcached"",
+ ""Port"": 11211
+ }
+ ],
+ ""Authentication"": {
+ ""Type"": ""Enyim.Caching.Memcached.PlainTextAuthenticator"",
+ ""Parameters"": {
+ ""zone"": """",
+ ""userName"": ""username"",
+ ""password"": ""password""
+ }
+ }
+}"
+ }
+ });
+ var cache1 = factory.BuildCache();
+ Assert.That(cache1, Is.Not.Null, "Factory has yielded null");
+ Assert.That(cache1, Is.InstanceOf(), "Unexpected cache");
+ var cache2 = factory.BuildCache();
+ Assert.That(cache2, Is.EqualTo(cache1),
+ "The Memcached cache factory is supposed to always yield the same instance");
+
+ var keySanitizer = factory.Constraints?.KeySanitizer;
+ Assert.That(keySanitizer, Is.Not.Null, "Factory lacks a key sanitizer");
+ Assert.That(keySanitizer("--abc \n\r\t\v\fdef--"), Is.EqualTo("--abc------def--"), "Unexpected key sanitization");
+ }
+
+ private static readonly FieldInfo MemoryCacheField =
+ typeof(MemoryDistributedCache).GetField("_memCache", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ private static readonly FieldInfo MemoryCacheOptionsField =
+ typeof(MemoryCache).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ [Test]
+ public void MemoryCacheFactory()
+ {
+ var factory =
+ new MemoryFactory(new Dictionary
+ {
+ { "expiration-scan-frequency", "00:10:00" },
+ { "size-limit", "1048576" }
+ });
+ Assert.That(factory, Is.Not.Null, "Factory not found");
+ Assert.That(factory, Is.InstanceOf(), "Unexpected factory");
+ var cache1 = factory.BuildCache();
+ Assert.That(cache1, Is.Not.Null, "Factory has yielded null");
+ Assert.That(cache1, Is.InstanceOf(), "Unexpected cache");
+ var cache2 = factory.BuildCache();
+ Assert.That(cache2, Is.EqualTo(cache1),
+ "The distributed cache factory is supposed to always yield the same instance");
+
+ var memCache = MemoryCacheField.GetValue(cache1);
+ Assert.That(memCache, Is.Not.Null, "Underlying memory cache not found");
+ Assert.That(memCache, Is.InstanceOf(), "Unexpected memory cache");
+ var options = MemoryCacheOptionsField.GetValue(memCache);
+ Assert.That(options, Is.Not.Null, "Memory cache options not found");
+ Assert.That(options, Is.InstanceOf(), "Unexpected options type");
+ var memOptions = (MemoryCacheOptions) options;
+ Assert.That(memOptions.ExpirationScanFrequency, Is.EqualTo(TimeSpan.FromMinutes(10)));
+ Assert.That(memOptions.SizeLimit, Is.EqualTo(1048576));
+ }
+
+ private static readonly FieldInfo RedisCacheOptionsField =
+ typeof(RedisFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ [Test]
+ public void RedisCacheFactory()
+ {
+ var factory =
+ new RedisFactory(new Dictionary
+ {
+ { "configuration", "config" },
+ { "instance-name", "instance" }
+ });
+ var cache1 = factory.BuildCache();
+ Assert.That(cache1, Is.Not.Null, "Factory has yielded null");
+ Assert.That(cache1, Is.InstanceOf(), "Unexpected cache");
+ var cache2 = factory.BuildCache();
+ Assert.That(cache2, Is.Not.EqualTo(cache1),
+ "The Redis cache factory is supposed to always yield a new instance");
+
+ var options = RedisCacheOptionsField.GetValue(factory);
+ Assert.That(options, Is.Not.Null, "Factory cache options not found");
+ Assert.That(options, Is.InstanceOf(), "Unexpected options type");
+ var redisOptions = (RedisCacheOptions) options;
+ Assert.That(redisOptions.Configuration, Is.EqualTo("config"));
+ Assert.That(redisOptions.InstanceName, Is.EqualTo("instance"));
+ }
+
+ private static readonly FieldInfo SqlServerCacheOptionsField =
+ typeof(SqlServerFactory).GetField("_options", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ [Test]
+ public void SqlServerCacheFactory()
+ {
+ var factory = new SqlServerFactory(new Dictionary
+ {
+ { "connection-string", "connection" },
+ { "schema-name", "schema" },
+ { "table-name", "table" },
+ { "expired-items-deletion-interval", "5" }
+ });
+ var cache1 = factory.BuildCache();
+ Assert.That(cache1, Is.Not.Null, "Factory has yielded null");
+ Assert.That(cache1, Is.InstanceOf(), "Unexpected cache");
+ var cache2 = factory.BuildCache();
+ Assert.That(cache2, Is.Not.EqualTo(cache1),
+ "The SQL Server cache factory is supposed to always yield a new instance");
+
+ var options = SqlServerCacheOptionsField.GetValue(factory);
+ Assert.That(options, Is.Not.Null, "Factory cache options not found");
+ Assert.That(options, Is.InstanceOf(), "Unexpected options type");
+ var sqlServerOptions = (SqlServerCacheOptions) options;
+ Assert.That(sqlServerOptions.ConnectionString, Is.EqualTo("connection"));
+ Assert.That(sqlServerOptions.SchemaName, Is.EqualTo("schema"));
+ Assert.That(sqlServerOptions.TableName, Is.EqualTo("table"));
+ Assert.That(sqlServerOptions.ExpiredItemsDeletionInterval, Is.EqualTo(TimeSpan.FromMinutes(5)));
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj
new file mode 100644
index 00000000..fd88b603
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/NHibernate.Caches.CoreDistributedCache.Tests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ NHibernate.Caches.CoreDistributedCache
+ Unit tests of cache provider for NHibernate using .Net Core IDistributedCache (Microsoft.Extensions.Caching.Abstractions).
+ net461;netcoreapp2.0
+ true
+
+
+ NETFX;$(DefineConstants)
+
+
+ Exe
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs
new file mode 100644
index 00000000..5df1b669
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Program.cs
@@ -0,0 +1,12 @@
+#if !NETFX
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ return new NUnitLite.AutoRun(typeof(Program).Assembly).Execute(args);
+ }
+ }
+}
+#endif
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..345f41dd
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System;
+
+[assembly: CLSCompliant(false)]
\ No newline at end of file
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs
new file mode 100644
index 00000000..669478e6
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache.Tests/TestsContext.cs
@@ -0,0 +1,42 @@
+using log4net.Repository.Hierarchy;
+#if !NETFX
+using NHibernate.Caches.Common.Tests;
+#endif
+using NUnit.Framework;
+
+namespace NHibernate.Caches.CoreDistributedCache.Tests
+{
+ [SetUpFixture]
+ public class TestsContext
+ {
+ [OneTimeSetUp]
+ public void RunBeforeAnyTests()
+ {
+#if !NETFX
+ TestsContextHelper.RunBeforeAnyTests(typeof(TestsContext).Assembly, "coredistributedcache");
+#endif
+ ConfigureLog4Net();
+ }
+
+#if !NETFX
+ [OneTimeTearDown]
+ public void RunAfterAnyTests()
+ {
+ TestsContextHelper.RunAfterAnyTests();
+ }
+#endif
+
+ private static void ConfigureLog4Net()
+ {
+ var hierarchy = (Hierarchy)log4net.LogManager.GetRepository(typeof(TestsContext).Assembly);
+
+ var consoleAppender = new log4net.Appender.ConsoleAppender
+ {
+ Layout = new log4net.Layout.PatternLayout("%d{ABSOLUTE} %-5p %c{1}:%L - %m%n"),
+ };
+ hierarchy.Root.Level = log4net.Core.Level.Info;
+ hierarchy.Root.AddAppender(consoleAppender);
+ hierarchy.Configured = true;
+ }
+ }
+}
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs
new file mode 100644
index 00000000..bd9d30e7
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System;
+using System.Reflection;
+
+[assembly: CLSCompliant(true)]
+[assembly: AssemblyDelaySign(false)]
\ No newline at end of file
diff --git a/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs
new file mode 100644
index 00000000..13eb410a
--- /dev/null
+++ b/CoreDistributedCache/NHibernate.Caches.CoreDistributedCache/Async/CoreDistributedCache.cs
@@ -0,0 +1,174 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System;
+using NHibernate.Cache;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.Extensions.Caching.Distributed;
+using NHibernate.Util;
+
+namespace NHibernate.Caches.CoreDistributedCache
+{
+ using System.Threading.Tasks;
+ using System.Threading;
+ public partial class CoreDistributedCache : ICache
+ {
+
+ ///
+ public async Task