Naming Policies
Naming policies in Cratis Applications control how collection names and property names are transformed when serializing to MongoDB. This ensures consistent naming conventions across your database.
Overview
Section titled “Overview”Collection names and their members are named based on a naming policy (INamingPolicy). The framework provides flexible configuration options and built-in policies for common scenarios.
Default Behavior
Section titled “Default Behavior”The default naming policy does not alter the input, giving you the names exactly as defined in your types:
public class UserAccount{ public string UserName { get; set; } // Stored as: "UserName" public string EmailAddress { get; set; } // Stored as: "EmailAddress" public DateTime CreatedDate { get; set; } // Stored as: "CreatedDate"}Built-in Naming Policies
Section titled “Built-in Naming Policies”Camel Case Policy
Section titled “Camel Case Policy”The most common naming policy is camel case, which converts PascalCase property names to camelCase:
builder.UseCratisMongoDB(configureMongoDB: builder => builder.WithCamelCaseNamingPolicy());With camel case policy:
public class UserAccount{ public string UserName { get; set; } // Stored as: "userName" public string EmailAddress { get; set; } // Stored as: "emailAddress" public DateTime CreatedDate { get; set; } // Stored as: "createdDate"}Custom Naming Policies
Section titled “Custom Naming Policies”You can create your own naming policy by implementing INamingPolicy. The interface has
two members: GetPropertyName transforms a member name, and GetReadModelName transforms
a type into a collection name:
public class SnakeCaseNamingPolicy : INamingPolicy{ public string GetPropertyName(string name) => ToSnakeCase(name);
public string GetReadModelName(Type readModelType) => ToSnakeCase(readModelType.Name);
static string ToSnakeCase(string name) { if (string.IsNullOrEmpty(name)) return name;
var result = new StringBuilder(); for (int i = 0; i < name.Length; i++) { if (i > 0 && char.IsUpper(name[i])) result.Append('_'); result.Append(char.ToLower(name[i])); } return result.ToString(); }}Registering Custom Policies
Section titled “Registering Custom Policies”Register your custom naming policy by passing an instance to WithNamingPolicy:
builder.UseCratisMongoDB(configureMongoDB: builder => builder.WithNamingPolicy(new SnakeCaseNamingPolicy()));This also works when your naming policy requires constructor parameters — just construct the instance with the values you need:
var namingPolicy = new CustomNamingPolicy(prefix: "app_", suffix: "_v1");
builder.UseCratisMongoDB(configureMongoDB: builder => builder.WithNamingPolicy(namingPolicy));How Naming Policies Work
Section titled “How Naming Policies Work”Convention Integration
Section titled “Convention Integration”Naming policies are applied through the NamingPolicyNameConvention, which is automatically registered as a convention pack. This convention:
- Applies to all members: Processes every property and field in your classes
- Uses configured policy: Applies the naming policy you’ve configured
- Integrates with filtering: Respects convention pack filters and ignore attributes
Property Name Transformation
Section titled “Property Name Transformation”The convention applies to:
- Public properties: All public get/set properties
- Public fields: Public field members (if configured)
- Nested objects: Properties within embedded documents
- Collection elements: Properties of objects within arrays
Configuration Examples
Section titled “Configuration Examples”Multiple Policies
Section titled “Multiple Policies”You can create policies that combine multiple transformations:
public class CompoundNamingPolicy : INamingPolicy{ private readonly INamingPolicy[] _policies;
public CompoundNamingPolicy(params INamingPolicy[] policies) { _policies = policies; }
public string GetPropertyName(string name) => _policies.Aggregate(name, (current, policy) => policy.GetPropertyName(current));
public string GetReadModelName(Type readModelType) => _policies.Aggregate(readModelType.Name, (current, policy) => policy.GetPropertyName(current));}
// Usagevar policy = new CompoundNamingPolicy( new CamelCaseNamingPolicy(), new PrefixNamingPolicy("data_"));
builder.UseCratisMongoDB(configureMongoDB: builder => builder.WithNamingPolicy(policy));Conditional Policies
Section titled “Conditional Policies”Create policies that apply different rules based on the property name:
public class ConditionalNamingPolicy : INamingPolicy{ public string GetPropertyName(string name) { // Don't transform ID fields if (name.EndsWith("Id", StringComparison.OrdinalIgnoreCase)) return name.ToLower();
// Use camel case for everything else return char.ToLower(name[0]) + name[1..]; }
public string GetReadModelName(Type readModelType) => char.ToLower(readModelType.Name[0]) + readModelType.Name[1..];}Convention Pack Integration
Section titled “Convention Pack Integration”The naming policy is implemented as a MongoDB convention pack, which means:
Automatic Application
Section titled “Automatic Application”// This is registered automatically during setupConventionRegistry.Register( NamingPolicyNameConvention.ConventionName, new ConventionPack { new NamingPolicyNameConvention() }, type => /* filter logic */);Filtering Support
Section titled “Filtering Support”You can control which types the naming policy applies to using convention pack filters:
public class NoNamingPolicyForDTOs : ICanFilterMongoDBConventionPacksForType{ public bool ShouldInclude(string conventionPackName, IConventionPack conventionPack, Type type) { if (conventionPackName == NamingPolicyNameConvention.ConventionName) { return !type.Name.EndsWith("DTO"); } return true; }}Ignore Naming Conventions
Section titled “Ignore Naming Conventions”For specific types that shouldn’t use naming policies, use the IgnoreConventions attribute:
[IgnoreConventions(NamingPolicyNameConvention.ConventionName)]public class LegacyDocument{ public string UserName { get; set; } // Stored as: "UserName" (unchanged) public string EmailAddr { get; set; } // Stored as: "EmailAddr" (unchanged)}Impact on Queries
Section titled “Impact on Queries”Remember that naming policies affect how you write queries:
With Camel Case Policy
Section titled “With Camel Case Policy”// Property defined as: public string UserName { get; set; }// Stored in MongoDB as: "userName"
// Query using the MongoDB field namevar filter = Builders<User>.Filter.Eq("userName", "john.doe");
// Or use expression trees (automatically converted)var users = await collection .Find(u => u.UserName == "john.doe") // Automatically converted to "userName" .ToListAsync();Error Handling
Section titled “Error Handling”Missing Policy Configuration
Section titled “Missing Policy Configuration”If no naming policy is configured, the framework will throw a descriptive error:
NamingPolicyNotConfigured: A naming policy for MongoDB has not been configured.Please configure it using the WithNamingPolicy method.Null or Empty Names
Section titled “Null or Empty Names”Naming policies should handle edge cases:
public string GetPropertyName(string name){ if (string.IsNullOrWhiteSpace(name)) return name; // Return unchanged for invalid input
// Your transformation logic return TransformName(name);}Best Practices
Section titled “Best Practices”Consistency
Section titled “Consistency”Choose one naming convention and apply it consistently across your application:
// Good: Consistent camelCase{ "userId": "123", "userName": "john", "createdAt": "2024-01-15T10:30:00Z"}
// Avoid: Mixed conventions{ "userId": "123", "UserName": "john", "created_at": "2024-01-15T10:30:00Z"}Consider External Systems
Section titled “Consider External Systems”If you’re integrating with external systems that expect specific naming conventions, align your policy accordingly:
// For systems expecting snake_casebuilder.WithNamingPolicy(new SnakeCaseNamingPolicy());
// For JavaScript/JSON APIs expecting camelCasebuilder.WithCamelCaseNamingPolicy();Performance Considerations
Section titled “Performance Considerations”Naming policies are called for every property during serialization setup, so keep them efficient:
// Good: Simple, efficient transformationpublic string GetPropertyName(string name) => char.ToLower(name[0]) + name[1..];
// Avoid: Complex regex or multiple string operationspublic string GetPropertyName(string name) => Regex.Replace(name, "([A-Z])", "_$1").ToLower().Trim('_');Next Steps
Section titled “Next Steps”- Learn about Class Mapping for custom type configurations
- Explore Convention Packs for advanced customization
- Understand Concepts and how they work with naming policies