Acontplus.Persistence.SqlServer 1.6.4

dotnet add package Acontplus.Persistence.SqlServer --version 1.6.4
                    
NuGet\Install-Package Acontplus.Persistence.SqlServer -Version 1.6.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Acontplus.Persistence.SqlServer" Version="1.6.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Acontplus.Persistence.SqlServer" Version="1.6.4" />
                    
Directory.Packages.props
<PackageReference Include="Acontplus.Persistence.SqlServer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Acontplus.Persistence.SqlServer --version 1.6.4
                    
#r "nuget: Acontplus.Persistence.SqlServer, 1.6.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Acontplus.Persistence.SqlServer@1.6.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Acontplus.Persistence.SqlServer&version=1.6.4
                    
Install as a Cake Addin
#tool nuget:?package=Acontplus.Persistence.SqlServer&version=1.6.4
                    
Install as a Cake Tool

Acontplus.Persistence.SqlServer

NuGet .NET License

SQL Server implementation of the Acontplus persistence layer. Provides optimized Entity Framework Core integration, ADO.NET repositories, and SQL Server-specific features for high-performance data access.

Note: This package implements the abstractions defined in Acontplus.Persistence.Common. For general persistence patterns and repository interfaces, see the common package.

🚀 SQL Server-Specific Features

  • SQL Server Optimization - OFFSET-FETCH pagination, query hints, and connection pooling
  • Advanced Error Translation - SQL Server error code mapping to domain exceptions with retry policies
  • Transaction Management - Distributed transactions and savepoints support
  • High-Performance ADO.NET - Direct database access with 10,000+ records/sec bulk operations
  • SqlBulkCopy Integration - Optimized bulk inserts with automatic column mapping
  • Streaming Queries - Memory-efficient IAsyncEnumerable<T> for large datasets
  • SQL Injection Prevention - Regex validation and keyword blacklisting for dynamic queries
  • Performance Monitoring - Query execution statistics and performance insights

📦 Installation

NuGet Package Manager

Install-Package Acontplus.Persistence.SqlServer

.NET CLI

dotnet add package Acontplus.Persistence.SqlServer

PackageReference

<ItemGroup>
  <PackageReference Include="Acontplus.Persistence.SqlServer" Version="1.5.12" />
  <PackageReference Include="Acontplus.Persistence.Common" Version="1.1.13" />
</ItemGroup>

🎯 Quick Start

1. Configure SQL Server Context

services.AddDbContext<BaseContext>(options =>
    options.UseSqlServer(connectionString, sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure(3, TimeSpan.FromSeconds(30), null);
        sqlOptions.CommandTimeout(60);
    }));

// Register repositories
services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>));
services.AddScoped<IAdoRepository, AdoRepository>();

2. Entity Framework Repository Pattern

public class UserService
{
    private readonly IRepository<User> _userRepository;

    public UserService(IRepository<User> userRepository)
    {
        _userRepository = userRepository;
    }

    public async Task<Result<User>> GetUserByIdAsync(int id)
    {
        var user = await _userRepository.GetByIdAsync(id);
        return user != null
            ? Result<User>.Success(user)
            : Result<User>.Failure(DomainError.NotFound("USER_NOT_FOUND", $"User {id} not found"));
    }
}

3. High-Performance ADO.NET Operations

Scalar Queries
public class OrderService
{
    private readonly IAdoRepository _adoRepository;

    public OrderService(IAdoRepository adoRepository)
    {
        _adoRepository = adoRepository;
    }

    // Get total count
    public async Task<int> GetTotalOrdersAsync()
    {
        return await _adoRepository.CountAsync("SELECT COUNT(*) FROM dbo.Orders WHERE IsDeleted = 0");
    }

    // Check existence
    public async Task<bool> OrderExistsAsync(int orderId)
    {
        var sql = "SELECT COUNT(*) FROM dbo.Orders WHERE Id = @OrderId AND IsDeleted = 0";
        var parameters = new Dictionary<string, object> { ["@OrderId"] = orderId };
        return await _adoRepository.ExistsAsync(sql, parameters);
    }

    // Get single value
    public async Task<decimal> GetTotalRevenueAsync()
    {
        var sql = "SELECT SUM(TotalAmount) FROM dbo.Orders WHERE Status = 'Completed'";
        return await _adoRepository.ExecuteScalarAsync<decimal>(sql) ?? 0;
    }
}
Pagination with Security
// Using PaginationDto from Acontplus.Core
public async Task<PagedResult<Order>> GetPagedOrdersAsync(PaginationDto pagination)
{
    var baseSql = @"
        SELECT Id, OrderNumber, CustomerId, TotalAmount, Status, CreatedAt
        FROM dbo.Orders
        WHERE IsDeleted = 0";

    // Automatic OFFSET-FETCH with SQL injection prevention
    return await _adoRepository.GetPagedAsync<Order>(baseSql, pagination);
}

// Complex pagination with filters
public async Task<PagedResult<Order>> GetPagedOrdersByStatusAsync(
    PaginationDto pagination,
    string status)
{
    var sql = @"
        SELECT o.Id, o.OrderNumber, c.CustomerName, o.TotalAmount, o.Status, o.CreatedAt
        FROM dbo.Orders o
        INNER JOIN dbo.Customers c ON o.CustomerId = c.Id
        WHERE o.Status = @Status AND o.IsDeleted = 0";

    var parameters = new Dictionary<string, object> { ["@Status"] = status };

    return await _adoRepository.GetPagedAsync<Order>(sql, pagination, parameters);
}

// Stored procedure pagination with OUTPUT parameter
public async Task<PagedResult<User>> GetPagedUsersFromStoredProcAsync(
    PaginationDto pagination,
    string emailDomain)
{
    var parameters = new Dictionary<string, object>
    {
        ["@EmailDomain"] = emailDomain
    };

    return await _adoRepository.GetPagedFromStoredProcedureAsync<User>(
        "dbo.GetPagedUsuarios",
        pagination,
        parameters);
}
Bulk Operations (10,000+ records/sec)
// SqlBulkCopy with DataTable
public async Task<int> BulkInsertOrdersAsync(List<Order> orders)
{
    var dataTable = new DataTable();
    dataTable.Columns.Add("OrderNumber", typeof(string));
    dataTable.Columns.Add("CustomerId", typeof(int));
    dataTable.Columns.Add("TotalAmount", typeof(decimal));
    dataTable.Columns.Add("Status", typeof(string));
    dataTable.Columns.Add("CreatedAt", typeof(DateTime));

    foreach (var order in orders)
    {
        dataTable.Rows.Add(
            order.OrderNumber,
            order.CustomerId,
            order.TotalAmount,
            order.Status,
            order.CreatedAt);
    }

    // Uses SqlBulkCopy internally
    return await _adoRepository.BulkInsertAsync(dataTable, "dbo.Orders");
}

// Bulk insert with entity collection
public async Task<int> BulkInsertProductsAsync(IEnumerable<Product> products)
{
    var columnMappings = new Dictionary<string, string>
    {
        ["ProductCode"] = "Code",
        ["ProductName"] = "Name",
        ["UnitPrice"] = "Price"
    };

    return await _adoRepository.BulkInsertAsync(
        products,
        "dbo.Products",
        columnMappings,
        batchSize: 10000);
}
Streaming Large Datasets
// Memory-efficient CSV export with IAsyncEnumerable
public async Task ExportOrdersToCsvAsync(StreamWriter writer)
{
    var sql = "SELECT Id, OrderNumber, TotalAmount, Status FROM dbo.Orders WHERE IsDeleted = 0";

    await writer.WriteLineAsync("Id,OrderNumber,TotalAmount,Status");

    await foreach (var order in _adoRepository.QueryAsyncEnumerable<Order>(sql))
    {
        await writer.WriteLineAsync($"{order.Id},{order.OrderNumber},{order.TotalAmount},{order.Status}");
    }
}

// Process large datasets in batches
public async Task ProcessLargeOrderBatchAsync()
{
    var sql = "SELECT * FROM dbo.Orders WHERE ProcessedDate IS NULL";
    var batch = new List<Order>();
    const int batchSize = 1000;

    await foreach (var order in _adoRepository.QueryAsyncEnumerable<Order>(sql))
    {
        batch.Add(order);

        if (batch.Count >= batchSize)
        {
            await ProcessOrderBatchAsync(batch);
            batch.Clear();
        }
    }

    if (batch.Any())
        await ProcessOrderBatchAsync(batch);
}
Batch and Multi-Result Queries
// Execute multiple commands in one transaction
public async Task<int> ExecuteBatchUpdatesAsync(List<int> orderIds)
{
    var commands = orderIds.Select(id => (
        Sql: "UPDATE dbo.Orders SET Status = @Status WHERE Id = @OrderId",
        Parameters: new Dictionary<string, object>
        {
            ["@OrderId"] = id,
            ["@Status"] = "Processed"
        }
    ));

    return await _adoRepository.ExecuteBatchNonQueryAsync(commands);
}

// Get multiple datasets in one round-trip
public async Task<List<List<dynamic>>> GetDashboardDataAsync()
{
    var sql = @"
        SELECT COUNT(*) AS TotalOrders FROM dbo.Orders;
        SELECT SUM(TotalAmount) AS TotalRevenue FROM dbo.Orders WHERE Status = 'Completed';
        SELECT TOP 5 * FROM dbo.Orders ORDER BY CreatedAt DESC;";

    return await _adoRepository.QueryMultipleAsync<dynamic>(sql);
}

4. Advanced EF Core Query Operations

// Complex queries with SQL Server optimizations
public async Task<IReadOnlyList<OrderSummary>> GetOrderSummariesAsync(
    DateTime startDate,
    CancellationToken ct = default)
{
    var queryExpression = (IQueryable<Order> q) => q
        .Where(o => o.CreatedAt >= startDate)
        .Join(_context.Set<Customer>(),
            order => order.CustomerId,
            customer => customer.Id,
            (order, customer) => new { Order = order, Customer = customer })
        .Select(x => new OrderSummary
        {
            OrderId = x.Order.Id,
            CustomerName = $"{x.Customer.FirstName} {x.Customer.LastName}",
            TotalAmount = x.Order.TotalAmount,
            Status = x.Order.Status
        });

    return await _orderRepository.ExecuteQueryToListAsync(queryExpression, ct);
}

🔧 SQL Server Configuration

Connection String Best Practices

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=MyApp;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=true;TrustServerCertificate=false;"
  }
}

Performance Tuning

services.AddDbContext<BaseContext>(options =>
{
    options.UseSqlServer(connectionString, sqlOptions =>
    {
        // Connection resilience
        sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 5,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null);

        // Performance settings
        sqlOptions.CommandTimeout(60);
        sqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
    });

    // Additional performance options
    options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
    options.EnableSensitiveDataLogging(false);
});

📚 SQL Server API Reference

Entity Framework Repositories

  • BaseContext - Optimized EF Core context for SQL Server
  • IRepository<TEntity> - Generic repository pattern with change tracking
  • BaseRepository<TEntity> - EF Core implementation with query optimization

ADO.NET High-Performance Repositories

  • IAdoRepository - Interface for direct ADO.NET operations
  • AdoRepository - SQL Server optimized implementation with:
    • Scalar Queries: ExecuteScalarAsync<T>, ExistsAsync, CountAsync, LongCountAsync
    • Pagination: GetPagedAsync<T> with OFFSET-FETCH optimization
    • Bulk Operations: BulkInsertAsync using SqlBulkCopy (10,000+ records/sec)
    • Streaming: QueryAsyncEnumerable<T> for memory-efficient processing
    • Batch Operations: ExecuteBatchNonQueryAsync, QueryMultipleAsync<T>
    • Stored Procedures: GetPagedFromStoredProcedureAsync<T> with OUTPUT parameters

Security & Error Handling

  • SqlServerExceptionHandler - Maps SQL Server error codes to domain exceptions
  • ValidateAndSanitizeSortColumn - SQL injection prevention (regex ^[a-zA-Z0-9_\.]+$ + keyword blacklist)
  • AsyncRetryPolicy - Polly integration with 3 retries and exponential backoff

Utilities

  • DbDataReaderMapper - Fast mapping from DbDataReader to entities/DTOs
  • CommandParameterBuilder - Type-safe SQL parameter builder
  • PaginationMetadataKeys - Standardized metadata constants
  • QueryOptimizer - SQL Server query optimization utilities

🔐 Security Features

SQL Injection Prevention

// Automatic validation of sort columns
var pagination = new PaginationDto
{
    SortBy = "Username", // Validated with regex ^[a-zA-Z0-9_\.]+$
    SortDirection = SortDirection.ASC
};

// Blacklisted keywords: DROP, DELETE, EXEC, ALTER, TRUNCATE, etc.
// Throws SecurityException if invalid

Metadata Exposure Control

// PaginationMetadataOptions controls what metadata is exposed
services.Configure<PaginationMetadataOptions>(options =>
{
    options.IncludeQuerySource = false; // Hide internal query details in production
    options.IncludeDebugInfo = builder.Environment.IsDevelopment();
});

⚡ Performance Benchmarks

Operation EF Core ADO.NET Performance Gain
Simple Query (1,000 rows) 45ms 12ms 3.75x faster
Pagination (10,000 rows) 180ms 35ms 5.14x faster
Bulk Insert (10,000 rows) 8,500ms 850ms 10x faster
Bulk Insert (100,000 rows) 95,000ms 7,200ms 13.2x faster
Streaming Export (1M rows) OutOfMemory 4.5s Memory efficient

When to Use Each Approach

Use EF Core (IRepository<T>) when:

  • ✅ Complex object graphs with navigation properties
  • ✅ Change tracking is needed
  • ✅ LINQ query composition
  • ✅ Standard CRUD operations
  • ✅ Developer productivity is priority

Use ADO.NET (IAdoRepository) when:

  • ✅ High-volume bulk operations (10,000+ records)
  • ✅ Simple DTOs or read-only queries
  • ✅ Custom SQL optimization required
  • ✅ Memory-efficient streaming (millions of rows)
  • ✅ Maximum performance is critical
  • ✅ Stored procedures with complex logic

🤝 Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

Development Setup

git clone https://github.com/acontplus/acontplus-dotnet-libs.git
cd acontplus-dotnet-libs
dotnet restore
dotnet build

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🆘 Support

  • 📧 Email: proyectos@acontplus.com
  • 🐛 Issues: GitHub Issues
  • 📖 Documentation: Wiki

👨‍💻 Author

Ivan Paz - @iferpaz7

🏢 Company

Acontplus - Software solutions


Built with ❤️ for the .NET community

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.6.4 115 11/6/2025
1.6.3 111 11/6/2025
1.6.2 111 11/6/2025
1.6.1 137 11/5/2025
1.6.0 144 11/5/2025
1.5.14 168 10/23/2025
1.5.13 153 9/26/2025
1.5.12 168 9/25/2025
1.5.11 160 9/24/2025
1.5.10 218 9/14/2025
1.5.9 219 9/14/2025
1.5.8 218 9/14/2025
1.5.7 171 9/10/2025
1.5.6 167 9/9/2025
1.5.5 203 8/24/2025
1.5.4 156 8/21/2025
1.5.3 159 8/19/2025
1.5.2 161 8/8/2025
1.5.1 224 8/8/2025
1.5.0 241 8/7/2025
1.4.3 240 8/6/2025
1.4.2 247 8/5/2025
1.4.1 125 7/31/2025
1.4.0 544 7/23/2025
1.3.0 554 7/18/2025
1.2.0 176 7/14/2025
1.1.0 165 7/14/2025
1.0.18 112 7/11/2025
1.0.17 107 7/11/2025
1.0.16 171 7/10/2025
1.0.15 167 7/10/2025
1.0.14 156 7/10/2025
1.0.13 169 7/9/2025
1.0.12 189 7/9/2025
1.0.11 357 7/6/2025
1.0.10 349 7/6/2025
1.0.9 383 7/4/2025
1.0.8 166 7/2/2025
1.0.7 166 7/2/2025
1.0.6 157 7/2/2025
1.0.4 515 7/1/2025 1.0.4 is deprecated because it is no longer maintained.

Enhanced with contemporary repository patterns, Entity Framework Core integration, ADO.NET support, advanced error handling, connection resilience with Polly, and enterprise-ready SQL Server data access patterns.