22

I have used StackExchange.Redis for c# redis cache.

cache.StringSet("Key1", CustomerObject);

but I want to store data like

cache.StringSet("Key1", ListOfCustomer);

so that one key has all Customer List stored and it is easy to search,group,filter customer Data also inside that List

Answers are welcome using ServiceStack.Redis or StackExchange.Redis

Neo
  • 15,491
  • 59
  • 215
  • 405

3 Answers3

36

If you use Stackechange.Redis, you can use the List methods on its API. Here is a naive implementation of IList using a redis list to store the items.

Hopefully it can help you to understand some of the list API methods:

public class RedisList<T> : IList<T>
{
    private static ConnectionMultiplexer _cnn;
    private string key;
    public RedisList(string key)
    {
        this.key = key;
        _cnn = ConnectionMultiplexer.Connect("localhost");
    }
    private IDatabase GetRedisDb()
    {
        return _cnn.GetDatabase();
    }
    private string Serialize(object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
    private T Deserialize<T>(string serialized)
    {
        return JsonConvert.DeserializeObject<T>(serialized);
    }
    public void Insert(int index, T item)
    {
        var db = GetRedisDb();
        var before = db.ListGetByIndex(key, index);
        db.ListInsertBefore(key, before, Serialize(item));
    }
    public void RemoveAt(int index)
    {
        var db = GetRedisDb();
        var value = db.ListGetByIndex(key, index);
        if (!value.IsNull)
        {
            db.ListRemove(key, value);
        }
    }
    public T this[int index]
    {
        get
        {
            var value = GetRedisDb().ListGetByIndex(key, index);
            return Deserialize<T>(value.ToString());
        }
        set
        {
            Insert(index, value);
        }
    }
    public void Add(T item)
    {
        GetRedisDb().ListRightPush(key, Serialize(item));
    }
    public void Clear()
    {
        GetRedisDb().KeyDelete(key);
    }
    public bool Contains(T item)
    {
        for (int i = 0; i < Count; i++)
        {
            if (GetRedisDb().ListGetByIndex(key, i).ToString().Equals(Serialize(item)))
            {
                return true;
            }
        }
        return false;
    }
    public void CopyTo(T[] array, int arrayIndex)
    {
        GetRedisDb().ListRange(key).CopyTo(array, arrayIndex);
    }
    public int IndexOf(T item)
    {
        for (int i = 0; i < Count; i++)
        {
            if (GetRedisDb().ListGetByIndex(key, i).ToString().Equals(Serialize(item)))
            {
                return i;
            }
        }
        return -1;
    }
    public int Count
    {
        get { return (int)GetRedisDb().ListLength(key); }
    }
    public bool IsReadOnly
    {
        get { return false; }
    }
    public bool Remove(T item)
    {
        return GetRedisDb().ListRemove(key, Serialize(item)) > 0;
    }
    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < this.Count; i++)
        {
            yield return Deserialize<T>(GetRedisDb().ListGetByIndex(key, i).ToString());
        }
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        for (int i = 0; i < this.Count; i++)
        {
            yield return Deserialize<T>(GetRedisDb().ListGetByIndex(key, i).ToString());
        }
    }
}

Note the use of Newtonsoft.Json for the serialization. You will need the following nu-get packages:

Install-Package Newtonsoft.Json
Install-Package StackExchange.Redis

After reading your question and comments, since you want to access elements by key, I think you're looking for Redis Hashes, which are maps composed of fields associated with values.

So you can have a Redis Key for a Hash containing all your Customers, each one being a Value associated to a Field. You can choose the CustomerId as the Field, so you can then get a customer by its id in O(1).

I think implementing IDictionary is a good way to see it working. So a RedisDictionary class similar to the RedisList but using a Redis Hash could be:

public class RedisDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private static ConnectionMultiplexer _cnn;
    private string _redisKey;
    public RedisDictionary(string redisKey)
    {
        _redisKey = redisKey;
        _cnn = ConnectionMultiplexer.Connect("localhost");
    }
    private IDatabase GetRedisDb()
    {
        return _cnn.GetDatabase();
    }
    private string Serialize(object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
    private T Deserialize<T>(string serialized)
    {
        return JsonConvert.DeserializeObject<T>(serialized);
    }
    public void Add(TKey key, TValue value)
    {
        GetRedisDb().HashSet(_redisKey, Serialize(key), Serialize(value));
    }
    public bool ContainsKey(TKey key)
    {
        return GetRedisDb().HashExists(_redisKey, Serialize(key));
    }
    public bool Remove(TKey key)
    {
        return GetRedisDb().HashDelete(_redisKey, Serialize(key));
    }
    public bool TryGetValue(TKey key, out TValue value)
    {
        var redisValue = GetRedisDb().HashGet(_redisKey, Serialize(key));
        if (redisValue.IsNull)
        {
            value = default(TValue);
            return false;
        }
        value = Deserialize<TValue>(redisValue.ToString());
        return true;
    }
    public ICollection<TValue> Values
    {
        get { return new Collection<TValue>(GetRedisDb().HashValues(_redisKey).Select(h => Deserialize<TValue>(h.ToString())).ToList()); }
    }
    public ICollection<TKey> Keys
    {
        get { return new Collection<TKey>(GetRedisDb().HashKeys(_redisKey).Select(h => Deserialize<TKey>(h.ToString())).ToList()); }
    }
    public TValue this[TKey key]
    {
        get
        {
            var redisValue = GetRedisDb().HashGet(_redisKey, Serialize(key));
            return redisValue.IsNull ? default(TValue) : Deserialize<TValue>(redisValue.ToString());
        }
        set
        {
            Add(key, value);
        }
    }
    public void Add(KeyValuePair<TKey, TValue> item)
    {
        Add(item.Key, item.Value);
    }
    public void Clear()
    {
        GetRedisDb().KeyDelete(_redisKey);
    }
    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return GetRedisDb().HashExists(_redisKey, Serialize(item.Key));
    }
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        GetRedisDb().HashGetAll(_redisKey).CopyTo(array, arrayIndex);
    }
    public int Count
    {
        get { return (int)GetRedisDb().HashLength(_redisKey); }
    }
    public bool IsReadOnly
    {
        get { return false; }
    }
    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        return Remove(item.Key);
    }
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        var db = GetRedisDb();
        foreach (var hashKey in db.HashKeys(_redisKey))
        {
            var redisValue = db.HashGet(_redisKey, hashKey);
            yield return new KeyValuePair<TKey, TValue>(Deserialize<TKey>(hashKey.ToString()), Deserialize<TValue>(redisValue.ToString()));
        }
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        yield return GetEnumerator();
    }
    public void AddMultiple(IEnumerable<KeyValuePair<TKey, TValue>> items)
    {
        GetRedisDb()
            .HashSet(_redisKey, items.Select(i => new HashEntry(Serialize(i.Key), Serialize(i.Value))).ToArray());
    }
}

And here are some examples to use it:

// Insert customers to the cache            
var customers = new RedisDictionary<int, Customer>("customers");
customers.Add(100, new Customer() { Id = 100, Name = "John" });
customers.Add(200, new Customer() { Id = 200, Name = "Peter" });

// Or if you have a list of customers retrieved from DB:
IList<Customer> customerListFromDb;
customers.AddMultiple(customerListFromDb.ToDictionary(k => k.Id));

// Query a customer by its id
var customers = new RedisDictionary<int, Customer>("customers");
Customer customer100 = customers[100];

Update (Oct 2015)

A better implementation of these collections can be found on CachingFramework.Redis library.

Here is the code.

thepirat000
  • 12,362
  • 4
  • 46
  • 72
  • thanks a lot any example sample for the same with Customer list values with one key ? – Neo Aug 13 '15 at 07:06
  • Thanks a lot same as my requirement is stored customer values into one customer list and i able to get values from list by passing customer name also – Neo Aug 14 '15 at 05:38
  • unble to get all customer elements using `customers.AddMultiple(customerListFromDb.ToDictionary(k => k.Id)); ` – Neo Aug 14 '15 at 05:42
  • What if Id is autogenerated during Add, and we don't know Id in advance. In that case, how to Add to hash? – Karan Desai May 03 '17 at 03:35
  • You must provide a key (field) to insert an element to a Redis Hash. You could use a _key_ different from your _Id_ – thepirat000 May 03 '17 at 05:32
13

You can use ServiceStack.Redis high-level IRedisTypedClient Typed API for managing rich POCO Types.

First get a typed Redis client for Customers with:

var redisCustomers = redis.As<Customer>();

Which will resolve a high-level typed API for managing Customer POCO's that then lets you persist a single Customer with:

redisCustomers.SetEntry("Customer1", CustomerObject);

Or a list of Customers with:

redisCustomers.Lists["Customers"].AddRange(ListOfCustomer);
mythz
  • 141,670
  • 29
  • 246
  • 390
  • 12
    Be aware of the ServiceStack.Redis [limits for free usage](https://servicestack.net/download#free-quotas) – thepirat000 Aug 12 '15 at 06:20
  • @thepirat000 how to use list using `ServiceStack.Redis`? – Neo Aug 12 '15 at 06:49
  • 1
    Depends on how do you want to access the list. Will you always retrieve the entire list from redis, or do you want to handle items individually? – thepirat000 Aug 12 '15 at 16:21
  • actually both. for filtered values i will use individual for all customer show i want entire list items – Neo Aug 13 '15 at 07:08
  • 5
    NOTE: When I read "ServiceStack.Redis" was not free, I got worried because I read it as "StackExchange.Redis" (which is in fact free MIT license). Maybe someone else could make that mistake, just wanted to add this warning because the names are similar and easy to mistake when reading quickly. – Rick Love Sep 30 '16 at 15:01
3

StackExchange.Redis has already predefined functions to deal with list and set of values.

Get IDatabase object :

string cacheConnection = Utils.Sections.Storage.RedisCache.ConnectionString;

IDatabase cache = ConnectionMultiplexer.Connect(cacheConnection).GetDatabase();

methods of list :

cache.ListLeftPushAsync(key, values) -> push one of list of elements

cache.ListRangeAsync(key, startIndex, endIndex) -> get list of values

cache.KeyExpire(key, timspan)

please package StackExchange.Redis for more methods. You don't need to include any extra nuget package.

Happy Mittal
  • 109
  • 7