Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating Service Stack Redis List

Is there a correct way to update a IRedisList? With the sample code below, I can modify it to remove the list, update the pizza and the re-add the list, but that feels wrong. The command line documentation is pretty thourough, but it's a much bigger project than I though and I'm not entirely sure where to start looking.

    public void UpdatePizza(Pizza pizza)
    {
        using (var redisClient = new RedisClient(Host, Port))
        {
            IRedisTypedClient<Pizza> redis = redisClient.As<Pizza>();

            IRedisList<Pizza> pizzas = redis.Lists["pizzas:live"];

            var toUpdate = pizzas.First(x => x.Id == pizza.Id);

            toUpdate.State = pizza.State;

            //??How to save 
        }                   
    }
like image 827
Joe Avatar asked May 23 '14 20:05

Joe


1 Answers

Avoid Lists:

Unfortunately Redis lists are not really a good choice in this situation. I had the same issue when I started using Redis, they seem like the obvious choice ;). Redis lists are useful if you are using them as a readonly set, or if you just want to pop and push, but not for modifying an item in the middle of the list.

You can "update" items in a Redis list if you know the index of the item, but it requires to remove and re-insert, and it must be by index, which determining is horribly inefficient. It does so by iterating the collection, because there is no native way to do it, and this isn't a good idea. This is a snippet of the IndexOf method of the RedisClientList<T>.

public int IndexOf(T item)
{
    //TODO: replace with native implementation when exists
    var i = 0;
    foreach (var existingItem in this)
    {
        if (Equals(existingItem, item)) return i;
        i++;
    }
    return -1;
}

So to complete your code, it would be:

public void UpdatePizza(Pizza pizza)
{
    using (var redisClient = new RedisClient(Host, Port))
    {
        IRedisTypedClient<Pizza> redis = redisClient.As<Pizza>();
        IRedisList<Pizza> pizzas = redis.Lists["pizzas:live"];
        var toUpdate = pizzas.First(x => x.Id == pizza.Id);
        toUpdate.State = pizza.State;

        // Update by removing & inserting (don't do it!)
        var index = pizzas.IndexOf(toUpdate);
        pizzas.Remove(index);
        pizzas.Insert(index, toUpdate);
    }                   
}

But this isn't a nice way to handle it as I have said. It will retrieve the list of the other pizza objects then iterate over them until it matches the index. And two operations to update! :( Best to avoid lists in this situation.

Solution:

As you are trying to access the pizza by it's Id then you can create a unique pizza key for each object, this will allow you to access the pizza directly. So we might use:

pizzas:live:{Id}

Examples:

Create a pizza

using (var redisClient = new RedisClient())
{
    IRedisTypedClient<Pizza> redis = redisClient.As<Pizza>();
    var pizzaKey = string.Format("pizzas:live:{0}", 123);
    var pizza = new Pizza { Id = 123, Type = "Mushroom", State = "Cooking" };
    redis.SetEntry(pizzaKey, pizza);
}

Get a pizza by Id

using (var redisClient = new RedisClient())
{
    IRedisTypedClient<Pizza> redis = redisClient.As<Pizza>();
    var pizzaKey = string.Format("pizzas:live:{0}", pizza.Id);
    var pizza = redis.GetValue(pizzaKey);
}

Update a pizza by Id (Simply a GET and SET)

using (var redisClient = new RedisClient())
{
    IRedisTypedClient<Pizza> redis = redisClient.As<Pizza>();
    var pizzaKey = string.Format("pizzas:live:{0}", pizza.Id);
    var pizza = redis.GetValue(pizzaKey); // Get
    pizza.State = "Delivery"; // Update
    redis.SetEntry(pizzaKey, pizza); // Save
}

Move to another "list" (maybe: when a pizza changes state)

using (var redisClient = new RedisClient())
{
    var pizzaKey = string.Format("pizzas:live:{0}", pizza.Id);
    var deliveredKey = string.Format("pizzas:delivered:{0}", pizza.Id);
    redisClient.RenameKey(pizzaKey, deliveredKey);
}

Delete a pizza

using (var redisClient = new RedisClient())
{
    var pizzaKey = string.Format("pizzas:live:{0}", pizza.Id);
    redisClient.Remove(pizzaKey);
}

List all the live pizzas

using (var redisClient = new RedisClient())
{
    var livePizzaKeys = redisClient.ScanAllKeys("pizzas:live:*").ToList();
    List<Pizza> livePizzas = redisClient.GetValues<Pizza>(livePizzaKeys);
}

I hope this helps.

like image 143
Scott Avatar answered Oct 18 '22 23:10

Scott