Configuration
DapperMatic is designed to work with minimal configuration, following convention-over-configuration principles. However, there are several ways to customize its behavior when needed.
Table Factory Configuration
The DmTableFactory provides methods to customize how classes are mapped to DmTable instances.
When Configuration is Applied
Important: Configuration in DapperMatic is applied only when creating database schema objects (DDL operations), not during data queries. Specifically:
- ✅ Applied during:
CreateTableIfNotExistsAsync(),CreateViewIfNotExistsAsync(), and other DDL operations - ✅ Applied when:
DmTableFactory.GetTable(typeof(MyClass))is called to generate table definitions - ❌ NOT applied during:
QueryAsync(),ExecuteAsync(), or any data access operations - ❌ NOT used for: Query result mapping to C# objects
What Adding Default Columns Does
When you add columns through configuration, you are modifying the table schema definition that DapperMatic will create in your database. This affects:
- Database Schema Creation: Added columns become real database columns with constraints and defaults
- DDL Generation: The columns are included in CREATE TABLE statements
- Schema Introspection: DapperMatic will detect these columns when reading existing table structures
- Schema Validation: The columns are considered part of the expected table structure
Example Impact:
// Original User class (no created_at field)
[DmTable("dbo", "users")]
public class User
{
[DmColumn("id", isPrimaryKey: true, isAutoIncrement: true)]
public int Id { get; set; }
[DmColumn("name", length: 100)]
public string Name { get; set; }
[DmColumn("email", length: 200)]
public string Email { get; set; }
}
// Configure audit columns
DmTableFactory.Configure((type, table) => {
// This adds REAL database columns
table.Columns = table.Columns.Concat(new[] {
new DmColumn("created_at", typeof(DateTime)) { DefaultExpression = "GETDATE()" }
}).ToArray();
});
// When you call this, the created_at column will be physically created in the database
var userTable = DmTableFactory.GetTable(typeof(User));
await connection.CreateTableIfNotExistsAsync(userTable);
// The database now has: id, name, email, created_at (with GETDATE() default)
// Even though User class only has 3 properties, the table has 4 columns!
// Future INSERTs will automatically populate created_at if not specifiedGlobal Configuration
Configure mapping behavior for all types at application startup:
⚠️ Configuration Timing: Configure DapperMatic before calling
DmTableFactory.GetTable(typeof(SomeClass))for any type, as type-to-table mappings are cached after first generation. Ideally, set up configuration during application startup.
📝 What Gets Cached: DapperMatic caches the C# Type → DmTable mapping, NOT database schema. The cache stores the result of analyzing your C# classes (attributes, properties, configuration) and converting them to
DmTableobjects. This is purely in-memory and has nothing to do with database introspection or schema caching.
🔄 Cache Management: Currently, there is no public API to clear or refresh the mapping cache. Once a type is processed by
DmTableFactory.GetTable(typeof(SomeClass)), itsDmTabledefinition is permanently cached for the application lifetime. To apply new configuration to already-processed types, you need to restart the application.
// Configure custom mapping logic for all types
DmTableFactory.Configure((type, table) =>
{
// Apply custom logic to all generated tables
if (type.Name.EndsWith("Entity"))
{
// Remove "Entity" suffix from table names
table.TableName = table.TableName.Replace("Entity", "");
}
// Add audit columns to all tables
if (!table.Columns.Any(c => c.ColumnName == "created_at"))
{
table.Columns = table.Columns.Concat(new[]
{
new DmColumn("created_at", typeof(DateTime))
{
IsNullable = false,
DefaultExpression = "GETDATE()"
},
new DmColumn("updated_at", typeof(DateTime))
{
IsNullable = true
}
}).ToArray();
}
});Type-Specific Configuration
Configure mapping for specific types:
// Configure a specific type
DmTableFactory.Configure<Customer>(table =>
{
// Customize the Customer table
table.TableName = "app_customers";
// Add a custom column for business logic
var statusColumn = new DmColumn("status", typeof(string))
{
Length = 20,
IsNullable = false,
DefaultExpression = "'Active'"
};
table.Columns = table.Columns.Concat(new[] { statusColumn }).ToArray();
// Add additional constraints
table.CheckConstraints = table.CheckConstraints.Concat(new[]
{
new DmCheckConstraint("CK_Customer_Email_Valid", "email LIKE '%@%'"),
new DmCheckConstraint("CK_Customer_Status_Valid", "status IN ('Active', 'Inactive', 'Suspended')")
}).ToArray();
});
// Alternative syntax with Type parameter
DmTableFactory.Configure(typeof(Product), table =>
{
table.SchemaName = "catalog";
// Additional customizations...
});Configuration Best Practices
public class DatabaseConfig
{
public static void ConfigureDapperMatic()
{
DmTableFactory.Configure((type, table) =>
{
// 1. Standardize naming conventions
if (string.IsNullOrEmpty(table.SchemaName))
{
table.SchemaName = GetSchemaForType(type);
}
// 2. Add common audit fields
AddAuditFields(table);
// 3. Apply organization-specific rules
ApplyBusinessRules(type, table);
});
}
private static string GetSchemaForType(Type type)
{
// Apply your organization's schema naming rules
var namespaceParts = type.Namespace?.Split('.') ?? Array.Empty<string>();
return namespaceParts.Length > 2 ? namespaceParts[2].ToLower() : "dbo";
}
private static void AddAuditFields(DmTable table)
{
var auditFields = new[]
{
new DmColumn("created_at", typeof(DateTime))
{
IsNullable = false,
DefaultExpression = "GETDATE()"
},
new DmColumn("created_by", typeof(string))
{
Length = 100,
IsNullable = false,
DefaultExpression = "SYSTEM_USER"
}
};
// Only add if they don't already exist
var existingColumns = table.Columns.Select(c => c.ColumnName).ToHashSet();
var newColumns = auditFields.Where(c => !existingColumns.Contains(c.ColumnName));
table.Columns = table.Columns.Concat(newColumns).ToArray();
}
}
// Call this at application startup
DatabaseConfig.ConfigureDapperMatic();Extension Method Parameters
DapperMatic extension methods accept standard Dapper method parameters (e.g.: QueryAsync, ExecuteAsync, ...) for customization:
Command Timeout
Control how long database operations wait before timing out:
// Set timeout for individual operations
var table = new DmTable(schema, "TableName", columns);
await connection.CreateTableIfNotExistsAsync(table, commandTimeout: 120); // 2 minutes
// For complex operations like large table creation
var complexTable = new DmTable("dbo", "ComplexTable", complexColumns);
await connection.CreateTableIfNotExistsAsync(complexTable, commandTimeout: 300); // 5 minutes
// Apply timeout to introspection operations
var tables = await connection.GetTablesAsync("dbo", commandTimeout: 60);Transaction Support
Use transactions for atomic operations:
using var transaction = connection.BeginTransaction();
try
{
// Create multiple related tables in a transaction
var usersTable = new DmTable("dbo", "Users", userColumns);
var ordersTable = new DmTable("dbo", "Orders", orderColumns);
var orderItemsTable = new DmTable("dbo", "OrderItems", orderItemColumns);
await connection.CreateTableIfNotExistsAsync(usersTable, tx: transaction);
await connection.CreateTableIfNotExistsAsync(ordersTable, tx: transaction);
await connection.CreateTableIfNotExistsAsync(orderItemsTable, tx: transaction);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}Cancellation Token Support
Handle cancellation gracefully in async operations:
public async Task CreateSchemaAsync(CancellationToken cancellationToken = default)
{
var tables = GetRequiredTables();
foreach (var table in tables)
{
var table = new DmTable("dbo", "TableName", tableColumns);
await connection.CreateTableIfNotExistsAsync(
table,
cancellationToken: cancellationToken
);
// Check for cancellation between operations
cancellationToken.ThrowIfCancellationRequested();
}
}Provider-Specific Configuration
Connection String Considerations
Different providers may require specific connection string parameters:
// SQL Server - Enable Multiple Active Result Sets for complex operations
var sqlServerConnectionString = "Server=.;Database=MyApp;Trusted_Connection=true;MultipleActiveResultSets=true";
// PostgreSQL - Set command timeout and application name
var pgConnectionString = "Host=localhost;Database=myapp;Username=user;Password=pass;CommandTimeout=30;ApplicationName=MyApp";
// MySQL - Configure SSL and charset
var mysqlConnectionString = "Server=localhost;Database=myapp;Uid=user;Pwd=pass;SslMode=Required;CharSet=utf8mb4";
// SQLite - Enable foreign keys and set journal mode
var sqliteConnectionString = "Data Source=myapp.db;Foreign Keys=true;Journal Mode=WAL";Provider-Specific Data Types
Use provider-specific data types when needed:
public class Document
{
public int Id { get; set; }
// Use provider-specific types for optimal performance
[DmColumn("content",
providerDataType: "{sqlserver:nvarchar(max),mysql:longtext,postgresql:text,sqlite:text}")]
public string Content { get; set; }
// JSON support varies by provider
[DmColumn("metadata",
providerDataType: "{sqlserver:nvarchar(max),mysql:json,postgresql:jsonb,sqlite:text}")]
public string Metadata { get; set; }
// UUID/GUID handling
[DmColumn("document_id",
providerDataType: "{sqlserver:uniqueidentifier,mysql:char(36),postgresql:uuid,sqlite:text}")]
public Guid DocumentId { get; set; }
}Environment-Specific Configuration
Development vs Production
Configure different behaviors based on environment:
public static class DatabaseSetup
{
public static void Configure(IConfiguration configuration)
{
var environment = configuration["Environment"];
DmTableFactory.Configure((type, table) =>
{
if (environment == "Development")
{
// In development, add debug-friendly features
AddDevelopmentFeatures(table);
}
else if (environment == "Production")
{
// In production, optimize for performance
AddProductionOptimizations(table);
}
});
}
private static void AddDevelopmentFeatures(DmTable table)
{
// Add debug columns in development
if (!table.Columns.Any(c => c.ColumnName == "debug_info"))
{
var debugColumn = new DmColumn("debug_info", typeof(string))
{
Length = 1000,
IsNullable = true
};
table.Columns = table.Columns.Concat(new[] { debugColumn }).ToArray();
}
}
private static void AddProductionOptimizations(DmTable table)
{
// Add performance-focused indexes
var performanceIndexes = table.Columns
.Where(c => c.ColumnName.EndsWith("_id") && !c.IsPrimaryKey)
.Select(c => new DmIndex($"IX_{table.TableName}_{c.ColumnName}", new[] { c.ColumnName }));
table.Indexes = table.Indexes.Concat(performanceIndexes).ToArray();
}
}Configuration Validation
Validate your configuration to catch issues early:
public static class ConfigurationValidator
{
public static void ValidateTableConfiguration<T>()
{
var table = DmTableFactory.GetTable(typeof(T));
// Validate table has primary key
if (table.PrimaryKeyConstraint == null || table.PrimaryKeyConstraint.Columns.Count == 0)
{
throw new InvalidOperationException($"Table {table.TableName} must have a primary key");
}
// Validate foreign key references exist
foreach (var fk in table.ForeignKeyConstraints ?? [])
{
if (string.IsNullOrEmpty(fk.ReferencedTableName))
{
throw new InvalidOperationException($"Foreign key {fk.ConstraintName} must reference a table");
}
}
// Validate index coverage
ValidateIndexCoverage(table);
}
private static void ValidateIndexCoverage(DmTable table)
{
var foreignKeyColumns = table.ForeignKeyConstraints
.SelectMany(fk => fk.Columns.Select(c => c.ColumnName))
.ToHashSet();
var indexedColumns = table.Indexes
.SelectMany(idx => idx.Columns.Select(c => c.ColumnName))
.ToHashSet();
var unindexedForeignKeys = foreignKeyColumns.Except(indexedColumns);
if (unindexedForeignKeys.Any())
{
Console.WriteLine($"Warning: Foreign key columns without indexes in {table.TableName}: {string.Join(", ", unindexedForeignKeys)}");
}
}
}
// Usage
ConfigurationValidator.ValidateTableConfiguration<User>();
ConfigurationValidator.ValidateTableConfiguration<Order>();Best Practices
- Configure early - Set up factory configuration at application startup
- Use transactions - Group related DDL operations in transactions
- Set appropriate timeouts - Complex operations may need longer timeouts
- Validate configuration - Check your setup in tests or at startup
- Environment-specific settings - Different configurations for dev/prod
- Provider considerations - Use provider-specific features when beneficial
- Monitor performance - Use cancellation tokens for long-running operations
Remember that DapperMatic is designed to work well with minimal configuration. Only customize when you have specific requirements that differ from the sensible defaults.