How to add Generic List to Redis via StackExchange.Redis?

23,831

Solution 1

StackExchange.Redis is a raw client - it talks in Redis terms only. It does not attempt to be an ORM of any kind. It will, however, store any string or byte[] that you care to throw at it - which means you should have your choice of serializers. JSON would be a reasonable default (Jil is awesome), although we tend to use protocol-buffers ourselves (via protobuf-net).

It you intend to use list semantics, I strongly recommend starting with the List* commands - sets have different semantics to lists - sets are unordered and only store unique values; lists preserve order and allow duplicates.

Solution 2

May be it helps. I also faced the same question at the beginning of my diving into StackExchange.Redis. In my project I created 2 extension methods, which help me serialize/deserialize complex type for Redis database. You may extend them to your needs.

Methods:

    public static class RedisUtils
        {
//Serialize in Redis format:
            public static HashEntry[] ToHashEntries(this object obj)
            {
                PropertyInfo[] properties = obj.GetType().GetProperties();
                return properties.Select(property => new HashEntry(property.Name, property.GetValue(obj).ToString())).ToArray();
            }
    //Deserialize from Redis format
            public static T ConvertFromRedis<T>(this HashEntry[] hashEntries)
            {
                PropertyInfo[] properties = typeof(T).GetProperties();
                var obj = Activator.CreateInstance(typeof(T));
                foreach (var property in properties)
                {
                    HashEntry entry = hashEntries.FirstOrDefault(g => g.Name.ToString().Equals(property.Name));
                    if (entry.Equals(new HashEntry())) continue;
                    property.SetValue(obj, Convert.ChangeType(entry.Value.ToString(), property.PropertyType));
                }
                return (T)obj;
            }
        }

Usage:

var customer = new Customer
{
//Initialization
};

Db.HashSet("customer", customer.ToHashEntries());
Customer result = Db.HashGetAll("customer").ConvertFromRedis<Customer>();

Assert.AreEqual(customer.FirstName, result.FirstName);
Assert.AreEqual(customer.LastName, result.LastName);
Assert.AreEqual(customer.Address1, result.Address1);

Solution 3

An improvement to Andrey Gubal's answer, to handle nullable properties or null values:

public static class RedisUtils
{
    //Serialize in Redis format:
    public static HashEntry[] ToHashEntries(this object obj)
    {
        PropertyInfo[] properties = obj.GetType().GetProperties();
        return properties
            .Where(x=> x.GetValue(obj)!=null) // <-- PREVENT NullReferenceException
            .Select(property => new HashEntry(property.Name, property.GetValue(obj)
            .ToString())).ToArray();
    }

    //Deserialize from Redis format
    public static T ConvertFromRedis<T>(this HashEntry[] hashEntries)
    {
        PropertyInfo[] properties = typeof(T).GetProperties();
        var obj = Activator.CreateInstance(typeof(T));
        foreach (var property in properties)
        {
            HashEntry entry = hashEntries.FirstOrDefault(g => g.Name.ToString().Equals(property.Name));
            if (entry.Equals(new HashEntry())) continue;
            property.SetValue(obj, Convert.ChangeType(entry.Value.ToString(), property.PropertyType));
        }
        return (T)obj;
    }
}

Solution 4

This is an option

public static class StackExchangeRedisExtensions
{

    public static T Get<T>(string key)
    {
        var connect = AzureredisDb.Cache;
        var r = AzureredisDb.Cache.StringGet(key);
        return Deserialize<T>(r);
    }

    public static List<T> GetList<T>(string key)
    {                       
        return (List<T>)Get(key);
    }

    public static void SetList<T>(string key, List<T> list)
    {
        Set(key, list);
    }

    public static object Get(string key)
    {
        return Deserialize<object>(AzureredisDb.Cache.StringGet(key));
    }

    public static void Set(string key, object value)
    {
        AzureredisDb.Cache.StringSet(key, Serialize(value));
    }

    static byte[] Serialize(object o)
    {
        if (o == null)
        {
            return null;
        }

        BinaryFormatter binaryFormatter = new BinaryFormatter();
        using (MemoryStream memoryStream = new MemoryStream())
        {
            binaryFormatter.Serialize(memoryStream, o);
            byte[] objectDataAsStream = memoryStream.ToArray();
            return objectDataAsStream;
        }
    }

    static T Deserialize<T>(byte[] stream)
    {
        if (stream == null)
        {
            return default(T);
        }

        BinaryFormatter binaryFormatter = new BinaryFormatter();
        using (MemoryStream memoryStream = new MemoryStream(stream))
        {
            T result = (T)binaryFormatter.Deserialize(memoryStream);
            return result;
        }
    }
}

AzureredisDb.Cache is СonnectionMultiplexer.Connect and GetDatabase();

Share:
23,831
Shane
Author by

Shane

Software Consultant

Updated on April 26, 2020

Comments

  • Shane
    Shane about 4 years

    For example, if I have a model called Customer

    public class Customer
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Address1 { get; set; }
            public string City { get; set; }
            public string State { get; set; }
        }
    

    Example:

    var customers = new List<Customer>();
    

    How would I added a List of Customer? How would I do this?

     using (var redis = ConnectionMultiplexer.Connect(this.redisServer))
                {
                    var db = redis.GetDatabase();
    
                    db.SetAdd(key, ?????);
    }
    

    I think SetAdd is the right method, but I can't see how to get my generic list of Customer (i.e. List into the format of RedisValue.

  • Shane
    Shane over 9 years
    Understood. So I would maybe serialize my generic list of Customers to Json (via newtonsoft or what not), then I could save as a string and just serialize/deserialize back and forth? I'll check out the List commands.
  • Marc Gravell
    Marc Gravell over 9 years
    @Shane two options - if you want to use the list features of redis, serialize each object separately and send as separate list rows. If you just want "store my list, fetch my list" - then: serialize the list and use the String* redis functions. A redis "string" just means "a single value" - it doesn't need to be a string (if that makes sense)
  • Philip P.
    Philip P. over 9 years
    OR, you could store objects like Customer as hashes, and then have sorted sets to group them into "lists". That's actually cool because you can have same customer be in different "lists" (entirely reasonable) and store them only once. And easy to move between "lists".
  • MickyD
    MickyD about 9 years
    Generally avoid code-only answers. Consider adding a description that helps to explain your code. Thanks
  • MickyD
    MickyD almost 8 years
  • Karan Desai
    Karan Desai about 7 years
    Suppose Customer class asked by OP has a custom object of say Product. In that case can you please explain how to proceed?
  • knocte
    knocte over 5 years
    storing binary data is a really bad approach IMO