Note: phiên bản Tiếng Việt của bài này ở link dưới.

https://duongnt.com/redis-python-csharp-vie

This is the third part of a three-part series about Redis-based distributed caching. You can find the other parts from here.

In the previous two articles, we have looked at how to integrate Redis with a Python or C# .NET application. Today, we will use Python to read data written into Redis with C# and vice versa.

Before we start

Obviously, we need a Redis instance to run all the test code in this article. Please reference the first part for how to set up Redis using Docker.

And as we know, C# and Python are two different languages with their own type system. Because of this, we can only exchange primitive data types. This means while types like string, integer orboolean,… are fine, framework or user-defined classes like DateTime, StringBuilder or StopWatch are off limit. It’s true that we can serialize a custom type before writing it into Redis, but there is no reasonable way to deserialize that data on the other side.

Use Python to read data written with C# .NET

Read a string

This is the C# code to write a string into Redis. This should look familiar if you have read the second part of this series.

await _cache.SetAsync("string", Encoding.UTF8.GetBytes("Example")); Write the string "Example" into Redis cache

We use redis-cli to check the value inside Redis (remember that StackExchangeRedis write data into Redis as hash).

hgetall string

1) "absexp"
2) "-1"
3) "sldexp"
4) "-1"
5) "data"
6) "Example"

Since we don’t care about the expiration settings, we only retrieve the data field from the hash. As mentioned in part one, we can use the hget method in this case. Remember that the returned value is of type bytes.

string_bytes = await redis.hget('string', 'data')
string_value = string_bytes.decode('utf-8')
print(string_value) # Will print "Example"

Or we can retrieve the whole hash with hgetall.

dict = await redis.hgetall('string') # dict is a dictionary

The content of dict is below.

{b'absexp': b'-1', b'sldexp': b'-1', b'data': b'Example'}

The string Example is an entry in the returned dictionary. Note that not only the values, but also the keys of dict are of type bytes. Below is the code to retrieve the value of the data field.

string_key = 'data'.encode('utf-8')
string_bytes = dict[string_key]
print(string_bytes.decode('utf-8')) # Will print "Example"

Read a number

This is the C# code to write an integer into Redis.

await _cache.SetAsync("number", BitConverter.GetBytes(17)); // Write the number 17 into Redis cache

Again, we use redis-cli to check the value inside Redis.

hgetall number

1) "absexp"
2) "-1"
3) "sldexp"
4) "-1"
5) "data"
6) "\x11\x00\x00\x00"

The code to retrieve the bytes representation is simple.

number_bytes = await redis.hget('number', 'data')

To convert number_bytes back into a number, we need its "endianness". As mentioned above, our number is an integer, which is type int in Python. The "endianness", however, depends on the system we run our code on. In my case, I’m using a Windows computer with little-endian.

number_val = int.from_bytes(number_bytes, byteorder='little') 
print(number_val)# Will print 17

Of course, we can also read the whole hash.

dict = await redis.hgetall('number')

# {b'absexp': b'-1', b'sldexp': b'-1', b'data': b'\x11\x00\x00\x00'}

Use C# .NET to read data written with Python

Given the type restriction of StackExchangeRedis, the actual question is how can Python write data in a format that C# .NET code can understand. This means we need to write our data as bytes into a field called data of a hash.

Read a string

First, we need to write a string into Redis from the Python side. We can simply use the hset method in this case. This is because aioredis will automatically encode our value.

await redis.hset('string_python', 'data', 'Example Python')

Or you can be explicit.

str_bytes = 'Example Python'.encode('utf-8')
await redis.hset('string_python', 'data', str_bytes)

Then we can use the C# code below to read that string from Redis.

var stringBytes = await _cache.GetAsync("string_python");
var stringVal = Encoding.UTF8.GetString(stringBytes) # stringVal is "Example"

Read a number

At first glance, the Python code to write a number into Redis seems simple.

await redis.hset('number_python', 'data', 1989)

But this is the result when we try to read number_python from the C# side.

var numberBytes = await _cache.GetAsync("number_python");
var numberVal = BitConverter.ToInt32(numberBytes); // numberVal is 959985969

This is because aioredis will convert the number 1989 into a string, encode that string, then write the encoded string into Redis (we covered this in the first part of this series). To write a number using aioredis, we need to manually convert it into bytes.

number_bytes = (1989).to_bytes(length=4, byteorder='little')
  • length: this is the size of our number. Because we will convert the number into an Int32 on C# side, the length here will be 4 bytes (32 bits).
  • byteorder: as mentioned above, my machine uses little-endian; yours might differ.

Then we can write the bytes value into Redis.

await redis.hset('number_python', 'data', number_bytes)

Now, the C# code should return the value we expected.

var numberBytes = await _cache.GetAsync("number_python");
var numberVal = BitConverter.ToInt32(numberBytes); // numberVal is 1989

How can we set cache entries to expire from Python side?

Perhaps you have noticed that we did not write the absexp and sldexp fields into Redis. Can we just add them back to set the expiration time for our cache entries? Unfortunately, those two fields alone is do not have any effect on the lifetime of any entry. Instead, we need to use the EXPIRE command as documented here.

With aioredis, that command maps to the expire method.

redis.expire(<mykey>, <seconds>)

For example, to set the string_python entry to expire after one hour.

await redis.expire('string_python', 3600)

Conclusion

Although limited to primitive types, we managed to exchange data between a Python app and a C# .NET app via Redis. Interestingly, there is no concept of sliding expiration time in Redis. If so, how does StackExchangeRedis implement that feature? Let’s find out in the bonus part of this series.

A software developer from Vietnam and is currently living in Japan.

Leave a Reply