Ticomix.EFCore 2025.12.11.1

Ticomix.EFCore

Purpose Statement

A comprehensive .NET Entity Framework Core extension library that provides enhanced database functionality including automatic audit fields, advanced logging, SQL utilities, and database context management. Built to work with both .NET 6.0 and .NET 8.0 with Entity Framework Core for enterprise applications requiring robust data access patterns and comprehensive logging capabilities.

Core Systems

1. Logging System

Comprehensive database-backed logging with support for error tracking, batch operations, and integration with Microsoft.Extensions.Logging.

2. Audit Fields System

Automatically manages audit fields (AddDate, AddUser, ChangeDate, ChangeUser) for all entities implementing the IAudit or IAuditUtc interfaces.

3. SQL Utilities

Advanced SQL execution utilities for raw queries, stored procedures, and data mapping with async support.

4. Database Schema Management

String length attributes for easy configuration of database string lengths.
Easy overrides of default column types for specific C# data types.

5. Startup and One-Time Scripts

Automated running of sql scripts either every time the application starts or only once.

Core Classes and Interfaces

Database Context

  • IDbContext: Core database context interface with user tracking and enhanced functionality
  • IErrorLogDbContext: Interface for error logging database context
  • DbContextSetup: Automated model configuration for entity properties
  • GenericDbSet: Enhanced DbSet with generic operations and key management

Audit System

  • AuditHelper: Automatically populates audit fields on entity changes

Logging System

  • Logger<TKey>: Default ILogger implementation - Database-backed logger with batch support and error tracking
  • ILogger: Interface for logging operations
  • LogEntryErrorLevel: Enumeration for log levels (Information, Warning, Error, Debug, etc.)

SQL Utilities

  • SqlExtensionMethods: Extension methods for raw SQL execution and data mapping
  • SqlSchemaHelper: Database schema inspection and management utilities
  • SqlStream: Stream-based SQL data processing
  • KeyHelper: Primary key management and generation utilities

Data Context Interfaces - Types referenced by other Core Modules

  • IAccountingDbContext: Accounting system database context
  • IAttachmentsDbContext: File attachment management context
  • ICompanyDbContext: Company data management context
  • IEmailNoteDbContext: Email and note management context
  • IPrintingDbContext: Printing system context
  • IReportingDbContext: Reporting system context
  • IWebJobDbContext: Background job management context
  • IWorkflowDbContext: Workflow management context
  • ICodeMaintenanceDbContext: Code maintenance context

Security and Validation

  • Reflection: Reflection utilities for dynamic property access respecting ACL
  • ExtensionMethods: Security-related extension methods for setting entity state by ACL

Installation Instructions

Prerequisites

Ensure your application targets:

  • .NET 6.0 or .NET 8.0
  • Entity Framework Core 6.0+ or 8.0+
  • SQL Server 2016+

Configuration

1. Error Logging Setup

Model and DbContext Setup

ErrorLog.cs

public class ErrorLog : IAudit, IErrorLog<int> 
{ 
    // Columns omitted for brevity...
}
ApplicationDbContext.cs

public class ApplicationDbContext : DbContext, IDbContext
{
    // Rest of class omitted for brevity...

    // Error logging support - Required DbSet
    public DbSet<ErrorLog> ErrorLog { get; set; }

    // ...
}
ApplicationDbContextErrorLog.cs

public partial class ApplicationDbContext : IErrorLogDbContext<int>
{
    // Implement IErrorLogDbContext interface for Logger compatibility
    GenericDbSet<IErrorLog<int>> IErrorLogDbContext<int>.ErrorLog => 
        new GenericDbSet<IErrorLog<int>>(this, this.ErrorLog);
}

Startup.cs - Dependency Injection Setup

// ConfigureServices method
services.AddScoped<IErrorLogDbContext<int>>(provider => 
    provider.GetRequiredService<ApplicationDbContext>());

services.AddScoped<ILogger, Ticomix.Common.Helpers.Logger<int>>();

Usage Examples

public class CustomerService
{
    private readonly ILogger logger;
    private readonly ApplicationDbContext db;

    public CustomerService(ILogger logger, ApplicationDbContext db)
    {
        this.logger = logger;
        this.db = db;
    }

    public async Task<Customer> CreateCustomerAsync(Customer customer)
    {
        try
        {
            db.Set<Customer>().Add(customer);
            await db.SaveChangesAsync();
            
            logger.LogMessage(LogEntryErrorLevel.Information, 
                $"Customer created: {customer.Name}");
            
            return customer;
        }
        catch (Exception ex)
        {
            logger.LogError(ex);
            throw;
        }
    }

    // Batch logging for related operations
    public async Task ProcessCustomerBatch(List<Customer> customers)
    {
        logger.Batch = Guid.NewGuid();
        
        foreach (var customer in customers)
        {
            try
            {
                await CreateCustomerAsync(customer);
                logger.LogMessageBatch(LogEntryErrorLevel.Information, 
                    $"Processed customer: {customer.Name}");
            }
            catch (Exception ex)
            {
                logger.LogErrorBatch(ex);
            }
        }
    }
}

2. Audit Fields Implementation

Entity with Audit Fields

public class Customer : IAudit
{
    // Columns omitted for brevity...

    // Audit fields
    public DateTime? AddDate { get; set; }
    public string? AddUser { get; set; }
    public DateTime? ChangeDate { get; set; }
    public string? ChangeUser { get; set; }
}

Audit Configuration

When used in the SaveChanges or SaveChangesAsync methods, the AuditHelper automatically:

  • Sets AddDate and AddUser on entity creation
  • Updates ChangeDate and ChangeUser on entity modification
  • Prevents modification of AddDate and AddUser after creation
  • Supports both DateTime and DateTimeOffset types
ApplicationDbContext.cs - Audit Helper usage
public class ApplicationDbContext : DbContext, IDbContext
{
    // Rest of class omitted for brevity...
    public bool SaveChangesAuditEnabled { get; set; } = true;

    public override int SaveChanges()
    {
        try
        {
            if (SaveChangesAuditEnabled)
            {
                new AuditHelper().SaveChangesAudit(this, UserName);
            }

            return base.SaveChanges();
        }
        catch (DbUpdateException ex)
        {
            if (ex.InnerException != null)
            {
                string message = ex.Message + "\r\n" + ex.InnerException.Message;
                if (ex.InnerException.InnerException != null)
                {
                    message += "\r\n" + ex.InnerException.InnerException.Message;
                }

                foreach (var entry in this.ChangeTracker.Entries().Where(a => a.State == EntityState.Added || a.State == EntityState.Modified))
                {
                    string values = "\r\n";
                    foreach (var prop in entry.CurrentValues.Properties.Select(a => a.Name))
                    {
                        values += "\r\n" + prop + ": " + entry.CurrentValues[prop];
                    }
                    message += values;
                }
                throw new DbUpdateException(message, ex);
            }
            else
            {
                throw;
            }
        }
    }

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        try
        {
            if (SaveChangesAuditEnabled)
            {
                new AuditHelper().SaveChangesAudit(this, UserName);
            }

            return base.SaveChangesAsync(cancellationToken);
        }
        catch (DbUpdateException ex)
        {
            if (ex.InnerException != null)
            {
                string message = ex.Message + "\r\n" + ex.InnerException.Message;
                if (ex.InnerException.InnerException != null)
                {
                    message += "\r\n" + ex.InnerException.InnerException.Message;
                }

                foreach (var entry in this.ChangeTracker.Entries().Where(a => a.State == EntityState.Added || a.State == EntityState.Modified))
                {
                    string values = "\r\n";
                    foreach (var prop in entry.CurrentValues.Properties.Select(a => a.Name))
                    {
                        values += "\r\n" + prop + ": " + entry.CurrentValues[prop];
                    }
                    message += values;
                }
                throw new DbUpdateException(message, ex);
            }
            else
            {
                throw;
            }
        }
    }
}

3. SQL Utilities Usage

Raw SQL Queries

public class ReportService
{
    private readonly IDbContext db;

    public ReportService(IDbContext db)
    {
        this.db = db;
    }

    // Execute scalar query
    public async Task<int> GetCustomerCountAsync()
    {
        return await db.ExecuteScalarAsync<int>(
            "SELECT COUNT(*) FROM Customers WHERE Active = 1");
    }

    // Execute mapped query
    public async Task<List<CustomerSummary>> GetCustomerSummariesAsync()
    {
        var results = new List<CustomerSummary>();
        await foreach (var item in db.SQLexecAsync<CustomerSummary>(
            "SELECT Id, Name, Email, LastOrderDate FROM vw_CustomerSummary"))
        {
            results.Add(item);
        }
        return results;
    }

    // Parameterized queries
    public List<Customer> GetCustomersByState(string state)
    {
        return db.SQLexec<Customer>(
            "SELECT * FROM Customers WHERE State = @State",
            new Dictionary<string, object?> { { "@State", state } });
    }

    // Custom mapping
    public async Task<List<CustomResult>> GetCustomDataAsync()
    {
        var results = new List<CustomResult>();
        await foreach (var item in db.RawSqlQueryAsync(
            "SELECT Col1, Col2, Col3 FROM CustomView",
            reader => new CustomResult
            {
                Value1 = reader.GetString("Col1"),
                Value2 = reader.GetInt32("Col2"),
                Value3 = reader.GetStringNullable(2) // Nullable string helper
            }))
        {
            results.Add(item);
        }
        return results;
    }
}

4. Database Schema Configuration

String Length Attributes

public class Product
{
    public int Id { get; set; }
    
    [StringLengthSmall] // Uses StringLengths.Small
    public string Code { get; set; }
    
    [StringLengthMedium] // Uses StringLengths.Medium  
    public string Name { get; set; }
    
    [StringLengthMax] // Uses maximum length (nvarchar(max))
    public string Description { get; set; }
    
    // Default length applied automatically
    public string Category { get; set; }
}

5. One-Time Scripts and Startup Scripts

The package supports exexution of one-time scripts and startup scripts:

ApplicationDbContext.cs - One-Time Script History

public class ApplicationDbContext : DbContext, IOneTimeScriptHistoryDbContext
{
    public DbSet<OneTimeScriptHistory> OneTimeScriptHistory { get; set; }
    
    // Implementation automatically tracks script execution
}

Startup.cs - add the following lines after migrations are applied

if (!env.IsDevelopment())
{
    SqlSchemaHelper.RunStartupScripts(context);
}
SqlSchemaHelper.RunOneTimeScripts(context);


var logger = serviceScope.ServiceProvider.GetService<ILogger>();
logger.Source = "Startup";
logger.LogMessage(LogEntryErrorLevel.Information, "Database updated");

Advanced Features

Custom Schema Setup

public class CustomDbContextSetup : DbContextSetup
{
    public CustomDbContextSetup()
    {
        // Set default string length (default is StringLengths.Large)
        DefaultStringLength = 500;
    }

    // Override property filters for custom behavior
    public override bool StringPropertyFilter(ModelBuilderProperty p)
    {
        // Custom logic for string property configuration
        return base.StringPropertyFilter(p) && !p.MutableProperty.Name.EndsWith("Code");
    }

    // Custom property building
    public override void BuildStringProperty(ModelBuilderProperty property)
    {
        if (property.MutableProperty.Name.EndsWith("Code"))
        {
            property.PropertyBuilder.HasMaxLength(20).IsUnicode(false);
        }
        else
        {
            base.BuildStringProperty(property);
        }
    }
}

ApplicationDbContext.cs - OnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder) {
    base.OnModelCreating(modelBuilder);

    var dbContextSetup = new DbContextSetup();
    dbContextSetup.OnModelCreating(modelBuilder);
}

Schema Inspection

public class SchemaService
{
    private readonly IDbContext db;

    public SchemaService(IDbContext db)
    {
        this.db = db;
    }

    public async Task<List<string>> GetTableColumnsAsync(string tableName)
    {
        var columns = await db.SQLexecAsync<SysColumn>(
            "SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(@TableName)",
            new Dictionary<string, object?> { { "@TableName", tableName } });

        return await columns.Select(c => c.name).ToListAsync();
    }
}

Exception Handling

try
{
    await db.SaveChangesAsync();
}
catch (Exception ex)
{
    var parser = new EfCoreExceptionParser();
    var friendlyMessage = parser.GetUserFriendlyMessage(ex);
    
    logger.LogError(ex);
    return BadRequest(friendlyMessage);
}

Showing the top 20 packages that depend on Ticomix.EFCore.

Packages Downloads
Ticomix.AspNetCore.Mvc
Package Description
221
Ticomix.AspNetCore.Web
Package Description
220
Ticomix.AspNetCore.Web
Package Description
173
Ticomix.AspNetCore.Mvc
Package Description
171
Ticomix.Attachments.Common
Package Description
165
Ticomix.EmailNote
Package Description
165
Ticomix.AspNetCore.Security.AspNetGroups
Package Description
164
Ticomix.Accounting
Package Description
164
Ticomix.EmailNote.Core
Package Description
164
Ticomix.CodeMaintenance
Package Description
163
Ticomix.Company
Package Description
163
Ticomix.Excel
Package Description
163
Ticomix.Workflow
Package Description
163
Ticomix.TelerikReporting
A .NET library that provides Telerik Reporting integration for web applications, enabling report design, viewing, and management capabilities with support for both Angular and MVC frameworks. Built on Telerik Reporting 18.1.24.709 with enhanced features for enterprise reporting solutions including report subscriptions, versioning, and distributed architecture support.
162
Ticomix.WebJobs
A .NET library that provides comprehensive background job scheduling and execution capabilities for web applications, including support for both scheduled jobs and on-demand background tasks. Built with Quartz.NET for robust job scheduling with distributed locking, progress tracking, and SignalR integration for real-time updates. Ideal for enterprise applications requiring reliable background processing including job management, task execution, and progress monitoring.
162
Ticomix.AspNetCore.Mvc
Package Description
120
Ticomix.AspNetCore.Web
Package Description
120
Ticomix.Attachments.Common
Package Description
119
Ticomix.AspNetCore.Mvc
Package Description
118
Ticomix.EmailNote
Package Description
118

Version Downloads Last updated
2025.12.11.1 2 12/11/2025
2025.12.10.2 2 12/10/2025
2025.12.10.1 2 12/10/2025
2025.12.9.3 6 12/9/2025
2025.12.9.2 6 12/9/2025
2025.12.9.1 0 12/9/2025
2025.12.8.2 1 12/8/2025
2025.12.7.1 12 12/7/2025
2025.12.5.2 6 12/5/2025
2025.12.5.1 1 12/5/2025
2025.12.3.2 16 12/3/2025
2025.12.3.1 2 12/3/2025
2025.11.25.1 1 11/25/2025
2025.11.24.1 12 11/24/2025
2025.11.22.1 8 11/22/2025
2025.11.21.2 3 11/22/2025
2025.11.21.1 2 11/21/2025
2025.11.20.4 3 11/20/2025
2025.11.20.3 2 11/20/2025
2025.11.20.2 2 11/20/2025
2025.11.20.1 2 11/20/2025
2025.11.18.2 4 11/18/2025
2025.11.18.1 4 11/18/2025
2025.11.4.1 31 11/4/2025
2025.10.31.1 13 10/31/2025
2025.10.29.1 24 10/29/2025
2025.10.23.2 59 10/23/2025
2025.10.23.1 63 10/23/2025
2025.10.22.1 21 10/22/2025
2025.10.15.1 72 10/15/2025
2025.10.9.1 14 10/10/2025
2025.10.3.2 20 10/3/2025
2025.10.3.1 3 10/3/2025
2025.10.1.4 24 10/1/2025
2025.10.1.3 3 10/1/2025
2025.10.1.2 3 10/1/2025
2025.9.24.1 33 9/24/2025
2025.9.23.1 12 9/23/2025
2025.9.22.2 6 9/22/2025
2025.9.17.1 15 9/17/2025
2025.9.10.1 49 9/10/2025
2025.9.2.3 75 9/2/2025
2025.8.19.4 5 8/19/2025
2025.8.19.3 41 8/19/2025
2025.8.19.2 5 8/19/2025
2025.7.25.1 221 7/25/2025
2025.7.21.3 111 7/21/2025
2025.7.19.2 21 7/19/2025
2025.7.16.1 42 7/16/2025
2025.7.15.1 20 7/15/2025
2025.7.11.2 29 7/11/2025
2025.7.2.2 8 7/2/2025
2025.6.25.3 98 6/25/2025
2025.6.24.1 10 6/24/2025
2025.6.23.1 11 6/23/2025
2025.6.20.2 96 6/20/2025
2025.6.18.1 28 6/18/2025
2025.6.16.5 25 6/16/2025
2025.6.16.4 7 6/16/2025
2025.6.11.2 53 6/11/2025
2025.6.5.2 56 6/5/2025
2025.6.4.1 82 6/4/2025
2025.6.2.1 32 6/2/2025
2025.5.22.1 115 5/22/2025
2025.5.14.1 171 5/14/2025
2025.5.8.2 20 5/8/2025
2025.5.2.1 74 5/2/2025
2025.5.1.1 58 5/1/2025
2025.4.18.12 57 4/18/2025
2025.4.15.1 97 4/15/2025
2025.4.10.1 35 4/10/2025
2025.4.8.4 16 4/8/2025
2025.4.4.10 9 4/4/2025
2025.4.4.7 30 4/4/2025
2025.3.28.1 38 3/28/2025
2025.3.25.3 75 3/25/2025
2025.3.25.2 26 3/25/2025
2025.3.20.1 25 3/20/2025
2025.3.19.5 19 3/19/2025
2025.3.19.3 14 3/19/2025
2025.3.18.5 18 3/18/2025
2025.3.18.4 14 3/18/2025
2025.3.18.3 11 3/18/2025
2025.3.18.2 10 3/18/2025
2025.3.12.1 39 3/12/2025
2025.3.7.1 26 3/7/2025
2025.3.4.1 64 3/4/2025
2025.2.17.2 40 2/17/2025
2025.2.17.1 11 2/17/2025
2025.2.14.2 62 2/14/2025
2025.2.7.1 26 2/7/2025
2025.2.6.6 11 2/6/2025
2025.2.6.1 120 2/6/2025
2025.2.5.2 19 2/6/2025
2025.2.5.1 15 2/5/2025
2025.1.30.1 18 1/30/2025
2025.1.29.2 14 1/29/2025
2025.1.29.1 15 1/29/2025
2025.1.28.2 20 1/28/2025
2025.1.28.1 47 1/28/2025
2025.1.27.4 14 1/27/2025
2025.1.27.3 13 1/27/2025
2025.1.27.2 18 1/27/2025
2025.1.27.1 9 1/27/2025
2025.1.6.1 214 1/6/2025
2025.1.2.2 86 1/2/2025
2024.12.31.2 46 12/31/2024
2024.12.31.1 11 12/31/2024
2024.12.30.1 15 12/30/2024
2024.12.20.2 25 12/20/2024
2024.12.17.18 22 12/17/2024
2024.12.17.2 16 12/17/2024
2024.12.11.3 28 12/11/2024
2024.12.10.1 18 12/10/2024
2024.12.5.3 22 12/5/2024
2024.12.5.2 15 12/5/2024
2024.12.4.10 13 12/4/2024
2024.12.4.9 12 12/4/2024
2024.11.15.4 48 11/16/2024
2024.11.15.1 14 11/15/2024
2024.11.6.3 35 11/6/2024
2024.11.6.1 15 11/6/2024
2024.11.5.6 16 11/5/2024
2024.11.5.4 12 11/5/2024
2024.10.28.3 28 10/28/2024
2024.10.28.2 14 10/28/2024
2024.10.28.1 13 10/28/2024
2024.10.24.1 17 10/24/2024
2024.10.23.1 15 10/23/2024
2024.10.17.2 21 10/17/2024
2024.10.8.1 171 10/8/2024
2024.9.13.1 146 9/13/2024