Redis as Primary Database in .NET 8 Web API

Introduction

In this article, I'm going to discuss the Redis and use cases on using it as a Primary Database to store our data.

  • Introduction to Redis
  • Redis Datatypes
  • Pros and cons of using Redis as DB
  • Setting up Redis in a Container
  • Using Redis as a Primary database with .NET 8 Web API.

Prerequesities

  • Visual Code
  • .NET 8 SDK
  • Redis Desktop Manager. You can download it from the URL(https://redis.io/resources/tools/)
  • Docker Desktop
  • Packages
    • Microsoft.Extensions.Caching.stackExchangeRedis
    • StackExchange.Redis

Introduction to Redis

Redis is an in-memory datastore, and key-value datastore, and is used mostly for the caching layer. Because Redis stores data in-memory, it allows for very fast read and write operations which are well-suited for low-latency access to the data. You will have questions like the Redis stores the data in-memory but when the system reboots we'll lose the data.

To solve the issue of data persistence, Redis provides different mechanisms such as snapshots and append-only files (AOF). These mechanisms allow you to persistently store the data on disk, ensuring that it can be recovered even after a system reboot. You can learn more about Redis Persistence in theURL

Mostly Redis is used as a Cache layer but we can also use it as a Database to reduce the other call to the complex database systems. Redis using the Document database model where it stores the data in documents mostly in JSON format. You can also know more about from the URL

Redis Datatypes

Redis provides a handful of data types that allow us to solve problems from caching, queuing, and event processing.

  • Strings: representing a sequence of bytes
  • Lists - Lists of strings
  • Sets - Unordered collection of unique string
  • Hashes - record types modeled as collections of field-value pairs

You'll learn more about the data types from the URL

Pro's of using Redis as DB

  • High Performance
  • Versatile DataStructure
  • Low Latency
  • Scalability

Con's of using Redis as DB

  • Data Durability
  • Limited Query Capabilities
  • Memory Constraints

Setting up Redis in a Container

Make sure that, Docker Desktop is running and open Visual Studio 2022.

Choose "Create a New Project" --> Click the "ASP.NET Core Web API" and then click "Next".

create a new project

On the Configure page, enter the Project name and then press "next".

Configure

On the Additional information page, choose the information as per the screenshot and choose "Create".

Additional information

Now, Create "docker-compose.yaml" file to hold the configuration for our Redis and paste the below code.

version: '3.8'
services:
  redis:
    image: redis:alpine
    container_name: redisStudentAPI
    ports:
      - 6379:6379

After that, open the Developer Powershell prompt from the Tools -> Command line -> Developer Powershell.

Then navigate inside the Project folder and enter the command to run the YAML file "docker compose up -d"

command prompt4-.jpg

Open the docker desktop, and navigate to the containers you can see the container created from the YAML file. Also, you can run the 'docker ps' to see the running containers.

container

container redis Student API6-.jpg

To interact with the Redis Server, you need to run the below command with the container ID.

docker exec -it <container_id> /bin/sh

container redis Student API-.jpg

The basic setup of Docker is done and we can able to interact with Redis via the command line.

Now, we are going to see more about the string data types because we are going to store the key, and value pair as strings

Strings

It's the simplest type of value to be associated with a key and a one-to-one mapping between key & value.

We can set the value using the Set<key> <value> and also get the using GET <key>

You can also delete the key using the Del <key>

command prompt

Now, you need to install the necessary packages from the NuGet Package Manager.

Installed

Add the configuration in the Program.cs files

Programs.cs

builder.Services.AddSingleton<IConnectionMultiplexer>(options =>
  ConnectionMultiplexer.Connect(("127.0.0.1:6379")));
builder.Services.AddScoped<IStudentRepository, StudentRepository>();

Create a Models Folder and Create a file for our Model Student.cs then paste the code.

Student.cs

namespace StudentAPIWithRedisDB.Models
{
    public class Student
    {
        public string Id { get; set; } = $"student:{Guid.NewGuid().ToString()}";
        public required string StudentName { get; set; } = string.Empty;

    }
}

Then copy and Paste the below code on the Respection files.

StudentRepository.cs

using StudentAPIWithRedisDB.Models;

namespace StudentAPIWithRedisDB.Data
{
    public interface IStudentRepository
    {
        IEnumerable<Student> GetAllStudents();
        Student? GetStudentById(string id);
        void AddStudent(Student student);
        Student? UpdateStudent(Student student);
        Student? DeleteStudent(string id);
    }
}

StudentRepository.cs is the implementation of the IStudentRespository.cs interface and all its methods.

using StackExchange.Redis;
using StudentAPIWithRedisDB.Models;
using System.Text.Json;

namespace StudentAPIWithRedisDB.Data
{
    public class StudentRepository : IStudentRepository
    {
        private readonly IConnectionMultiplexer _redis;
        public StudentRepository(IConnectionMultiplexer redis) 
        {
            _redis = redis;
        }
        public void AddStudent(Student student)
        {
            if(student == null)
            {
                throw new ArgumentOutOfRangeException(nameof(student));
            }
            var db = _redis.GetDatabase();
            var serializedStudent = JsonSerializer.Serialize(student);
            db.StringSet(student.Id, serializedStudent);
        }

        public Student? DeleteStudent(string id)
        {
            var db = _redis.GetDatabase();
            var student = db.StringGet(id);
            if (student.IsNullOrEmpty)
            {
                return null;
            }
            db.KeyDelete(id);
            return JsonSerializer.Deserialize<Student>(student);
        }

        public IEnumerable<Student> GetAllStudents()
        {
            var db = _redis.GetDatabase();

            var studentKeys = db.Multiplexer.GetServer(_redis.GetEndPoints().First()).Keys(pattern: "student:*");

            var students = new List<Student>();

            foreach (var key in studentKeys)
            {
                var studentJson = db.StringGet(key);
                if (!studentJson.IsNullOrEmpty)
                {
                    var student = JsonSerializer.Deserialize<Student>(studentJson);
                    students.Add(student);
                }
            }

            return students;
        }

        public Student? GetStudentById(string id)
        {
            var db = _redis.GetDatabase();
            var student = db.StringGet(id);
            if(student.IsNullOrEmpty)
            {
                return null;
            }
            return JsonSerializer.Deserialize<Student>(student);
        }

        public Student UpdateStudent(Student student)
        {
            var db = _redis.GetDatabase();
 
            var id = student.Id;
            if (db.KeyExists(id))
            {
                var updatedStudentJson = JsonSerializer.Serialize(student);
                db.StringSet(id, updatedStudentJson);
                return student;
            }
            else
            {
                return null;
            }
        }
    }
}

StudentsController.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using StudentAPIWithRedisDB.Data;
using StudentAPIWithRedisDB.Models;

namespace StudentAPIWithRedisDB.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class StudentsController : ControllerBase
    {
        private readonly IStudentRepository _studentRepository;

        public StudentsController(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        [HttpGet("{Id}", Name = "GetStudentById")]
        public ActionResult<Student> GetStudentById(string Id)
        {
            var student = _studentRepository.GetStudentById(Id);

            if(student == null)
            {
                return NotFound();
            }   
            return Ok(student);
        }

        [HttpPost]
        public ActionResult<Student> AddStudent(Student student)
        {
            _studentRepository.AddStudent(student);
            return CreatedAtRoute(nameof(GetStudentById), new { Id = student.Id }, student);
        }

        [HttpGet(Name = "GetAllStudents")]
        public ActionResult<Student> GetAllStudents()
        {
            var students = _studentRepository.GetAllStudents();
            return Ok(students);
        }

        [HttpDelete("{id}")]
        public ActionResult<Student> DeleteStudent(string id)
        {
            var student = _studentRepository.DeleteStudent(id);
            if(student == null)
            {
                return NotFound();
            }
            return Ok(student);
        }

        [HttpPatch]
        public ActionResult<Student> UpdateStudent(Student student)
        {
            var studentToUpdate = _studentRepository.GetStudentById(student.Id);
            if(studentToUpdate == null)
            {
                return NotFound();
            }
            _studentRepository.UpdateStudent(student);
            return NoContent();
        }
    }
}

Now, run the application you can able to see the endpoints showing on the browser using Swagger.

Swagger-.jpg

Also, it would help if you opened the Redis Desktop Manager to see the data. Initially, it'll have 16 databases without any data.

Redis Desktop Manager-.jpg

Once, you hit the create endpoint it'll automatically create a record in the DB0.

CREATE

For easy testing purposes, I have used the Postman to test the endpoints.

Pass the name only, then it'll automatically create an ID with the name of the student and the suffix with the new Guid

dashboard

And the document is created on the DB0.

connect to redis server 16-.jpg

GetStudentById

For this, you need to copy the ID from the document. Then pass it on the request URL.

my work space

and it returns the value based on the ID.

GetAllStudents

It returns all the documents available in the Database.

get all student 15-.jpg

Update

You need to pass the ID and the updated value. So that, it'll update based on the ID.

Update student name by id -.jpg

Once, you refresh the manager then you'll see the updated value.

connect to redis server 16-.jpg

Delete

For deleting a user, kindly pass the ID, then it'll remove the document from the Student DB.

delete student by id 17-.jpg

And the document is deleted from the file.

redical local

Previously, I had the two documents on the Student's table but now the first one is deleted.

That'll this is a simple way of showing the usage of Redis as a Database in our application. Here, we have reduced the Cache and stored the data directly to the Redis and also retrieved and made changes to it. Likewise, you can also apply the same to your application.