Skip to main content
The Intent.Redis.Om.Repositories module provides Redis Object Mapping (OM) support, enabling high-performance in-memory data storage with repository pattern implementation.

Overview

This module generates:
  • Redis OM repository implementations
  • Document models for Redis storage
  • Index configuration
  • Search capabilities
  • Distributed caching patterns
  • Real-time data access

Installation

Intent.Redis.Om.Repositories

Key Features

  • In-memory storage - Ultra-fast data access
  • Document database - JSON document storage
  • Full-text search - RediSearch integration
  • Indexing - Secondary indexes for fast queries
  • Aggregations - Data aggregation capabilities
  • Real-time - Immediate data availability

Configuration

Connection Settings

appsettings.json
{
  "Redis": {
    "ConnectionString": "localhost:6379",
    "DefaultDatabase": 0,
    "InstanceName": "MyApp"
  }
}

Azure Redis Cache

appsettings.json
{
  "Redis": {
    "ConnectionString": "your-cache.redis.cache.windows.net:6380,password=your-key,ssl=True,abortConnect=False",
    "DefaultDatabase": 0,
    "InstanceName": "MyApp"
  }
}

Document Generation

Redis Document Model

Customer Document
[Document(StorageType = StorageType.Json, Prefixes = new[] { "Customer" })]
public class CustomerDocument
{
    [RedisIdField]
    [Indexed]
    public string Id { get; set; }

    [Indexed]
    [Searchable]
    public string Name { get; set; }

    [Indexed]
    public string Email { get; set; }

    [Indexed]
    public bool IsVIP { get; set; }

    [Indexed(Sortable = true)]
    public DateTime CreatedDate { get; set; }

    [Indexed]
    public string Status { get; set; }

    public decimal CreditLimit { get; set; }

    public Customer ToEntity()
    {
        return new Customer
        {
            Id = Guid.Parse(Id),
            Name = Name,
            Email = Email,
            IsVIP = IsVIP,
            CreatedDate = CreatedDate,
            Status = Enum.Parse<CustomerStatus>(Status),
            CreditLimit = CreditLimit
        };
    }

    public static CustomerDocument FromEntity(Customer entity)
    {
        return new CustomerDocument
        {
            Id = entity.Id.ToString(),
            Name = entity.Name,
            Email = entity.Email,
            IsVIP = entity.IsVIP,
            CreatedDate = entity.CreatedDate,
            Status = entity.Status.ToString(),
            CreditLimit = entity.CreditLimit
        };
    }
}

Indexing Options

Index Attributes
// Indexed - Creates a secondary index
[Indexed]
public string Email { get; set; }

// Searchable - Enables full-text search
[Searchable]
public string Description { get; set; }

// Sortable - Allows sorting in queries
[Indexed(Sortable = true)]
public DateTime CreatedDate { get; set; }

// Aggregatable - Enables aggregations
[Indexed(Aggregatable = true)]
public decimal Amount { get; set; }

// Normalized - Case-insensitive search
[Searchable(Normalize = true)]
public string Name { get; set; }

Repository Implementation

Base Repository

Redis Repository Base
public abstract class RedisRepositoryBase<TDomain, TDocument>
{
    protected readonly IRedisCollection<TDocument> _collection;
    protected readonly RedisConnectionProvider _connectionProvider;

    protected RedisRepositoryBase(RedisConnectionProvider connectionProvider)
    {
        _connectionProvider = connectionProvider;
        _collection = connectionProvider.RedisCollection<TDocument>();
    }

    public virtual async Task AddAsync(TDomain entity)
    {
        var document = ConvertToDocument(entity);
        await _collection.InsertAsync(document);
    }

    public virtual async Task UpdateAsync(TDomain entity)
    {
        var document = ConvertToDocument(entity);
        await _collection.UpdateAsync(document);
    }

    public virtual async Task RemoveAsync(string id)
    {
        await _collection.DeleteAsync(id);
    }

    public virtual async Task<TDomain?> FindByIdAsync(string id)
    {
        var document = await _collection.FindByIdAsync(id);
        return document != null ? ConvertToEntity(document) : default;
    }

    public virtual async Task<List<TDomain>> FindAllAsync()
    {
        var documents = await _collection.ToListAsync();
        return documents.Select(ConvertToEntity).ToList();
    }

    protected abstract TDocument ConvertToDocument(TDomain entity);
    protected abstract TDomain ConvertToEntity(TDocument document);
}

Entity Repository

Customer Repository
public interface ICustomerRepository
{
    Task<Customer?> FindByIdAsync(Guid id);
    Task<Customer?> FindByEmailAsync(string email);
    Task<List<Customer>> FindVIPCustomersAsync();
    Task<List<Customer>> SearchByNameAsync(string searchTerm);
    Task AddAsync(Customer customer);
    Task UpdateAsync(Customer customer);
    Task RemoveAsync(Guid id);
}

public class CustomerRepository : RedisRepositoryBase<Customer, CustomerDocument>, ICustomerRepository
{
    public CustomerRepository(RedisConnectionProvider connectionProvider) 
        : base(connectionProvider)
    {
    }

    public async Task<Customer?> FindByIdAsync(Guid id)
    {
        return await FindByIdAsync(id.ToString());
    }

    public async Task<Customer?> FindByEmailAsync(string email)
    {
        var results = await _collection
            .Where(x => x.Email == email)
            .ToListAsync();
        
        return results.Select(ConvertToEntity).FirstOrDefault();
    }

    public async Task<List<Customer>> FindVIPCustomersAsync()
    {
        var results = await _collection
            .Where(x => x.IsVIP == true)
            .ToListAsync();
        
        return results.Select(ConvertToEntity).ToList();
    }

    public async Task<List<Customer>> SearchByNameAsync(string searchTerm)
    {
        var results = await _collection
            .Where(x => x.Name == searchTerm)
            .ToListAsync();
        
        return results.Select(ConvertToEntity).ToList();
    }

    public async Task RemoveAsync(Guid id)
    {
        await RemoveAsync(id.ToString());
    }

    protected override CustomerDocument ConvertToDocument(Customer entity)
    {
        return CustomerDocument.FromEntity(entity);
    }

    protected override Customer ConvertToEntity(CustomerDocument document)
    {
        return document.ToEntity();
    }
}

Usage Examples

Basic CRUD

CRUD Operations
public class CustomerService
{
    private readonly ICustomerRepository _repository;

    public async Task<Customer> CreateCustomerAsync(string name, string email)
    {
        var customer = new Customer(name, email);
        await _repository.AddAsync(customer);
        return customer;
    }

    public async Task<Customer?> GetCustomerAsync(Guid id)
    {
        return await _repository.FindByIdAsync(id);
    }

    public async Task UpdateCustomerAsync(Guid id, string name)
    {
        var customer = await _repository.FindByIdAsync(id);
        if (customer != null)
        {
            customer.UpdateName(name);
            await _repository.UpdateAsync(customer);
        }
    }

    public async Task DeleteCustomerAsync(Guid id)
    {
        await _repository.RemoveAsync(id);
    }
}

Advanced Queries

Advanced Queries
// Filter queries
var activeCustomers = await _collection
    .Where(x => x.Status == "Active" && x.IsVIP == true)
    .ToListAsync();

// Range queries
var recentCustomers = await _collection
    .Where(x => x.CreatedDate >= DateTime.UtcNow.AddDays(-30))
    .ToListAsync();

// Full-text search
var searchResults = await _collection
    .Where(x => x.Name == "John*") // Wildcard search
    .ToListAsync();

// Sorting
var sortedCustomers = await _collection
    .OrderBy(x => x.CreatedDate)
    .ToListAsync();

// Pagination
var page = await _collection
    .Skip(pageNo * pageSize)
    .Take(pageSize)
    .ToListAsync();

// Combined query
var results = await _collection
    .Where(x => x.IsVIP == true && x.Status == "Active")
    .OrderByDescending(x => x.CreatedDate)
    .Skip(0)
    .Take(20)
    .ToListAsync();

Aggregations

Aggregations
// Count
var vipCount = await _collection
    .Where(x => x.IsVIP == true)
    .CountAsync();

// Group by
var aggregation = await _collection
    .GroupBy(x => x.Status)
    .Select(g => new 
    { 
        Status = g.Key, 
        Count = g.Count() 
    })
    .ToListAsync();

// Sum/Average
var stats = await _collection
    .GroupBy(x => x.Status)
    .Select(g => new
    {
        Status = g.Key,
        TotalCredit = g.Sum(x => x.CreditLimit),
        AverageCredit = g.Average(x => x.CreditLimit)
    })
    .ToListAsync();

Caching Patterns

Cache-Aside Pattern

Cache-Aside
public class CustomerService
{
    private readonly ICustomerRepository _redisRepository;
    private readonly ICustomerRepository _dbRepository;

    public async Task<Customer?> GetCustomerAsync(Guid id)
    {
        // Try cache first
        var customer = await _redisRepository.FindByIdAsync(id);
        
        if (customer != null)
            return customer;

        // Cache miss - get from database
        customer = await _dbRepository.FindByIdAsync(id);
        
        if (customer != null)
        {
            // Populate cache
            await _redisRepository.AddAsync(customer);
        }

        return customer;
    }

    public async Task UpdateCustomerAsync(Customer customer)
    {
        // Update database
        await _dbRepository.UpdateAsync(customer);
        
        // Invalidate or update cache
        await _redisRepository.UpdateAsync(customer);
    }
}

Write-Through Cache

Write-Through
public async Task CreateCustomerAsync(Customer customer)
{
    // Write to database
    await _dbRepository.AddAsync(customer);
    await _dbRepository.UnitOfWork.SaveChangesAsync();
    
    // Write to cache
    await _redisRepository.AddAsync(customer);
}

TTL (Time To Live)

TTL
public async Task SetWithExpirationAsync(Customer customer, TimeSpan expiration)
{
    var document = CustomerDocument.FromEntity(customer);
    await _collection.InsertAsync(document, expiration);
}

// Example: Cache for 1 hour
await SetWithExpirationAsync(customer, TimeSpan.FromHours(1));

Session Management

Session Storage
[Document(StorageType = StorageType.Json, Prefixes = new[] { "Session" })]
public class UserSessionDocument
{
    [RedisIdField]
    public string SessionId { get; set; }

    [Indexed]
    public string UserId { get; set; }

    public string UserName { get; set; }
    public List<string> Roles { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime ExpiresAt { get; set; }
}

public class SessionService
{
    private readonly IRedisCollection<UserSessionDocument> _sessions;

    public async Task<string> CreateSessionAsync(
        string userId, 
        string userName, 
        List<string> roles)
    {
        var session = new UserSessionDocument
        {
            SessionId = Guid.NewGuid().ToString(),
            UserId = userId,
            UserName = userName,
            Roles = roles,
            CreatedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddHours(24)
        };

        await _sessions.InsertAsync(
            session, 
            TimeSpan.FromHours(24));

        return session.SessionId;
    }

    public async Task<UserSessionDocument?> GetSessionAsync(string sessionId)
    {
        return await _sessions.FindByIdAsync(sessionId);
    }

    public async Task InvalidateSessionAsync(string sessionId)
    {
        await _sessions.DeleteAsync(sessionId);
    }
}

Performance Optimization

Index Design

Index only the fields you query frequently. Over-indexing can slow down writes.

Connection Pooling

Reuse Redis connections using a connection provider pattern.

Batch Operations

Use pipeline or batch operations for multiple commands.

Data Modeling

Design documents to minimize the need for joins or multiple queries.

Batch Operations

Batch Insert
public async Task BulkInsertAsync(List<Customer> customers)
{
    var documents = customers.Select(CustomerDocument.FromEntity);
    
    // Insert all in parallel
    var tasks = documents.Select(doc => _collection.InsertAsync(doc));
    await Task.WhenAll(tasks);
}

Monitoring

Performance Monitoring
public class RedisMetrics
{
    private readonly ILogger _logger;

    public async Task<T> ExecuteWithMetricsAsync<T>(
        Func<Task<T>> operation,
        string operationName)
    {
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            var result = await operation();
            stopwatch.Stop();
            
            _logger.LogInformation(
                "Redis {Operation} completed in {ElapsedMs}ms",
                operationName,
                stopwatch.ElapsedMilliseconds);
            
            return result;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            
            _logger.LogError(ex,
                "Redis {Operation} failed after {ElapsedMs}ms",
                operationName,
                stopwatch.ElapsedMilliseconds);
            
            throw;
        }
    }
}

Best Practices

  1. Use appropriate data structures - Choose Hash, Set, List, or JSON based on your use case
  2. Set expiration times - Prevent memory bloat with TTL on cached data
  3. Index wisely - Only index fields you query frequently
  4. Monitor memory usage - Redis is in-memory; monitor usage carefully
  5. Handle connection failures - Implement retry logic and circuit breakers
  6. Use pipelining - Batch commands to reduce round trips

Use Cases

  • Caching - High-speed data cache
  • Session storage - User sessions and temporary data
  • Real-time analytics - Counters, leaderboards, statistics
  • Rate limiting - API throttling and quota management
  • Message queues - Pub/sub and task queues
  • Search - Fast full-text search with RediSearch

Additional Resources

Build docs developers (and LLMs) love