<NME> RedisDatabase.cs <BEF> ADDFILE <MSG> WIP (doesn't compile) <DFF> @@ -0,0 +1,1996 @@ +﻿using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using StackExchange.Redis; +using StackExchange.Redis.Extensions.Core.Abstractions; +using StackExchange.Redis.Extensions.Core.Configuration; +using StackExchange.Redis.Extensions.Core.ServerIteration; +using StackExchange.Redis.KeyspaceIsolation; + +namespace StackExchange.Redis.Extensions.Core.Implementations +{ + /// <summary> + /// The implementation of <see cref="ICacheClient" /> + /// </summary> + internal class RedisDatabase : IRedisDatabase + { + private readonly IConnectionMultiplexer connectionMultiplexer; + private readonly ServerEnumerationStrategy serverEnumerationStrategy = new ServerEnumerationStrategy(); + private readonly string keyprefix; + + /// <summary> + /// Initializes a new instance of the <see cref="StackExchangeRedisCacheClient" /> class. + /// </summary> + /// <param name="connectionMultiplexer">The connection multiplexer.</param> + /// <param name="serializer">The serializer.</param> + /// <param name="serverEnumerationStrategy">The strategy to use when executing server wide commands.</param> + /// <param name="database">The database.</param> + /// <param name="keyPrefix">Specifies the key separation prefix to be used for all keys</param> + /// <exception cref="System.ArgumentNullException"> + /// connectionMultiplexer + /// or + /// serializer + /// </exception> + public RedisDatabase( + IConnectionMultiplexer connectionMultiplexer, + ISerializer serializer, + ServerEnumerationStrategy serverEnumerationStrategy, + IDatabase database, + string keyPrefix = null) + { + this.serverEnumerationStrategy = serverEnumerationStrategy ?? new ServerEnumerationStrategy(); + this.Serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + this.connectionMultiplexer = connectionMultiplexer ?? throw new ArgumentNullException(nameof(connectionMultiplexer)); + + Database = database; + + if (!string.IsNullOrWhiteSpace(keyPrefix)) + { + Database = Database.WithKeyPrefix(keyPrefix); + } + + keyprefix = keyPrefix; + } + + /// <summary> + /// Return the instance of <see cref="StackExchange.Redis.IDatabase" /> used be ICacheClient implementation + /// </summary> + public IDatabase Database { get; } + + /// <summary> + /// Gets the serializer. + /// </summary> + /// <value> + /// The serializer. + /// </value> + public ISerializer Serializer { get; } + + /// <summary> + /// Verify that the specified cache key exists + /// </summary> + /// <param name="key">The cache key.</param> + /// <param name="flags">The flags to use for this operation.</param> + /// <returns> + /// True if the key is present into Redis. Othwerwise False + /// </returns> + public bool Exists(string key, CommandFlags flags = CommandFlags.None) + { + return Database.KeyExists(key, flags); + } + + /// <summary> + /// Verify that the specified cache key exists + /// </summary> + /// <param name="key">The cache key.</param> + /// <param name="flags">The flags to use for this operation.</param> + /// <returns> + /// True if the key is present into Redis. Othwerwise False + /// </returns> + public Task<bool> ExistsAsync(string key, CommandFlags flags = CommandFlags.None) + { + return Database.KeyExistsAsync(key, flags); + } + + /// <summary> + /// Removes the specified key from Redis Database + /// </summary> + /// <param name="key">The key.</param> + /// <param name="flags">The flags to use for this operation.</param> + /// <returns> + /// True if the key has removed. Othwerwise False + /// </returns> + public bool Remove(string key, CommandFlags flags = CommandFlags.None) + { + return Database.KeyDelete(key, flags); + } + + /// <summary> + /// Removes the specified key from Redis Database + /// </summary> + /// <param name="key">The key.</param> + /// <param name="flags">The flags to use for this operation.</param> + /// <returns> + /// True if the key has removed. Othwerwise False + /// </returns> + public Task<bool> RemoveAsync(string key, CommandFlags flags = CommandFlags.None) + { + return Database.KeyDeleteAsync(key, flags); + } + + /// <summary> + /// Removes all specified keys from Redis Database + /// </summary> + /// <param name="keys">The key.</param> + /// <param name="flags">The flags to use for this operation.</param> + public void RemoveAll(IEnumerable<string> keys, CommandFlags flags = CommandFlags.None) + { + var redisKeys = keys.Select(x => (RedisKey)x).ToArray(); + Database.KeyDelete(redisKeys, flags); + } + + /// <summary> + /// Removes all specified keys from Redis Database + /// </summary> + /// <param name="keys">The key.</param> + /// <param name="flags">The flags to use for this operation.</param> + /// <returns></returns> + public Task RemoveAllAsync(IEnumerable<string> keys, CommandFlags flags = CommandFlags.None) + { + var redisKeys = keys.Select(x => (RedisKey)x).ToArray(); + return Database.KeyDeleteAsync(redisKeys, flags); + } + + /// <summary> + /// Get the object with the specified key from Redis database + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="flag">Behaviour markers associated with a given command</param> + /// <returns> + /// Null if not present, otherwise the instance of T. + /// </returns> + public T Get<T>(string key, CommandFlags flag = CommandFlags.None) + { + var valueBytes = Database.StringGet(key, flag); + + if (!valueBytes.HasValue) + return default; + + return Serializer.Deserialize<T>(valueBytes); + } + + /// <summary> + /// Get the object with the specified key from Redis database and update the expiry time + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="expiresAt">Expiration time.</param> + /// <param name="flag">Behaviour markers associated with a given command</param> + /// <returns> + /// Null if not present, otherwise the instance of T. + /// </returns> + public T Get<T>(string key, DateTimeOffset expiresAt, CommandFlags flag = CommandFlags.None) + { + var result = Get<T>(key, flag); + + if (!Equals(result, default(T))) + Database.KeyExpire(key, expiresAt.Subtract(DateTime.Now)); + + return result; + } + + /// <summary> + /// Get the object with the specified key from Redis database and update the expiry time + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="expiresIn">Time till the object expires.</param> + /// <param name="flags">The flags to use for this operation.</param> + /// <returns> + /// Null if not present, otherwise the instance of T. + /// </returns> + public T Get<T>(string key, TimeSpan expiresIn, CommandFlags flags = CommandFlags.None) + { + var result = Get<T>(key, flags); + + if (!Equals(result, default(T))) + Database.KeyExpire(key, expiresIn); + + return result; + } + + /// <summary> + /// Get the object with the specified key from Redis database + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="flag">Behaviour markers associated with a given command</param> + /// <returns> + /// Null if not present, otherwise the instance of T. + /// </returns> + public async Task<T> GetAsync<T>(string key, CommandFlags flag = CommandFlags.None) + { + var valueBytes = await Database.StringGetAsync(key, flag); + + if (!valueBytes.HasValue) + return default; + + return await Serializer.DeserializeAsync<T>(valueBytes); + } + + /// <summary> + /// Get the object with the specified key from Redis database and update the expiry time + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="expiresAt">Expiration time.</param> + /// <param name="flag">Behaviour markers associated with a given command</param> + /// <returns> + /// Null if not present, otherwise the instance of T. + /// </returns> + public async Task<T> GetAsync<T>(string key, DateTimeOffset expiresAt, CommandFlags flag = CommandFlags.None) + { + var result = await GetAsync<T>(key, flag); + + if (!Equals(result, default(T))) + await Database.KeyExpireAsync(key, expiresAt.Subtract(DateTime.Now)); + + return default; + } + + /// <summary> + /// Get the object with the specified key from Redis database and update the expiry time + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="expiresIn">Time till the object expires.</param> + /// <param name="flag">Behaviour markers associated with a given command</param> + /// <returns> + /// Null if not present, otherwise the instance of T. + /// </returns> + public async Task<T> GetAsync<T>(string key, TimeSpan expiresIn, CommandFlags flag = CommandFlags.None) + { + var result = await GetAsync<T>(key, flag); + + if (!Equals(result, default(T))) + await Database.KeyExpireAsync(key, expiresIn); + + return default; + } + + /// <summary> + /// Adds the specified instance to the Redis database. + /// </summary> + /// <typeparam name="T">The type of the class to add to Redis</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="value">The instance of T.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public bool Add<T>(string key, T value, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var entryBytes = Serializer.Serialize(value); + + return Database.StringSet(key, entryBytes, null, when, flag); + } + + /// <summary> + /// Adds the specified instance to the Redis database. + /// </summary> + /// <typeparam name="T">The type of the class to add to Redis</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="value">The instance of T.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public async Task<bool> AddAsync<T>(string key, T value, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var entryBytes = await Serializer.SerializeAsync(value); + + return await Database.StringSetAsync(key, entryBytes, null, when, flag); + } + + /// <summary> + /// Replaces the object with specified key into Redis database. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="value">The instance of T</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public bool Replace<T>(string key, T value, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + return Add(key, value, when, flag); + } + + /// <summary> + /// Replaces the object with specified key into Redis database. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="value">The instance of T</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public Task<bool> ReplaceAsync<T>(string key, T value, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + return AddAsync(key, value, when, flag); + } + + /// <summary> + /// Adds the specified instance to the Redis database. + /// </summary> + /// <typeparam name="T">The type of the class to add to Redis</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="value">The instance of T.</param> + /// <param name="expiresAt">Expiration time.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public bool Add<T>(string key, T value, DateTimeOffset expiresAt, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var entryBytes = Serializer.Serialize(value); + var expiration = expiresAt.Subtract(DateTimeOffset.Now); + + return Database.StringSet(key, entryBytes, expiration,when, flag); + } + + /// <summary> + /// Adds the specified instance to the Redis database. + /// </summary> + /// <typeparam name="T">The type of the class to add to Redis</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="value">The instance of T.</param> + /// <param name="expiresAt">Expiration time.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public async Task<bool> AddAsync<T>(string key, T value, DateTimeOffset expiresAt, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var entryBytes = await Serializer.SerializeAsync(value); + var expiration = expiresAt.Subtract(DateTimeOffset.Now); + + return await Database.StringSetAsync(key, entryBytes, expiration, when, flag); + } + + /// <summary> + /// Replaces the object with specified key into Redis database. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="value">The instance of T</param> + /// <param name="expiresAt">Expiration time.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public bool Replace<T>(string key, T value, DateTimeOffset expiresAt, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + return Add(key, value, expiresAt, when, flag); + } + + /// <summary> + /// Replaces the object with specified key into Redis database. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="value">The instance of T</param> + /// <param name="expiresAt">Expiration time.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public Task<bool> ReplaceAsync<T>(string key, T value, DateTimeOffset expiresAt, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + return AddAsync(key, value, expiresAt, when, flag); + } + + /// <summary> + /// Adds the specified instance to the Redis database. + /// </summary> + /// <typeparam name="T">The type of the class to add to Redis</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="value">The instance of T.</param> + /// <param name="expiresIn">The duration of the cache using Timespan.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public bool Add<T>(string key, T value, TimeSpan expiresIn, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var entryBytes = Serializer.Serialize(value); + + return Database.StringSet(key, entryBytes, expiresIn, when, flag); + } + + /// <summary> + /// Adds the specified instance to the Redis database. + /// </summary> + /// <typeparam name="T">The type of the class to add to Redis</typeparam> + /// <param name="key">The cache key.</param> + /// <param name="value">The instance of T.</param> + /// <param name="expiresIn">The duration of the cache using Timespan.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public async Task<bool> AddAsync<T>(string key, T value, TimeSpan expiresIn, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var entryBytes = await Serializer.SerializeAsync(value); + + return await Database.StringSetAsync(key, entryBytes, expiresIn, when, flag); + } + + /// <summary> + /// Replaces the object with specified key into Redis database. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="value">The instance of T</param> + /// <param name="expiresIn">The duration of the cache using Timespan.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public bool Replace<T>(string key, T value, TimeSpan expiresIn, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + return Add(key, value, expiresIn, when, flag); + } + + /// <summary> + /// Replaces the object with specified key into Redis database. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="value">The instance of T</param> + /// <param name="expiresIn">The duration of the cache using Timespan.</param> + /// <returns> + /// True if the object has been added. Otherwise false + /// </returns> + public Task<bool> ReplaceAsync<T>(string key, T value, TimeSpan expiresIn) + { + return AddAsync(key, value, expiresIn); + } + + /// <summary> + /// Get the objects with the specified keys from Redis database with one roundtrip + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="keys">The keys.</param> + /// <returns> + /// Empty list if there are no results, otherwise the instance of T. + /// If a cache key is not present on Redis the specified object into the returned Dictionary will be null + /// </returns> + public IDictionary<string, T> GetAll<T>(IEnumerable<string> keys) + { + var redisKeys = keys.Select(x => (RedisKey)x).ToArray(); + var result = Database.StringGet(redisKeys); + + var dict = new Dictionary<string, T>(StringComparer.Ordinal); + for (var index = 0; index < redisKeys.Length; index++) + { + var value = result[index]; + dict.Add(redisKeys[index], value == RedisValue.Null ? default : Serializer.Deserialize<T>(value)); + } + + return dict; + } + + /// <summary> + /// Get the objects with the specified keys from Redis database with one roundtrip + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="keys">The keys.</param> + /// <param name="expiresAt">Expiration time.</param> + /// <returns> + /// Empty list if there are no results, otherwise the instance of T. + /// If a cache key is not present on Redis the specified object into the returned Dictionary will be null + /// </returns> + public IDictionary<string, T> GetAll<T>(IEnumerable<string> keys, DateTimeOffset expiresAt) + { + var result = GetAll<T>(keys); + UpdateExpiryAll(keys.ToArray(), expiresAt); + return result; + } + + /// <summary> + /// Get the objects with the specified keys from Redis database with one roundtrip + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="keys">The keys.</param> + /// <param name="expiresIn">Time until expiration.</param> + /// <returns> + /// Empty list if there are no results, otherwise the instance of T. + /// If a cache key is not present on Redis the specified object into the returned Dictionary will be null + /// </returns> + public IDictionary<string, T> GetAll<T>(IEnumerable<string> keys, TimeSpan expiresIn) + { + var result = GetAll<T>(keys); + UpdateExpiryAll(keys.ToArray(), expiresIn); + return result; + } + + /// <summary> + /// Get the objects with the specified keys from Redis database with one roundtrip + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="keys">The keys.</param> + /// <returns> + /// Empty list if there are no results, otherwise the instance of T. + /// If a cache key is not present on Redis the specified object into the returned Dictionary will be null + /// </returns> + public async Task<IDictionary<string, T>> GetAllAsync<T>(IEnumerable<string> keys) + { + var redisKeys = keys.Select(x => (RedisKey)x).ToArray(); + var result = await Database.StringGetAsync(redisKeys); + var dict = new Dictionary<string, T>(StringComparer.Ordinal); + for (var index = 0; index < redisKeys.Length; index++) + { + var value = result[index]; + dict.Add(redisKeys[index], value == RedisValue.Null ? default : Serializer.Deserialize<T>(value)); + } + + return dict; + } + + /// <summary> + /// Get the objects with the specified keys from Redis database with one roundtrip + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="keys">The keys.</param> + /// <param name="expiresAt">Expiration time.</param> + /// <returns> + /// Empty list if there are no results, otherwise the instance of T. + /// If a cache key is not present on Redis the specified object into the returned Dictionary will be null + /// </returns> + public async Task<IDictionary<string, T>> GetAllAsync<T>(IEnumerable<string> keys, DateTimeOffset expiresAt) + { + var result = await GetAllAsync<T>(keys); + await UpdateExpiryAllAsync(keys.ToArray(), expiresAt); + return result; + } + + /// <summary> + /// Get the objects with the specified keys from Redis database with one roundtrip + /// </summary> + /// <typeparam name="T">The type of the expected object</typeparam> + /// <param name="keys">The keys.</param> + /// <param name="expiresIn">Time until expiration.</param> + /// <returns> + /// Empty list if there are no results, otherwise the instance of T. + /// If a cache key is not present on Redis the specified object into the returned Dictionary will be null + /// </returns> + public async Task<IDictionary<string, T>> GetAllAsync<T>(IEnumerable<string> keys, TimeSpan expiresIn) + { + var result = await GetAllAsync<T>(keys); + await UpdateExpiryAllAsync(keys.ToArray(), expiresIn); + return result; + } + + /// <summary> + /// Adds all. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="items">The items.</param> + public bool AddAll<T>(IList<Tuple<string, T>> items, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var values = items + .Select(item => new KeyValuePair<RedisKey, RedisValue>(item.Item1, Serializer.Serialize(item.Item2))) + .ToArray(); + + return Database.StringSet(values,when, flag); + } + + /// <summary> + /// Adds all asynchronous. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="items">The items.</param> + /// <returns></returns> + public async Task<bool> AddAllAsync<T>(IList<Tuple<string, T>> items, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var values = items + .Select(item => new KeyValuePair<RedisKey, RedisValue>(item.Item1, Serializer.Serialize(item.Item2))) + .ToArray(); + + return await Database.StringSetAsync(values, when, flag); + } + + /// <summary> + /// Adds all. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="items">The items.</param> + /// <param name="expiresAt"></param> + public bool AddAll<T>(IList<Tuple<string, T>> items, DateTimeOffset expiresAt, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var values = items + .Select(item => new KeyValuePair<RedisKey, RedisValue>(item.Item1, Serializer.Serialize(item.Item2))) + .ToArray(); + + var result = Database.StringSet(values, when, flag); + + foreach (var value in values) + Database.KeyExpire(value.Key, expiresAt.DateTime, flag); + + return result; + } + + /// <summary> + /// Adds all asynchronous. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="items">The items.</param> + /// <param name="expiresAt"></param> + /// <returns></returns> + public async Task<bool> AddAllAsync<T>(IList<Tuple<string, T>> items, DateTimeOffset expiresAt, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var values = items + .Select(item => new KeyValuePair<RedisKey, RedisValue>(item.Item1, Serializer.Serialize(item.Item2))) + .ToArray(); + + var result = await Database.StringSetAsync(values, when, flag); + + Parallel.ForEach(values, async value => await Database.KeyExpireAsync(value.Key, expiresAt.DateTime, flag)); + + return result; + } + + /// <summary> + /// Adds all. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="items">The items.</param> + /// <param name="expiresOn"></param> + public bool AddAll<T>(IList<Tuple<string, T>> items, TimeSpan expiresOn, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var values = items + .Select(item => new KeyValuePair<RedisKey, RedisValue>(item.Item1, Serializer.Serialize(item.Item2))) + .ToArray(); + + var result = Database.StringSet(values, when, flag); + + foreach (var value in values) + Database.KeyExpire(value.Key, expiresOn, flag); + + return result; + } + + /// <summary> + /// Adds all asynchronous. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="items">The items.</param> + /// <param name="expiresOn"></param> + /// <returns></returns> + public async Task<bool> AddAllAsync<T>(IList<Tuple<string, T>> items, TimeSpan expiresOn, When when = When.Always, CommandFlags flag = CommandFlags.None) + { + var values = items + .Select(item => new KeyValuePair<RedisKey, RedisValue>(item.Item1, Serializer.Serialize(item.Item2))) + .ToArray(); + + var result = await Database.StringSetAsync(values, when, flag); + + Parallel.ForEach(values, async value => await Database.KeyExpireAsync(value.Key, expiresOn,flag)); + + return result; + } + + /// <summary> + /// Run SADD command http://redis.io/commands/sadd + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key"></param> + /// <param name="item"></param> + /// <returns></returns> + public bool SetAdd<T>(string key, T item, CommandFlags flag = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (item == null) + throw new ArgumentNullException(nameof(item), "item cannot be null."); + + var serializedObject = Serializer.Serialize(item); + + return Database.SetAdd(key, serializedObject, flag); + } + + /// <summary> + /// Run SADD command http://redis.io/commands/sadd" + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key"></param> + /// <param name="item"></param> + /// <returns></returns> + public async Task<bool> SetAddAsync<T>(string key, T item, CommandFlags flag = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (item == null) throw new ArgumentNullException(nameof(item), "item cannot be null."); + + var serializedObject = await Serializer.SerializeAsync(item); + + return await Database.SetAddAsync(key, serializedObject, flag); + } + + /// <summary> + /// Run SADD command http://redis.io/commands/sadd + /// </summary> + /// <param name="items">Name of the member.</param> + /// <param name="key">The key.</param> + public long SetAddAll<T>(string key, CommandFlags flag = CommandFlags.None, params T[] items) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (items == null) + throw new ArgumentNullException(nameof(items), "items cannot be null."); + + if (items.Any(item => item == null)) + throw new ArgumentException("items cannot contains any null item.", nameof(items)); + + return Database.SetAdd(key, items.Select(item => Serializer.Serialize(item)).Select(x => (RedisValue)x).ToArray(), flag); + } + + /// <summary> + /// Run SADD command http://redis.io/commands/sadd + /// </summary> + /// <param name="items">Name of the member.</param> + /// <param name="key">The key.</param> + public async Task<long> SetAddAllAsync<T>(string key, CommandFlags flag = CommandFlags.None, params T[] items) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (items == null) throw new ArgumentNullException(nameof(items), "items cannot be null."); + + if (items.Any(item => item == null)) throw new ArgumentException("items cannot contains any null item.", nameof(items)); + + return await Database.SetAddAsync(key, items.Select(item => Serializer.Serialize(item)).Select(x => (RedisValue)x).ToArray(), flag); + } + + /// <summary> + /// Run SREM command http://redis.io/commands/srem + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key"></param> + /// <param name="item"></param> + /// <returns></returns> + public bool SetRemove<T>(string key, T item, CommandFlags flag = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (item == null) throw new ArgumentNullException(nameof(item), "item cannot be null."); + + var serializedObject = Serializer.Serialize(item); + + return Database.SetRemove(key, serializedObject, flag); + } + + /// <summary> + /// Run SREM command http://redis.io/commands/srem" + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key"></param> + /// <param name="item"></param> + /// <returns></returns> + public async Task<bool> SetRemoveAsync<T>(string key, T item, CommandFlags flag = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (item == null) throw new ArgumentNullException(nameof(item), "item cannot be null."); + + var serializedObject = await Serializer.SerializeAsync(item); + + return await Database.SetRemoveAsync(key, serializedObject, flag); + } + + /// <summary> + /// Run SREM command http://redis.io/commands/srem + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key"></param> + /// <param name="items"></param> + /// <returns></returns> + public long SetRemoveAll<T>(string key, CommandFlags flag = CommandFlags.None, params T[] items) where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (items == null) + throw new ArgumentNullException(nameof(items), "items cannot be null."); + + if (items.Any(item => item == null)) + throw new ArgumentException("items cannot contains any null item.", nameof(items)); + + return Database.SetRemove(key, items.Select(item => Serializer.Serialize(item)).Select(x => (RedisValue)x).ToArray(), flag); + } + + /// <summary> + /// Run SREM command http://redis.io/commands/srem + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key"></param> + /// <param name="items"></param> + /// <returns></returns> + public async Task<long> SetRemoveAllAsync<T>(string key, CommandFlags flag = CommandFlags.None, params T[] items) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (items == null) throw new ArgumentNullException(nameof(items), "items cannot be null."); + + if (items.Any(item => item == null)) throw new ArgumentException("items cannot contains any null item.", nameof(items)); + + return await Database.SetRemoveAsync(key, items.Select(item => Serializer.Serialize(item)).Select(x => (RedisValue)x).ToArray(), flag); + } + + /// <summary> + /// Run SMEMBERS command http://redis.io/commands/SMEMBERS + /// </summary> + /// <param name="memberName">Name of the member.</param> + /// <returns></returns> + public string[] SetMember(string memberName, CommandFlags flag = CommandFlags.None) + { + return Database.SetMembers(memberName, flag).Select(x => x.ToString()).ToArray(); + } + + /// <summary> + /// Run SMEMBERS command see http://redis.io/commands/SMEMBERS + /// </summary> + /// <param name="memberName">Name of the member.</param> + /// <returns></returns> + public async Task<string[]> SetMemberAsync(string memberName, CommandFlags flag = CommandFlags.None) + { + return (await Database.SetMembersAsync(memberName, flag)).Select(x => x.ToString()).ToArray(); + } + + /// <summary> + /// Run SMEMBERS command see http://redis.io/commands/SMEMBERS + /// Deserializes the results to T + /// </summary> + /// <typeparam name="T">The type of the expected objects in the set</typeparam> + /// <param name="key">The key</param> + /// <returns>An array of objects in the set</returns> + public IEnumerable<T> SetMembers<T>(string key, CommandFlags flag = CommandFlags.None) + { + var members = Database.SetMembers(key, flag); + return members.Select(m => m == RedisValue.Null ? default : Serializer.Deserialize<T>(m)); + } + + /// <summary> + /// Run SMEMBERS command see http://redis.io/commands/SMEMBERS + /// Deserializes the results to T + /// </summary> + /// <typeparam name="T">The type of the expected objects</typeparam> + /// <param name="key">The key</param> + /// <returns>An array of objects in the set</returns> + public async Task<IEnumerable<T>> SetMembersAsync<T>(string key, CommandFlags flag = CommandFlags.None) + { + var members = await Database.SetMembersAsync(key, flag); + + return members.Select(m => m == RedisValue.Null ? default : Serializer.Deserialize<T>(m)); + } + + /// <summary> + /// Searches the keys from Redis database + /// </summary> + /// <remarks> + /// Consider this as a command that should only be used in production environments with extreme care. It may ruin + /// performance when it is executed against large databases + /// </remarks> + /// <param name="pattern">The pattern.</param> + /// <example> + /// if you want to return all keys that start with "myCacheKey" uses "myCacheKey*" + /// if you want to return all keys that contain with "myCacheKey" uses "*myCacheKey*" + /// if you want to return all keys that end with "myCacheKey" uses "*myCacheKey" + /// </example> + /// <returns>A list of cache keys retrieved from Redis database</returns> + public IEnumerable<string> SearchKeys(string pattern) + { + pattern = $"{keyprefix}{pattern}"; + var keys = new HashSet<string>(); + + var multiplexer = Database.Multiplexer; + var servers = ServerIteratorFactory.GetServers(connectionMultiplexer, serverEnumerationStrategy).ToArray(); + + if (!servers.Any()) + throw new Exception("No server found to serve the KEYS command."); + + foreach (var server in servers) + { + var nextCursor = 0; + do + { + var redisResult = Database.Execute("SCAN", nextCursor.ToString(), "MATCH", pattern, "COUNT", "1000"); + var innerResult = (RedisResult[])redisResult; + + nextCursor = int.Parse((string)innerResult[0]); + + var resultLines = ((string[])innerResult[1]).ToList(); + + keys.UnionWith(resultLines); + } while (nextCursor != 0); + } + + return !string.IsNullOrEmpty(keyprefix) ? keys.Select(k => k.Substring(keyprefix.Length)) : keys; + } + + /// <summary> + /// Searches the keys from Redis database + /// </summary> + /// <remarks> + /// Consider this as a command that should only be used in production environments with extreme care. It may ruin + /// performance when it is executed against large databases + /// </remarks> + /// <param name="pattern">The pattern.</param> + /// <example> + /// if you want to return all keys that start with "myCacheKey" uses "myCacheKey*" + /// if you want to return all keys that contain with "myCacheKey" uses "*myCacheKey*" + /// if you want to return all keys that end with "myCacheKey" uses "*myCacheKey" + /// </example> + /// <returns>A list of cache keys retrieved from Redis database</returns> + public Task<IEnumerable<string>> SearchKeysAsync(string pattern) + { + return Task.Factory.StartNew(() => SearchKeys(pattern)); + } + + /// <summary> + /// Flushes the database. + /// </summary> + public void FlushDb() + { + var endPoints = Database.Multiplexer.GetEndPoints(); + foreach (var endpoint in endPoints) + { + var server = Database.Multiplexer.GetServer(endpoint); + + if (!server.IsSlave) + server.FlushDatabase(Database.Database); + } + } + + /// <summary> + /// Flushes the database asynchronous. + /// </summary> + /// <returns></returns> + public async Task FlushDbAsync() + { + var endPoints = Database.Multiplexer.GetEndPoints(); + + foreach (var endpoint in endPoints) + { + var server = Database.Multiplexer.GetServer(endpoint); + + if (!server.IsSlave) + await server.FlushDatabaseAsync(Database.Database); + } + } + + /// <summary> + /// Save the DB in background. + /// </summary> + /// <param name="saveType"></param> + public void Save(SaveType saveType, CommandFlags flags = CommandFlags.None) + { + var endPoints = Database.Multiplexer.GetEndPoints(); + + foreach (var endpoint in endPoints) + Database.Multiplexer.GetServer(endpoint).Save(saveType, flags); + } + + /// <summary> + /// Save the DB in background asynchronous. + /// </summary> + /// <param name="saveType"></param> + public async Task SaveAsync(SaveType saveType, CommandFlags flags = CommandFlags.None) + { + var endPoints = Database.Multiplexer.GetEndPoints(); + + foreach (var endpoint in endPoints) + await Database.Multiplexer.GetServer(endpoint).SaveAsync(saveType, flags); + } + + /// <summary> + /// Gets the information about redis. + /// More info see http://redis.io/commands/INFO + /// </summary> + /// <returns></returns> + public Dictionary<string, string> GetInfo() + { + var info = Database.ScriptEvaluate("return redis.call('INFO')").ToString(); + + return ParseInfo(info); + } + + /// <summary> + /// Gets the information about redis. + /// More info see http://redis.io/commands/INFO + /// </summary> + /// <returns></returns> + public async Task<Dictionary<string, string>> GetInfoAsync() + { + var info = (await Database.ScriptEvaluateAsync("return redis.call('INFO')")).ToString(); + + return ParseInfo(info); + } + + /// <summary> + /// Publishes a message to a channel. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="channel"></param> + /// <param name="message"></param> + /// <param name="flags"></param> + /// <returns></returns> + public long Publish<T>(RedisChannel channel, T message, CommandFlags flags = CommandFlags.None) + { + var sub = connectionMultiplexer.GetSubscriber(); + return sub.Publish(channel, Serializer.Serialize(message), flags); + } + + /// <summary> + /// Publishes a message to a channel. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="channel"></param> + /// <param name="message"></param> + /// <param name="flags"></param> + /// <returns></returns> + public async Task<long> PublishAsync<T>(RedisChannel channel, T message, CommandFlags flags = CommandFlags.None) + { + var sub = connectionMultiplexer.GetSubscriber(); + return await sub.PublishAsync(channel, await Serializer.SerializeAsync(message), flags); + } + + /// <summary> + /// Registers a callback handler to process messages published to a channel. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="channel"></param> + /// <param name="handler"></param> + /// <param name="flags"></param> + /// <exception cref="System.ArgumentNullException"></exception> + public void Subscribe<T>(RedisChannel channel, Action<T> handler, CommandFlags flags = CommandFlags.None) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + var sub = connectionMultiplexer.GetSubscriber(); + sub.Subscribe(channel, (redisChannel, value) => handler(Serializer.Deserialize<T>(value)), flags); + } + + /// <summary> + /// Registers a callback handler to process messages published to a channel. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="channel"></param> + /// <param name="handler"></param> + /// <param name="flags"></param> + /// <returns></returns> + /// <exception cref="System.ArgumentNullException"></exception> + public async Task SubscribeAsync<T>(RedisChannel channel, Func<T, Task> handler, CommandFlags flags = CommandFlags.None) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + var sub = connectionMultiplexer.GetSubscriber(); + await + sub.SubscribeAsync(channel, async (redisChannel, value) => await handler(Serializer.Deserialize<T>(value)), flags); + } + + /// <summary> + /// Unregisters a callback handler to process messages published to a channel. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="channel"></param> + /// <param name="handler"></param> + /// <param name="flags"></param> + /// <exception cref="System.ArgumentNullException"></exception> + public void Unsubscribe<T>(RedisChannel channel, Action<T> handler, CommandFlags flags = CommandFlags.None) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + var sub = connectionMultiplexer.GetSubscriber(); + sub.Unsubscribe(channel, (redisChannel, value) => handler(Serializer.Deserialize<T>(value)), flags); + } + + /// <summary> + /// Unregisters a callback handler to process messages published to a channel. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="channel"></param> + /// <param name="handler"></param> + /// <param name="flags"></param> + /// <returns></returns> + /// <exception cref="System.ArgumentNullException"></exception> + public async Task UnsubscribeAsync<T>(RedisChannel channel, Func<T, Task> handler, CommandFlags flags = CommandFlags.None) + { + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + + var sub = connectionMultiplexer.GetSubscriber(); + await sub.UnsubscribeAsync(channel, (redisChannel, value) => handler(Serializer.Deserialize<T>(value)), flags); + } + + /// <summary> + /// Unregisters all callback handlers on a channel. + /// </summary> + /// <param name="flags"></param> + public void UnsubscribeAll(CommandFlags flags = CommandFlags.None) + { + var sub = connectionMultiplexer.GetSubscriber(); + sub.UnsubscribeAll(flags); + } + + /// <summary> + /// Unregisters all callback handlers on a channel. + /// </summary> + /// <param name="flags"></param> + /// <returns></returns> + public async Task UnsubscribeAllAsync(CommandFlags flags = CommandFlags.None) + { + var sub = connectionMultiplexer.GetSubscriber(); + await sub.UnsubscribeAllAsync(flags); + } + + /// <summary> + /// Insert the specified value at the head of the list stored at key. If key does not exist, it is created as empty + /// list before performing the push operations. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="item">The item.</param> + /// <returns> + /// the length of the list after the push operations. + /// </returns> + /// <exception cref="System.ArgumentException">key cannot be empty.;key</exception> + /// <exception cref="System.ArgumentNullException">item;item cannot be null.</exception> + /// <remarks> + /// http://redis.io/commands/lpush + /// </remarks> + public long ListAddToLeft<T>(string key, T item) where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (item == null) + throw new ArgumentNullException(nameof(item), "item cannot be null."); + + var serializedItem = Serializer.Serialize(item); + + return Database.ListLeftPush(key, serializedItem); + } + + /// <summary> + /// Lists the add to left asynchronous. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <param name="item">The item.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">key cannot be empty.;key</exception> + /// <exception cref="System.ArgumentNullException">item;item cannot be null.</exception> + public async Task<long> ListAddToLeftAsync<T>(string key, T item, When when = When.Always, CommandFlags flags = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + if (item == null) + throw new ArgumentNullException(nameof(item), "item cannot be null."); + + var serializedItem = await Serializer.SerializeAsync(item); + + return await Database.ListLeftPushAsync(key, serializedItem, when, flags); + } + + /// <summary> + /// Removes and returns the last element of the list stored at key. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">key cannot be empty.;key</exception> + /// <remarks> + /// http://redis.io/commands/rpop + /// </remarks> + public T ListGetFromRight<T>(string key, CommandFlags flags = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("key cannot be empty.", nameof(key)); + + var item = Database.ListRightPop(key, flags); + + return item == RedisValue.Null ? null : Serializer.Deserialize<T>(item); + } + + /// <summary> + /// Removes and returns the last element of the list stored at key. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="key">The key.</param> + /// <returns></returns> + /// <exception cref="System.ArgumentException">key cannot be empty.;key</exception> + /// <remarks> + /// http://redis.io/commands/rpop + /// </remarks> + public async Task<T> ListGetFromRightAsync<T>(string key, CommandFlags flags = CommandFlags.None) where T : class + { + if (string.IsNullOrEmpty(key)) throw new ArgumentException("key cannot be empty.", nameof(key)); + + var item = await Database.ListRightPopAsync(key, flags); + + if (item == RedisValue.Null) return null; + + return item == RedisValue.Null ? null : await Serializer.DeserializeAsync<T>(item); + } + + /// <summary> + /// Removes the specified fields from the hash stored at key. + /// Specified fields that do not exist within this hash are ignored. + /// </summary> + /// <remarks> + /// Time complexity: O(1) + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="key">Key of the entry</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns> + /// If key is deleted returns true. + /// If key does not exist, it is treated as an empty hash and this command returns false. + /// </returns> + public bool HashDelete(string hashKey, string key, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashDelete(hashKey, key, commandFlags); + } + + /// <summary> + /// Removes the specified fields from the hash stored at key. + /// Specified fields that do not exist within this hash are ignored. + /// If key does not exist, it is treated as an empty hash and this command returns 0. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the number of fields to be removed. + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="keys"></param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>Tthe number of fields that were removed from the hash, not including specified but non existing fields.</returns> + public long HashDelete(string hashKey, IEnumerable<string> keys, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashDelete(hashKey, keys.Select(x => (RedisValue)x).ToArray(), commandFlags); + } + + /// <summary> + /// Returns if field is an existing field in the hash stored at key. + /// </summary> + /// <remarks> + /// Time complexity: O(1) + /// </remarks> + /// <param name="hashKey">The key of the hash in redis</param> + /// <param name="key">The key of the field in the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>Returns if field is an existing field in the hash stored at key.</returns> + public bool HashExists(string hashKey, string key, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashExists(hashKey, key, commandFlags); + } + + /// <summary> + /// Returns the value associated with field in the hash stored at key. + /// </summary> + /// <remarks> + /// Time complexity: O(1) + /// </remarks> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">Key of the hash</param> + /// <param name="key">Key of the entry</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>the value associated with field, or nil when field is not present in the hash or key does not exist.</returns> + public T HashGet<T>(string hashKey, string key, CommandFlags commandFlags = CommandFlags.None) + { + var redisValue = Database.HashGet(hashKey, key, commandFlags); + return redisValue.HasValue ? Serializer.Deserialize<T>(redisValue) : default; + } + + /// <summary> + /// Returns the values associated with the specified fields in the hash stored at key. + /// For every field that does not exist in the hash, a nil value is returned. + /// Because a non-existing keys are treated as empty hashes, running HMGET against a non-existing key will return a list of nil values. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the number of fields being requested. + /// </remarks> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">Key of the hash</param> + /// <param name="keys"></param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>list of values associated with the given fields, in the same order as they are requested.</returns> + public Dictionary<string, T> HashGet<T>(string hashKey, IEnumerable<string> keys, CommandFlags commandFlags = CommandFlags.None) + { + return keys.Select(x => new { key = x, value = HashGet<T>(hashKey, x, commandFlags) }) + .ToDictionary(kv => kv.key, kv => kv.value, StringComparer.Ordinal); + } + + /// <summary> + /// Returns all fields and values of the hash stored at key. In the returned value, every field name is followed by its value, so the length of the reply is twice the size of the hash. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the size of the hash. + /// </remarks> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">Key of the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>list of fields and their values stored in the hash, or an empty list when key does not exist.</returns> + public Dictionary<string, T> HashGetAll<T>(string hashKey, CommandFlags commandFlags = CommandFlags.None) + { + return Database + .HashGetAll(hashKey, commandFlags) + .ToDictionary( + x => x.Name.ToString(), + x => Serializer.Deserialize<T>(x.Value), + StringComparer.Ordinal); + } + + /// <summary> + /// Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key holding a hash is created. + /// If field does not exist the value is set to 0 before the operation is performed. + /// The range of values supported by HINCRBY is limited to 64 bit signed integers. + /// </summary> + /// <remarks> + /// Time complexity: O(1) + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="key">Key of the entry</param> + /// <param name="commandFlags">Command execution flags</param> + /// <param name="value">the value at field after the increment operation</param> + public long HashIncerementBy(string hashKey, string key, long value, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashIncrement(hashKey, key, value, commandFlags); + } + + /// <summary> + /// Increment the specified field of an hash stored at key, and representing a floating point number, by the specified increment. + /// If the field does not exist, it is set to 0 before performing the operation. + /// </summary> + /// <remarks> + /// <para> + /// An error is returned if one of the following conditions occur: + /// * The field contains a value of the wrong type (not a string). + /// * The current field content or the specified increment are not parsable as a double precision floating point number. + /// </para> + /// <para> + /// Time complexity: O(1) + /// </para> + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="key">Key of the entry</param> + /// <param name="commandFlags">Command execution flags</param> + /// <param name="value">the value at field after the increment operation</param> + public double HashIncerementBy(string hashKey, string key, double value, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashIncrement(hashKey, key, value, commandFlags); + } + + /// <summary> + /// Returns all field names in the hash stored at key. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the size of the hash. + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>list of fields in the hash, or an empty list when key does not exist.</returns> + public IEnumerable<string> HashKeys(string hashKey, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashKeys(hashKey, commandFlags).Select(x => x.ToString()); + } + + /// <summary> + /// Returns the number of fields contained in the hash stored at key. + /// </summary> + /// <remarks> + /// Time complexity: O(1) + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>number of fields in the hash, or 0 when key does not exist.</returns> + public long HashLength(string hashKey, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashLength(hashKey, commandFlags); + } + + /// <summary> + /// Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created. If field already exists in the hash, it is overwritten. + /// </summary> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">The key of the hash in redis</param> + /// <param name="key">The key of the field in the hash</param> + /// <param name="nx">Behave like hsetnx - set only if not exists</param> + /// <param name="value">The value to be inserted</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns> + /// <c>true</c> if field is a new field in the hash and value was set. + /// <c>false</c> if field already exists in the hash and no operation was performed. + /// </returns> + public bool HashSet<T>(string hashKey, string key, T value, bool nx = false, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashSet(hashKey, key, Serializer.Serialize(value), nx ? When.NotExists : When.Always, commandFlags); + } + + /// <summary> + /// Sets the specified fields to their respective values in the hash stored at key. This command overwrites any existing fields in the hash. If key does not exist, a new key holding a hash is created. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the number of fields being set. + /// </remarks> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">Key of the hash</param> + /// <param name="values"></param> + /// <param name="commandFlags">Command execution flags</param> + public void HashSet<T>(string hashKey, Dictionary<string, T> values, CommandFlags commandFlags = CommandFlags.None) + { + var entries = values.Select(kv => new HashEntry(kv.Key, Serializer.Serialize(kv.Value))); + Database.HashSet(hashKey, entries.ToArray(), commandFlags); + } + + /// <summary> + /// Returns all values in the hash stored at key. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the size of the hash. + /// </remarks> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">Key of the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>list of values in the hash, or an empty list when key does not exist.</returns> + public IEnumerable<T> HashValues<T>(string hashKey, CommandFlags commandFlags = CommandFlags.None) + { + return Database.HashValues(hashKey, commandFlags).Select(x => Serializer.Deserialize<T>(x)); + } + + /// <summary> + /// iterates fields of Hash types and their associated values. + /// </summary> + /// <remarks> + /// Time complexity: O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. + /// N is the number of elements inside the collection. + /// </remarks> + /// <typeparam name="T">Type of the returned value</typeparam> + /// <param name="hashKey">Key of the hash</param> + /// <param name="pattern">GLOB search pattern</param> + /// <param name="pageSize">Number of elements to retrieve from the redis server in the cursor</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns></returns> + public Dictionary<string, T> HashScan<T>(string hashKey, string pattern, int pageSize = 10, CommandFlags commandFlags = CommandFlags.None) + { + return Database + .HashScan(hashKey, pattern, pageSize, commandFlags) + .ToDictionary(x => x.Name.ToString(), + x => Serializer.Deserialize<T>(x.Value), + StringComparer.Ordinal); + } + + /// <param name="hashKey">Key of the hash</param> + /// <param name="key">Key of the entry</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns> + /// If key is deleted returns true. + /// If key does not exist, it is treated as an empty hash and this command returns false. + /// </returns> + public async Task<bool> HashDeleteAsync(string hashKey, string key, CommandFlags commandFlags = CommandFlags.None) + { + return await Database.HashDeleteAsync(hashKey, key, commandFlags); + } + + /// <summary> + /// Removes the specified fields from the hash stored at key. + /// Specified fields that do not exist within this hash are ignored. + /// If key does not exist, it is treated as an empty hash and this command returns 0. + /// </summary> + /// <remarks> + /// Time complexity: O(N) where N is the number of fields to be removed. + /// </remarks> + /// <param name="hashKey">Key of the hash</param> + /// <param name="keys">Keys to retrieve from the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>Tthe number of fields that were removed from the hash, not including specified but non existing fields.</returns> + public async Task<long> HashDeleteAsync(string hashKey, IEnumerable<string> keys, CommandFlags commandFlags = CommandFlags.None) + { + return await Database.HashDeleteAsync(hashKey, keys.Select(x => (RedisValue)x).ToArray(), commandFlags); + } + + /// <summary> + /// Returns if field is an existing field in the hash stored at key. + /// </summary> + /// <remarks> + /// Time complexity: O(1) + /// </remarks> + /// <param name="hashKey">The key of the hash in redis</param> + /// <param name="key">The key of the field in the hash</param> + /// <param name="commandFlags">Command execution flags</param> + /// <returns>Returns if field is an existing field in the hash stored at key.</returns> + public async Task<bool> HashExistsAsync(string hashKey, string key, CommandFlags commandFlags = CommandFlags.None) + { + return await Database.HashExistsAsync(hashKey, key, commandFlags); + } + + + /// <summary> + /// Returns the value associated with field in the hash stored at key. + /// </summary> + /// <remarks>
WIP (doesn't compile)