Skip to content

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.

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.

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"
}

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"
}

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();
}
}

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));

Naming policies are applied through the NamingPolicyNameConvention, which is automatically registered as a convention pack. This convention:

  1. Applies to all members: Processes every property and field in your classes
  2. Uses configured policy: Applies the naming policy you’ve configured
  3. Integrates with filtering: Respects convention pack filters and ignore attributes

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

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));
}
// Usage
var policy = new CompoundNamingPolicy(
new CamelCaseNamingPolicy(),
new PrefixNamingPolicy("data_")
);
builder.UseCratisMongoDB(configureMongoDB: builder =>
builder.WithNamingPolicy(policy));

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..];
}

The naming policy is implemented as a MongoDB convention pack, which means:

// This is registered automatically during setup
ConventionRegistry.Register(
NamingPolicyNameConvention.ConventionName,
new ConventionPack { new NamingPolicyNameConvention() },
type => /* filter logic */
);

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;
}
}

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)
}

Remember that naming policies affect how you write queries:

// Property defined as: public string UserName { get; set; }
// Stored in MongoDB as: "userName"
// Query using the MongoDB field name
var 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();

If no naming policy is configured, the framework will throw a descriptive error:

Terminal window
NamingPolicyNotConfigured: A naming policy for MongoDB has not been configured.
Please configure it using the WithNamingPolicy method.

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);
}

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"
}

If you’re integrating with external systems that expect specific naming conventions, align your policy accordingly:

// For systems expecting snake_case
builder.WithNamingPolicy(new SnakeCaseNamingPolicy());
// For JavaScript/JSON APIs expecting camelCase
builder.WithCamelCaseNamingPolicy();

Naming policies are called for every property during serialization setup, so keep them efficient:

// Good: Simple, efficient transformation
public string GetPropertyName(string name) =>
char.ToLower(name[0]) + name[1..];
// Avoid: Complex regex or multiple string operations
public string GetPropertyName(string name) =>
Regex.Replace(name, "([A-Z])", "_$1").ToLower().Trim('_');