Using the Redis NoSql database with .NET Part 9: starting with the Redis .NET client

Introduction

In the previous post we looked at how Redis can be used as a message broker. Messaging is a technique to enable two disparate systems to communicate with each other. Communication usually happens via data types that most systems understand: strings, bytes and the like. The party sending a message to a channel is called a publisher or sender and the ones consuming the messages are called subscribers or consumers. Any Redis client can subscribe to a channel and get the messages registered on a certain channel. A system that sits in between the publishers and subscribers, i.e. that can create channels, accept messages to those channels and funnel them out to the subscribers is called a message broker. Redis offers a basic but very efficient messaging service. If you see a need for a simple messaging solution in your system without the more complex features of other message brokers then Redis may be all you need, especially if you already use it as a database.

In this post we’ll start looking at using Redis through .NET.

The Redis .NET clients

There are many Redis clients developed for various technologies: Java, Ruby, C++, Python, you name it. The full list of clients is available here and the C# clients are listed in this section. The referenced Redis page marks the recommended clients with a yellow star. You’ll see that at this time there are two recommended clients for .NET:

Both clients can connect to the Redis database and communicate with it using code. This and this thread provide a short description of the single biggest difference between them: the ServiceStack implementation was turned into a commercial product starting from version 4 but it has a number of free-of-charge limits for testing and evaluation purposes. The StackExchange implementation is free of charge even in production environments. The ServiceStack free quotas are listed here:

10 Operations in ServiceStack (i.e. Request DTOs)
10 Database Tables in OrmLite
10 DynamoDB Tables in PocoDynamo
20 Different Types in Redis Client Typed APIs
6000 requests per hour with the Redis Client

ServiceStack is a large web service product and ServiceStack.Redis is one of the clients built upon ServiceStack. The free quotas above do not only concern Redis but other clients as well such as DynamoDb. The quota that’s most relevant in this discussion is the 6000 requests executed with the Redis client per hour. If this is exceeded then you’ll get the following exception message:

The free-quota limit on ‘6000 Redis requests per hour’ has been reached. Please see https://servicestack.net to upgrade to a commercial license.

Both libraries offer a good alternative for .NET developers. In this series we’ll be working with the ServiceStack implementation. However, if you’re interested in the StackExchange version then the relevant documentation includes a number of good examples.

The ServiceStack Redis client

Go ahead and create a console application in Visual Studio. The library can be downloaded from NuGet:

The Redis NuGet client package to be downloaded

The current version at the time of writing this post is 4.5.8. The ServiceStack Redis client offers various abstraction levels of communication with the Redis database. The concrete class RedisClient in the ServiceStack.Redis namespace implements a number of interfaces of which the two most important for us are the following:

  • IRedisNativeClient through deriving from the RedisNativeClient class: an interface with a large number of low-level communication points with Redis, such as Get, Set, Hlen, Sadd, ZCard etc., that reflect the commands GET, SET, HLEN, SADD and ZCARD we saw in earlier posts. Most function names in this interface directly reflect and map to Redis command names one to one. The functions generally accept and return primitive types such as strings, bytes, integers etc. There’s not much room for object orientation and custom objects and we have to take care of serialisation ourselves if we want to work with custom objects e.g. through JSON strings.
  • IRedisClient: offers a higher level of abstraction where the functions are often mapped to Redis commands like in IRedisNativeClient. However, there’s also a range of functions that hide more complex operations like GetRangeWithScoresFromSortedSetByHighestScore that returns an IDictionary of string and double. The corresponding functions in IRedisNativeClient, like ZRangeByScore or ZRangeByScoreWithScores return jagged byte arrays so the caller is responsible for converting the byte arrays to more meaningful data types like strings or numeric values. There’s no room for generics here so we need to implement serialisation back and forth for our custom types with this interface as well.

There’s also a third client-related interface called IRedisTypedClient. It is generic and can be obtained from the IRedisClient interface. It offers the highest level of abstraction of the client interfaces. The generic parameter type can be set to a custom object like Customer, Order, Car or whatever. The .NET client will take care of the serialisation for us. We’ll test each of these interfaces.

We originally started this series by looking at the most simple Redis commands: GET and SET. Let’s see how to do that using the native client interface. Here’s Program.cs to test our first interaction with the Redis server using C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ServiceStack.Redis;

namespace RedisClientTests
{
	class Program
	{
		static void Main(string[] args)
		{
			TryRedisNativeClient();
			Console.ReadKey();
		}

		private static void TryRedisNativeClient()
		{
			string customerOneNameKey = "customer:1:name";

			using (IRedisNativeClient nativeClient = new RedisClient())
			{
				nativeClient.Set(customerOneNameKey, Encoding.UTF8.GetBytes("Great Customer"));
			}

			using (IRedisNativeClient nativeClient = new RedisClient())
			{
				byte[] nameBytes = nativeClient.Get(customerOneNameKey);
				Console.WriteLine(string.Concat(customerOneNameKey, ": ", Encoding.UTF8.GetString(nameBytes)));
			}
		}
	}
}

The Get and Set functions work with byte arrays for the values so it is our responsibility to perform the byte array / string conversion back and forth. It’s immediately visible how low level this interface is.

Note that using RedisClient without any parameters will default to the local host and default port, i.e. the connection string will be 127.0.0.1:6379. The object has various overloads to set the connection string and other parameters.

Let’s try a couple more of these commands. The following code examples test a number of Redis commands that we’ve come through in this series. The function names reflect the commands directly just like in the case of GET and SET above:

private static void TryVariousRedisNativeClientCommands()
{
	string customerOneNameKey = "customer:1:name";

	string hashId = "customer:1";
	string nameKey = "name";
	string idKey = "id";
	string addressKey = "address";
	string name = "Great customer";
	string id = "1";
	string address = "Lyon, France";
	byte[][] hashKeyArray = new byte[3][];
	byte[][] hashValueArray = new byte[3][];
	hashKeyArray[0] = Encoding.UTF8.GetBytes(nameKey);
	hashKeyArray[1] = Encoding.UTF8.GetBytes(idKey);
	hashKeyArray[2] = Encoding.UTF8.GetBytes(addressKey);
	hashValueArray[0] = Encoding.UTF8.GetBytes(name);
	hashValueArray[1] = Encoding.UTF8.GetBytes(id);
	hashValueArray[2] = Encoding.UTF8.GetBytes(address);

	string listId = "to-do";

	string setId = "colours";

	using (IRedisNativeClient nativeClient = new RedisClient())
	{
		long keyExists = nativeClient.Exists(customerOneNameKey);
		Console.WriteLine(string.Concat("Key exists: ", keyExists));
		nativeClient.HMSet(hashId, hashKeyArray, hashValueArray);
		nativeClient.LPush(listId, Encoding.UTF8.GetBytes("shopping"));
		nativeClient.SAdd(setId, Encoding.UTF8.GetBytes("yellow"));
	}

	using (IRedisNativeClient nativeClient = new RedisClient())
	{
		byte[][] hashKeyValues = nativeClient.HMGet(hashId, hashKeyArray);
		foreach (byte[] bytes in hashKeyValues)
		{
			Console.WriteLine(Encoding.UTF8.GetString(bytes));
		}
		byte[] nextToDo = nativeClient.LPop(listId);
		Console.WriteLine(Encoding.UTF8.GetString(nextToDo));
		byte[][] setMembers = nativeClient.SMembers(setId);
		foreach (byte[] setMember in setMembers)
		{
			Console.WriteLine(Encoding.UTF8.GetString(setMember));
		}

	}
}

We test the EXISTS, HMSET, LPUSH, SADD, HMGET, LPOP and SMEMBERS commands. As you see it can be quite cumbersome to work with the client at this level with all these byte arrays, jagged arrays and conversions back and forth. This is a lot of code for achieving relatively little. If you call the above function from Main then you’ll see the following output in the console window:

Key exists: 1
Great customer
1
Lyon, France
shopping
yellow

The only reason to work at this level is if you for some reason need complete control over the commands that are executed against the Redis server. We won’t see any more examples with the native client in this series. If you ever need it then you’ll know how to use IRedisNativeClient. Most functions are easy to map to Redis commands.

We’ll continue with the Redis client in the next post.

You can view all posts related to data storage on this blog here.

Advertisement

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

%d bloggers like this: