Redis Hashes and .NET

Redis Hashes are maps between string fields and string values. So they are great storage for objects. I wrote in Getting Started chapter that strings can store objects, so when I should store objects into hashes or into strings/sets?

I will give a consultants answer: It depends.

There is a very good answer in StackOverflow which describes possible scenarios and their pros/cons. I think in most of cases you should go with the set when storing a regular object, because it allows easy storing/retrieval of whole object. However lets see how we can store and object into Redis Hash and retrieve it. I will again use StackExchange.Redis Nuget package as a DB adapter. I will do mapping manually in this example to provide a better understanding of what needs to be done when working with hashes.

In this example I will use following classes:
public class MyObject
{
   public long Id { get; set; }
   public string Name { get; set; }
   public Address ObjectAdress { get; set; }
 }
 public class Address
 {
     public string StreetAddress { get; set; }
     public string ZipCode { get; set; }
  }
This example is written as console application. I'm storing storedObject into Redis and reading it back into readObject instace.
static void Main(string[] args)
{
   // Setup code
   var redis = ConnectionMultiplexer.Connect("localhost");
   var db = redis.GetDatabase(2);

   var storedObject = new MyObject();
   storedObject.Id = db.StringIncrement("UniqueUserId"); // Fetch unique id for object
   storedObject.Name = "Test Object";
   storedObject.ObjectAdress = new Address() { StreetAddress = "Test Avenue 2", ZipCode = "00100" };

   // Store object
   var propertyList = ConvertToHashEntryList(storedObject);    db.HashSet("user:" + storedObject.Id, propertyList.ToArray());

   // Fetch object
   var readObject = new MyObject();
   readObject.ObjectAdress = new Address();
   var hashEntries = db.HashGetAll("user:" + storedObject.Id);

   // Map HashEntries into object
   // For simplicity I just manually read values from collection with matching names
   readObject.Id = (long)hashEntries.Where(entry => entry.Name == "Id").First().Value;
   readObject.Name = hashEntries.Where(entry => entry.Name == "Name").First().Value;
   readObject.ObjectAdress.StreetAddress = hashEntries.Where(entry => entry.Name == "StreetAddress").First().Value;
   readObject.ObjectAdress.ZipCode = hashEntries.Where(entry => entry.Name == "ZipCode").First().Value;        
}
/// <summary>
/// Example method for converting instance into hashentry list with reflection
/// Use library (like FastMember) for this kind of mapping
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>

private static List<HashEntry> ConvertToHashEntryList(object instance)
{
    var propertiesInHashEntryList = new List<HashEntry>();
    foreach (var property in instance.GetType().GetProperties())
    {
        if(!property.Name.Equals("ObjectAdress"))
        {
            // This is just for an example
            propertiesInHashEntryList.Add(new HashEntry(property.Name, instance.GetType().GetProperty(property.Name).GetValue(instance).ToString()));
        }
        else
        {
            var subPropertyList = ConvertToHashEntryList(instance.GetType().GetProperty(property.Name).GetValue(instance));
            propertiesInHashEntryList.AddRange(subPropertyList);
        }              
    }
    return propertiesInHashEntryList;

After storing the instance Redis will contain item in a hash:
User data viewed with Redis Desktop Manager

Now we can access this objects fields individually. For example if we want to update zip code into 00200 we can just run .NET line
db.HashSet("user:17", new []{new HashEntry("ZipCode", "00200") });
This is easy compared to String version where we need to fetch whole object, deserialize it, change value and serialize it back into db.

Summary

In Redis the datatype is decided by the usage and nature of data. We cannot say that "numeric values should be stored in numeric fields". You have to think about how data is used. Hashes aren't exception here. They provide a good way to store certain kind of data. Using hashes with StackExchange.Redis can be a bit pain in the ass, but building flexible framework around Hashes are worth it.

The best way to predict the future is to implement it.

1 kommentti:

  1. Cool! Call me old school, but is the constant reflection going to take as big of a hit in performance as if you were to just serialize/deserialize the entire object?

    VastaaPoista