Noundry.Tuxedo

Modern ORM combining the power of Dapper with additional functionality for rapid data access. High-performance, lightweight, and developer-friendly for .NET applications.

90%+
Code Coverage
4
Database Providers
High
Performance
CRUD
Built-In

Why Choose Noundry.Tuxedo?

Built on proven Dapper performance with modern .NET features and developer productivity enhancements.

High Performance

Built on Dapper's proven performance foundation with optimized query execution and minimal overhead.

Multi-Database Support

Support for SQL Server, PostgreSQL, MySQL, and SQLite with consistent API across all providers.

Built-In CRUD

Complete CRUD operations with conventions-based mapping, async support, and transaction management.

Get Started in 5 Minutes

Install Noundry.Tuxedo and start building high-performance data access layers.

1. Installation

$ dotnet add package Noundry.Tuxedo

2. Configure Services

builder.Services.AddTuxedoSqlServer(connectionString);

3. Inject & Use

await _connection.GetAsync<User>(id);
UserService.cs
using Noundry.Tuxedo;

public class UserService
{
    private readonly IDbConnection _connection;

    public UserService(IDbConnection connection)
    {
        _connection = connection;
    }

    // Get by ID - Simple and fast
    public async Task<User?> GetByIdAsync(int id)
    {
        return await _connection.GetAsync<User>(id);
    }

    // Create new user
    public async Task<int> CreateAsync(User user)
    {
        return await _connection.InsertAsync(user);
    }

    // Update existing user
    public async Task<bool> UpdateAsync(User user)
    {
        return await _connection.UpdateAsync(user);
    }

    // Delete user
    public async Task<bool> DeleteAsync(int id)
    {
        return await _connection.DeleteAsync<User>(id);
    }
}

Supported Database Providers

Choose your database provider with consistent API across all supported platforms.

Microsoft SQL Server

Full-featured SQL Server support with advanced features and optimizations.

Connection Pooling

Optimized connection management

Advanced Types

Support for SQL Server specific data types

Bulk Operations

High-performance bulk insert and update

// Program.cs - SQL Server configuration
builder.Services.AddTuxedoSqlServer(connectionString);

// Or with options
builder.Services.AddTuxedoSqlServer(connectionString, options =>
{
    options.CommandTimeout = 30;
    options.EnableRetryOnFailure = true;
    options.MaxRetryCount = 3;
});

// Connection string example
var connectionString = "Server=localhost;Database=MyApp;Integrated Security=true;TrustServerCertificate=true;";

PostgreSQL

Open-source PostgreSQL support with advanced features and JSON support.

JSON Support

Native JSON and JSONB column support

Arrays & Custom Types

PostgreSQL-specific data types

Full-Text Search

Advanced search capabilities

// Program.cs - PostgreSQL configuration
builder.Services.AddTuxedoPostgreSQL(connectionString);

// Connection string example
var connectionString = "Host=localhost;Database=myapp;Username=postgres;Password=password";

// JSON column example
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // JSON column for flexible data
    [Column(TypeName = "jsonb")]
    public Dictionary<string, object> Metadata { get; set; }
}

MySQL

Popular open-source database with excellent performance and wide hosting support.

Wide Compatibility

Works with MySQL 5.7+ and MariaDB

Cloud Ready

Amazon RDS, Azure Database, Google Cloud SQL

Cost Effective

Excellent price-performance ratio

// Program.cs - MySQL configuration
builder.Services.AddTuxedoMySQL(connectionString);

// Connection string examples
var localConnection = "Server=localhost;Database=myapp;Uid=root;Pwd=password;";
var cloudConnection = "Server=mysql.aws.com;Database=prod;Uid=user;Pwd=secret;SslMode=Required;";

// Model example with MySQL-specific attributes
public class Order
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    
    [Column(TypeName = "VARCHAR(100)")]
    public string OrderNumber { get; set; }
    
    [Column(TypeName = "DECIMAL(10,2)")]
    public decimal Total { get; set; }
}

SQLite

Lightweight, file-based database perfect for development, testing, and small applications.

Zero Configuration

No server setup required

Perfect for Testing

Fast in-memory database for unit tests

Cross-Platform

Works everywhere .NET runs

// Program.cs - SQLite configuration
builder.Services.AddTuxedoSQLite(connectionString);

// Connection string examples
var fileDb = "Data Source=myapp.db";
var memoryDb = "Data Source=:memory:";

// Perfect for testing
public class TestFixture
{
    private IDbConnection _connection;
    
    [SetUp]
    public void Setup()
    {
        _connection = new TuxedoConnection("Data Source=:memory:");
    }
    
    [Test]
    public async Task CanInsertAndRetrieveUser()
    {
        var user = new User { Name = "Test User" };
        var id = await _connection.InsertAsync(user);
        var retrieved = await _connection.GetAsync<User>(id);
        
        retrieved.Assert().IsNotNull();
    }
}

Complete CRUD Operations

Learn all the essential data access patterns with Noundry.Tuxedo.

Basic CRUD Operations

Simple Create, Read, Update, Delete operations with automatic SQL generation.

Convention-based mapping
Automatic SQL generation
Async/await support
Strong typing throughout
public class ProductService
{
    private readonly IDbConnection _connection;

    // CREATE - Insert new product
    public async Task<int> CreateProductAsync(Product product)
    {
        return await _connection.InsertAsync(product);
    }

    // READ - Get by ID
    public async Task<Product?> GetProductAsync(int id)
    {
        return await _connection.GetAsync<Product>(id);
    }

    // READ - Get all
    public async Task<IEnumerable<Product>> GetAllProductsAsync()
    {
        return await _connection.GetAllAsync<Product>();
    }

    // UPDATE - Update existing
    public async Task<bool> UpdateProductAsync(Product product)
    {
        return await _connection.UpdateAsync(product);
    }

    // DELETE - Remove by ID
    public async Task<bool> DeleteProductAsync(int id)
    {
        return await _connection.DeleteAsync<Product>(id);
    }
}

Advanced Queries & Custom SQL

Execute custom SQL queries with parameters, paging, and complex joins.

Custom SQL with parameters
Paged result support
Complex joins and aggregations
Transaction support
// Custom query with parameters
public async Task<IEnumerable<Product>> GetActiveProductsAsync(int categoryId)
{
    var sql = @"
        SELECT p.*, c.Name as CategoryName 
        FROM Products p 
        INNER JOIN Categories c ON p.CategoryId = c.Id
        WHERE p.IsActive = @IsActive 
        AND (@CategoryId IS NULL OR p.CategoryId = @CategoryId)
        ORDER BY p.CreatedAt DESC";
        
    return await _connection.QueryAsync<Product>(sql, new 
    { 
        IsActive = true, 
        CategoryId = categoryId == 0 ? null : categoryId 
    });
}

// Paged results for large datasets
public async Task<PagedResult<User>> GetUsersPagedAsync(int page, int pageSize)
{
    return await _connection.GetPagedAsync<User>(page, pageSize);
}

// Execute scalar queries
public async Task<int> GetProductCountAsync(int categoryId)
{
    var sql = "SELECT COUNT(*) FROM Products WHERE CategoryId = @CategoryId";
    return await _connection.ExecuteScalarAsync<int>(sql, new { CategoryId = categoryId });
}

Transaction Management

Ensure data consistency with built-in transaction support and rollback capabilities.

Automatic transaction management
Rollback on errors
Nested transaction support
Using statement pattern
// Transfer funds with transaction
public async Task TransferFundsAsync(int fromId, int toId, decimal amount)
{
    using var transaction = await _connection.BeginTransactionAsync();
    
    try
    {
        // Debit from source account
        await _connection.ExecuteAsync(
            "UPDATE Accounts SET Balance = Balance - @Amount WHERE Id = @Id", 
            new { Amount = amount, Id = fromId }, 
            transaction);
        
        // Credit to destination account
        await _connection.ExecuteAsync(
            "UPDATE Accounts SET Balance = Balance + @Amount WHERE Id = @Id", 
            new { Amount = amount, Id = toId }, 
            transaction);
        
        // Log the transaction
        await _connection.InsertAsync(new TransactionLog
        {
            FromAccountId = fromId,
            ToAccountId = toId,
            Amount = amount,
            TransactionDate = DateTime.UtcNow
        }, transaction);
        
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

Model Mapping & Attributes

Use attributes to customize table mapping, column names, and database behavior.

Convention-based mapping
Attribute-based customization
Computed columns
Relationship mapping
using Noundry.Tuxedo.Contrib;
using System.ComponentModel.DataAnnotations;

[Table("Products")]
public class Product
{
    [Key]
    [ExplicitKey] // For manually assigned IDs
    public int Id { get; set; }
    
    [Required]
    [StringLength(100)]
    public string Name { get; set; }
    
    [Column("product_price")] // Custom column name
    public decimal Price { get; set; }
    
    [ForeignKey("CategoryId")]
    public int CategoryId { get; set; }
    
    [Computed] // Not included in INSERT/UPDATE
    public string CategoryName { get; set; }
    
    [Write(false)] // Read-only property
    public DateTime CreatedAt { get; set; }
}

Dynamic Query Builder

Build complex queries dynamically with LINQ-style expressions and fluent API.

Fluent Query Building

Dynamic Filters

Build queries with conditional WHERE clauses

Complex Joins

Inner, Left, Right joins with multiple tables

Sorting & Pagination

Dynamic ordering and efficient pagination

// Dynamic query building
using Noundry.Tuxedo.QueryBuilder;

var query = _connection.QueryBuilder<Product>()
    .Where(p => p.IsActive)
    .Join<Category>(p => p.CategoryId, c => c.Id)
    .Select(p => new {
        p.Name,
        p.Price,
        CategoryName = p.Category.Name
    })
    .OrderBy(p => p.Name)
    .Take(50);

var results = await query.ToListAsync();

Advanced Search with Dynamic Filters

public async Task<IEnumerable<Product>> SearchProductsAsync(
    string? searchTerm = null,
    decimal? minPrice = null,
    decimal? maxPrice = null,
    int? categoryId = null,
    string sortBy = "Name",
    int page = 1,
    int pageSize = 20)
{
    var query = _connection.QueryBuilder<Product>()
        .Where(p => p.IsActive);
    
    // Dynamic search term
    if (!string.IsNullOrEmpty(searchTerm))
    {
        query = query.Where(p => p.Name.Contains(searchTerm) || 
                             p.Description.Contains(searchTerm));
    }
    
    // Price range filters
    if (minPrice.HasValue)
        query = query.Where(p => p.Price >= minPrice.Value);
        
    if (maxPrice.HasValue)
        query = query.Where(p => p.Price <= maxPrice.Value);
    
    // Category filter
    if (categoryId.HasValue)
        query = query.Where(p => p.CategoryId == categoryId.Value);
    
    // Dynamic sorting
    query = sortBy.ToLower() switch
    {
        "price" => query.OrderBy(p => p.Price),
        "date" => query.OrderByDescending(p => p.CreatedDate),
        _ => query.OrderBy(p => p.Name)
    };
    
    // Pagination
    return await query
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();
}

High-Performance Bulk Operations

Process thousands of records efficiently with built-in bulk operations and batch processing.

Bulk Insert & Update

// Bulk insert thousands of records
var products = GenerateProducts(10000);

await _connection.BulkInsertAsync(products);

// Bulk update with conditions
await _connection.BulkUpdateAsync<Product>(
    p => p.CategoryId == 1,
    new { IsDiscounted = true });

// Batch processing with progress
await _connection.BatchProcessAsync(
    products, 
    batchSize: 1000,
    processor: batch => ProcessBatch(batch));

Performance Comparison

Single Insert (10,000 records) 45.2s
Bulk Insert (10,000 records) 1.8s
Individual Updates (5,000 records) 28.7s
Bulk Update (5,000 records) 0.9s
25x faster

with bulk operations

Resiliency, Caching & Monitoring

Built-in fault tolerance, intelligent caching, and comprehensive diagnostics for production-ready applications.

Resiliency

Retry Policies

Configurable exponential backoff

Circuit Breaker

Fail fast on consecutive errors

Timeout Handling

Configurable command timeouts

Intelligent Caching

Query Result Cache

Automatic query result caching

Cache Invalidation

Smart cache invalidation strategies

Distributed Cache

Redis and in-memory providers

Diagnostics

Query Metrics

Execution time and performance

Health Checks

Database connectivity monitoring

Logging Integration

Structured logging with context

Complete E-commerce Data Service

public class EcommerceService
{
    private readonly IDbConnection _connection;

    // Get products with category information
    public async Task<IEnumerable<ProductWithCategory>> GetProductsWithCategoryAsync()
    {
        var sql = @"
            SELECT p.*, c.Name as CategoryName, c.Description as CategoryDescription
            FROM Products p
            INNER JOIN Categories c ON p.CategoryId = c.Id
            WHERE p.IsActive = 1
            ORDER BY p.Name";
            
        return await _connection.QueryAsync<ProductWithCategory>(sql);
    }

    // Create order with items (transaction)
    public async Task<int> CreateOrderWithItemsAsync(Order order, List<OrderItem> items)
    {
        using var transaction = await _connection.BeginTransactionAsync();
        
        try
        {
            // Insert order
            var orderId = await _connection.InsertAsync(order, transaction);
            
            // Insert order items
            foreach (var item in items)
            {
                item.OrderId = orderId;
                await _connection.InsertAsync(item, transaction);
                
                // Update product stock
                await _connection.ExecuteAsync(
                    "UPDATE Products SET StockQuantity = StockQuantity - @Quantity WHERE Id = @ProductId",
                    new { item.Quantity, item.ProductId },
                    transaction);
            }
            
            await transaction.CommitAsync();
            return orderId;
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

Resiliency Configuration

// Program.cs - Resiliency setup
builder.Services.AddTuxedoSqlServer(connectionString, options =>
{
    // Retry policy configuration
    options.EnableRetryOnFailure = true;
    options.MaxRetryCount = 3;
    options.MaxRetryDelay = TimeSpan.FromSeconds(30);
    
    // Circuit breaker
    options.CircuitBreakerThreshold = 5;
    options.CircuitBreakerDuration = TimeSpan.FromSeconds(60);
    
    // Timeout settings
    options.CommandTimeout = TimeSpan.FromSeconds(30);
    
    // Health checks
    options.EnableHealthChecks = true;
});

// Add caching with Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

builder.Services.AddTuxedoCache(cache =>
{
    cache.DefaultExpiration = TimeSpan.FromMinutes(15);
    cache.EnableDistributedCache = true;
});

Diagnostics & Monitoring

// Diagnostics service
public class ProductService
{
    private readonly IDbConnection _connection;
    private readonly ITuxedoMetrics _metrics;
    private readonly ILogger _logger;

    public async Task<Product> GetProductAsync(int id)
    {
        using var activity = _metrics.StartActivity("GetProduct");
        activity?.SetTag("product.id", id.ToString());
        
        try
        {
            // Cached query with metrics
            var product = await _connection
                .QueryCachedAsync<Product>(
                    "SELECT * FROM Products WHERE Id = @id",
                    new { id },
                    cacheKey: $"product:{id}",
                    expiration: TimeSpan.FromMinutes(30));
            
            _metrics.IncrementCounter("products.retrieved");
            _logger.LogInformation("Retrieved product {ProductId}", id);
            
            return product;
        }
        catch (Exception ex)
        {
            _metrics.IncrementCounter("products.errors");
            _logger.LogError(ex, "Error retrieving product {ProductId}", id);
            throw;
        }
    }
}

Database Schema Management with Bowtie

Automatically create and update database schemas from your .NET models using Noundry.Tuxedo.Bowtie - attribute-driven, declarative database management.

Installation & Key Features

$ dotnet add package Noundry.Tuxedo.Bowtie

Attribute-Based Models

Define schema using C# attributes on your model classes

Automatic Synchronization

Automatically create/update tables, indexes, and constraints

Multi-Database Support

SQL Server, PostgreSQL, MySQL, SQLite with unified API

Service Configuration

// Program.cs - Bowtie setup
builder.Services.AddNoundryTuxedoBowtie();

var app = builder.Build();

// Synchronize database schema in development
if (app.Environment.IsDevelopment())
{
    await app.Services.SynchronizeDatabaseAsync(
        connectionString: "Data Source=app.db",
        provider: DatabaseProvider.SQLite
    );
}

// Or for SQL Server
await app.Services.SynchronizeDatabaseAsync(
    connectionString: "Server=.;Database=MyApp;Integrated Security=true;",
    provider: DatabaseProvider.SqlServer
);

Attribute-Based Model

[Table("Products")]
public class Product
{
    [Key]
    public int Id { get; set; }

    [Column(MaxLength = 200)]
    [Index("IX_Products_Name")]
    public string Name { get; set; }

    [Column(MaxLength = 1000)]
    public string? Description { get; set; }

    [CheckConstraint("Price > 0")]
    public decimal Price { get; set; }

    [ForeignKey("Categories")]
    [Index("IX_Products_CategoryId")]
    public int CategoryId { get; set; }

    [Column(DefaultValue = true)]
    public bool IsActive { get; set; }

    [Column(DefaultValue = "GETUTCDATE()")]
    public DateTime CreatedDate { get; set; }
}

// PostgreSQL-specific example
[Table("Documents")]
public class Document
{
    [Key]
    public int Id { get; set; }

    // GIN index for JSON searches
    [Index("IX_Documents_Content_GIN", IndexType.GIN)]
    public string JsonContent { get; set; }
}

CLI Commands & Usage

# Synchronize database schema
$ bowtie sync --connection "Data Source=app.db" --provider sqlite
✨ Database schema synchronized successfully
# Generate DDL scripts
$ bowtie generate --output schema.sql
📄 Generated DDL scripts for review
# Validate model compatibility
$ bowtie validate --models MyApp.Models
✅ All models are valid
# Programmatic usage
SynchronizeDatabaseAsync() - Sync schema
GenerateDdlScriptsAsync() - Generate SQL
ValidateModels() - Validate schema

Exception Translation

Translate database-specific exceptions into meaningful, typed exceptions with detailed contextual information.

🎯 Five Exception Types

  • UniqueConstraintException - Unique violations with column/constraint names
  • ForeignKeyException - Foreign key violations with table references
  • DataTruncationException - Data too large for column with max length
  • ConnectionException - Database connectivity issues
  • TransactionException - Transaction-related failures

✨ Key Features

  • • Auto-detect database provider (SQL Server, PostgreSQL, MySQL, SQLite)
  • • Extract column names, constraint names, and table references
  • • Composable with other Tuxedo wrappers (Auditor, Resiliency)
  • • Minimal performance overhead with pattern matching

Installation

$ dotnet add package Noundry.Tuxedo.Exceptions

Basic Usage

using Noundry.Tuxedo.Exceptions;

// Auto-detect database provider
var wrappedConn = connection.WithExceptionTranslation();

try
{
    await wrappedConn.InsertAsync(user);
}
catch (UniqueConstraintException ex)
{
    if (ex.ColumnName?.Contains("Email") == true)
        return "Email already registered";
}
catch (ForeignKeyException ex)
{
    return $"Cannot delete: referenced by {ex.ReferencingTable}";
}

Real-World Example: User Registration

public class UserService
{
    public async Task<Result> RegisterUserAsync(User user)
    {
        var conn = _connection.WithExceptionTranslation();

        try
        {
            await conn.InsertAsync(user);
            return Result.Success();
        }
        catch (UniqueConstraintException ex)
        {
            if (ex.ColumnName?.Contains("Email") == true)
                return Result.Error("Email address is already registered");

            if (ex.ColumnName?.Contains("Username") == true)
                return Result.Error("Username is already taken");

            return Result.Error("User already exists");
        }
        catch (DataTruncationException ex)
        {
            return Result.Error($"{ex.ColumnName} is too long (max {ex.MaxLength})");
        }
    }
}

Pipeline Composition

Combine Exception Translation with Auditing and Resiliency:

using Noundry.Tuxedo.Exceptions;
using Noundry.Tuxedo.Auditor;
using Noundry.Tuxedo.Resiliency;

// Compose multiple wrappers
var connection = new SqlConnection(connectionString)
    .WithAuditing(config => config.UserProvider = userProvider)
    .WithExceptionTranslation()
    .WithRetryPolicy(retryPolicy);

Automatic Audit Logging

Comprehensive change tracking for Insert, Update, and Delete operations with automatic audit trail generation.

📊 Change Tracking

  • • Old and new values as JSON
  • • Changed columns detection
  • • Soft delete detection
  • • Success/failure logging

👤 User Providers

  • • Static user provider
  • • Delegate user provider
  • • HttpContext provider
  • • Custom implementations

🗄️ Multi-Database

  • • SQL Server (NVARCHAR)
  • • PostgreSQL (JSONB)
  • • MySQL (JSON)
  • • SQLite (TEXT)

Installation

$ dotnet add package Noundry.Tuxedo.Auditor

Basic Setup

using Noundry.Tuxedo.Auditor;
using Noundry.Tuxedo.Auditor.UserProviders;

// Wrap connection with auditing
var auditedConn = connection.WithAuditing(config =>
{
    config.UserProvider = new StaticAuditUserProvider("admin");
});

// All CRUD operations are automatically audited
await auditedConn.InsertAsync(product);  // Logged: Insert with new values
await auditedConn.UpdateAsync(product);  // Logged: Update with old/new values
await auditedConn.DeleteAsync(product);  // Logged: Delete with old values

User Providers

// Static user
config.UserProvider = new StaticAuditUserProvider("admin");

// Delegate (from current thread)
config.UserProvider = new DelegateAuditUserProvider(() =>
    Thread.CurrentPrincipal?.Identity?.Name ?? "Anonymous"
);

// HttpContext (ASP.NET Core)
services.AddScoped<IDbConnection>(sp =>
{
    var httpContext = sp.GetRequiredService<IHttpContextAccessor>();
    var connection = new SqlConnection(connectionString);

    return connection.WithAuditing(config =>
    {
        config.UserProvider = new HttpContextAuditUserProvider(httpContext);
    });
});

Soft Delete Detection

Automatically detects soft deletes when entities have IsDeleted and DeletedAt properties:

public class User
{
    [Key]
    public int Id { get; set; }
    public string Email { get; set; }
    public bool IsDeleted { get; set; }
    public DateTime? DeletedAt { get; set; }
}

// Regular update
user.Email = "newemail@example.com";
await auditedConn.UpdateAsync(user);
// Event Type: Update

// Soft delete
user.IsDeleted = true;
user.DeletedAt = DateTime.UtcNow;
await auditedConn.UpdateAsync(user);
// Event Type: SoftDelete

Querying Audit Logs

// Get audit history for a specific entity
var entityHistory = connection.Query<AuditLog>(@"
    SELECT * FROM audit_log
    WHERE table_name = @Table AND entity_id = @EntityId
    ORDER BY timestamp DESC",
    new { Table = "Users", EntityId = "123" }
);

// Get all changes by a specific user
var userChanges = connection.Query<AuditLog>(
    "SELECT * FROM audit_log WHERE username = @Username",
    new { Username = "admin" }
);

Ready to Build High-Performance Data Access?

Start using Noundry.Tuxedo today and build fast, reliable data access layers for your .NET applications.