Pular para o conteúdo principal

CRUD utilizando key/value e hash Redis com C#

O texto a seguir mostrará operações e um exmplo de integração com banco NoSQL Redis em uma aplicação console C# utilizando .NET 7 e a biblioteca NuGet StackExchange.Redis.

Redis é um banco open source de armazenamento em memória, útil para comunicação assíncrona e armazenamento de cache. Conheça mais sobre a solução no site oficial!

Estrutura do Projeto

+---Redis
		+---Factory
				+---RedisFactory.cs
		+---Interface
				+---IRedisClient.cs
		+---Models
				+---RedisClient.cs
+---Program.cs

Implementando a biblioteca

Utilizaremos uma simples estrutura de factory para nos conectarmos a uma instância Redis utilizando a biblioteca StackExchange.Redis 2.7.17

IRedisClient.cs

using StackExchange.Redis;

namespace sample_redis_crud.Redis.Interface
{
    public interface IRedisClient
    {
        string Url { get; }
        string Port { get; }
        string Pass { get; }
        ConfigurationOptions? Options { get; }
        ConnectionMultiplexer? Connection { get; }
        IDatabase? Database { get; }
				void SetOptions(string endpoint, string pass);
        void SetConnection(ConfigurationOptions? options);
        void SetDatabase(ConnectionMultiplexer conn);
    }
}

Começando pelo nosso contrato, especificamos todas as propriedades necessárias para a implementação.

RedisClient.cs

using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud.Redis.Models
{
    public class RedisClient : IRedisClient
    {
        public RedisClient(string url, string port, string pass)
        {
            Url = url;
            Port = port;
            Pass = pass;
            SetOptions($"{Url}:{Port}", Pass);
            SetConnection(Options);
            SetDatabase(Connection);
        }
        public string? Url { get; private set; }
        public string? Port { get; private set; }
        public string? Pass { get; private set; }
        public ConfigurationOptions? Options { get; private set; }
        public ConnectionMultiplexer? Connection { get; private set; }
        public IDatabase? Database { get; private set; }

        private  void SetOptions(string endpoint, string pass)
        {
            Options = new ConfigurationOptions
            {
                EndPoints = { endpoint },
                Password = pass,
                AbortOnConnectFail = false
            };
        }

        private void SetConnection(ConfigurationOptions? options)
        {
            if (options != null)
                Connection = ConnectionMultiplexer.Connect(options);
        }

        private void SetDatabase(ConnectionMultiplexer? conn)
        {
            if (conn != null)
                Database = conn.GetDatabase();
        }

    }
}

Na nossa classe, definimos os níveis de acesso e as rotinas dos nossos métodos, dos quais SetOptions define os detalhes da nossa instância Redis (IP e senha de acesso), SetConnection estabelece nossa conexão e SetDatabase retorna nosso banco para realizarmos nossas operações.

RedisFactory.cs

using sample_redis_crud.Redis.Interface;
using sample_redis_crud.Redis.Models;

namespace sample_redis_crud.Redis.Factory
{
    public static class RedisFactory
    {
        public static IRedisClient CreateInstance(string url, string port, string pass)
            => new RedisClient(url, port, pass);
    }
}

Acima, implementamos nossa Factory, na qual retornamos apenas nossa interface IRedisClient, diminuindo o acoplamento do código.

Caso ache interessante, é possível criar uma biblioteca de classes com essa mesma estrutura e utilizá-la como um componente em diversos projetos!

Realizando operações

Para rodar nosso banco Redis, fiz uso de um servidor local em WSL 2 no Windows 10.

Inserindo chaves simples

Na nossa classe Program, criaremos nosso método de Insert de chave e valor.

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");
            redisInstance.Insert("1", "test@email.com");
        }

        static void Insert(this IRedisClient redisInstance, string key, string value)
            => redisInstance.Database?.StringSet(key, value);
    }
}

No código, definimos nossa instância local e, acessando a propriedade Database, utilizamos o método StringSet para inserção Obtemos o seguinte resultado:

127.0.0.1:6379> get 1
"test@email.com"

Inserindo chaves hash

Podemos optar por inserir uma hash, que abriga mais de um valor em uma única chave. A funcionalidade define cada valor como uma HashEntry, possuindo um nome de propriedade e valor.

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");
            var hash = new HashEntry[]
            {
                new HashEntry("ContactId", "123"),
                new HashEntry("ContactName", "test"),
                new HashEntry("ContactEmail", "test@email.com")
            };
            redisInstance.InsertHash("2", hash);
        }

        static void InsertHash(this IRedisClient redisInstance, string key, HashEntry[] hash)
            => redisInstance.Database?.HashSet(key, hash);

    }
}

Utilizamos o método HashSet, que recebe um array HashEntry[], no qual inserimos as propriedades ContactId, ContactName e ContactEmail com seus respectivos valores:

127.0.0.1:6379> hget 2 ContactId
"123"
127.0.0.1:6379> hget 2 ContactName
"test"
127.0.0.1:6379> hget 2 ContactEmail
"test@email.com"

Consultando dados inseridos

Para valores simples, podemos utilizar o método StringGet, enviando a key do valor:

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");
            var value = redisInstance.Get("1");
            Console.WriteLine($"Valor = {value}");
        }

        static string Get(this IRedisClient redisInstance, string key)
            => redisInstance.Database.StringGet(key);
    }
}

Para consulta de hash, usaremos o método HashGet, enviando a key que referencia as propriedades que inserimos:

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");
            var hash = redisInstance.GetHash("2");
            if (hash is null) return;
            foreach(var item in hash)
                Console.WriteLine($"Prop = {item.Name}
Valor = {item.Value}
");
        }

        static HashEntry[]? GetHash(this IRedisClient redisInstance, string key)
            => redisInstance.Database?.HashGetAll(key);
    }
}

Atualizando informações

Para atualizar registros, basta que utilizemos a chave do valor que desejamos atualizar e acionar o método Insert e InsertHash:

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");

            redisInstance.Insert("1", "newtest@email.com");
            var hash = new HashEntry[]
            {
                new HashEntry("ContactId", "1234"),
                new HashEntry("ContactName", "newtest"),
                new HashEntry("ContactEmail", "newtest@email.com")
            };
            redisInstance.InsertHash("2", hash);
        }

        static void Insert(this IRedisClient redisInstance, string key, string value)
            => redisInstance.Database?.StringSet(key, value);
        static void InsertHash(this IRedisClient redisInstance, string key, HashEntry[] hash)
            => redisInstance.Database?.HashSet(key, hash);
    }
}
127.0.0.1:6379> get 1
"newtest@email.com"
127.0.0.1:6379> hget 2 ContactId
"1234"
127.0.0.1:6379> hget 2 ContactName
"newtest"
127.0.0.1:6379> hget 2 ContactEmail
"newtest@email.com"

Deletando informações

Para deletar valores, podemos utilizar o método StringGetDelete, que busca o valor e, em seguida, o deleta do banco:

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");
            var value = redisInstance.Delete("1");
            if (value is not null)
                Console.WriteLine($"{value} Deletado!");
        }

        static string? Delete(this IRedisClient redisInstance, string key)
            => redisInstance.Database?.StringGetDelete(key);
    }
}

Para valores Hash, podemos utilizar o método HashDelete. O HashDelete irá remover uma propriedade que for informada por vez, por isso, criaremos um loop para deletar cada hash existente:

using sample_redis_crud.Redis.Factory;
using sample_redis_crud.Redis.Interface;
using StackExchange.Redis;

namespace sample_redis_crud
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var redisInstance = RedisFactory.CreateInstance("127.0.0.1", "6379", "");
            redisInstance.DeleteHash("2");
        }

        static HashEntry[]? GetHash(this IRedisClient redisInstance, string key)
            => redisInstance.Database?.HashGetAll(key);

        static void DeleteHash(this IRedisClient redisInstance, string key) 
        {
            var hash = redisInstance.GetHash(key);
            if (hash is null) return;
            foreach(var item in hash)
                redisInstance.Database?.HashDelete(key, item.Name);
        } 
    }
}

Conclusão

Utilizar Redis como opção de armazenamento de cache pode auxiliar na comunicação entre serviços e diminuir acoplamento entre as aplicações. Além disso, existe a opção de estabelecer um “prazo de validade” para cada key que inserimos. Basta utilizar o método KeyExpire(key, timeSpan) logo após inserir um registro, permitindo um controle maior do armazenamento interno.

Tratando-se de hash, podemos checar a existência de um certo atributo utilizando o método HashExists(key, atrName), em que atrName é o apelido da hash criada. Dessa forma, a aplicação retornará true em caso positivo.

Sinta-se livre para utilizar técnicas de injeção de dependência na criação de sua instância, estabelecendo boas práticas na arquitetura e design de código de seus serviços!

Confira o repositório do projeto de exemplo: https://github.com/Guilherme-Maciel/sample-redis-crud

Comentários

Postagens mais visitadas deste blog

COMO EMULAR APLICAÇÃO NO CELULAR ANDROID VIA USB? - IONIC CORDOVA

    Muitos computadores, assim como o meu, não possuem uma boa configuração para rodar a AVD do Android Studio para compilar aplicações feitas utilizando Ionic, mas existe uma maneira mais leve de realizar está tarefa a partir da ferramenta Cordova. Para que o processo seja bem sucedido, é necessário que você tenha instalado todos os passos deste artigo . Basta clicar e ser redirecionado. ATIVANDO MODO DESENVOLVEDOR NO APARELHO O modelo que utilizo é o moto g6 play com android versão 9, caso o seu possua parâmetros semelhantes, siga os passos abaixo para habilitar a opção de desenvolvedor: Vá em configurações > S istema > Sobre o dispositivo e clique 7 vezes sobre "Número da versão". Após, irá aparecer um toast dizendo que o modo de desenvolvedor está ativado! Após, volte para "Sistema" e clique em "Opções de desenvolvedor", arraste um pouco para baixo e ative a depuração USB. Isso permitiria que a máquina transfira arquivos de uma forma mais ...

PRIMEIRO PROJETO IONIC USANDO ANGULAR

Neste tutorial, mostrarei como estar criando o seu primeiro projeto IONIC utilizando a tecnologia Angular. Para começarmos, é necessário que você tenha instalado algumas dependências, clicando aqui você pode estar conferindo-as. Agora que tudo está nos trinks, vamos iniciar com o nosso primeiro projeto em IONIC! CRIANDO UMA PASTA DE PROJETOS É importante possuir pastas separadas para cada coisa no seu HD, além de agilizar na hora da busca, deixa uma satisfação maior para trabalhar. Como estudo um número razoável de tecnologias, deixo uma pasta "projetos" no "C:" e crio pastas de acordo com a tecnologia que irei utilizar. Para alguns tipos de projeto, como em PHP que necessita de um programa para ser compilado, isso pode não ser viável, mas daí em diante é apenas questão de adaptação. CRIANDO O PROJETO Criado o seu cantinho para projetos, começaremos com a aplicação. Ela é feita inicialmente pelo terminal, o console do cmd funciona bem para esse trabalho. Um truque ...