Dapper currently has hardcoded values for cache eviction:
Description: Dapper currently has hardcoded values for cache eviction:
private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
These settings control how often Dapper clears its query cache and how many times a cached query must be accessed before it's retained. While they work for general use cases, they do not optimize well for all database architectures—especially in silo-style database designs or microservices where query patterns and cache retention needs differ significantly. Current Issues
Fixed Cache Behavior: Users cannot modify COLLECT_PER_ITEMS or COLLECT_HIT_COUNT_MIN without changing Dapper’s source.
Unbounded Memory Growth: Since COLLECT_HIT_COUNT_MIN = 0, queries are cached regardless of frequency, which can lead to excessive memory usage in high-throughput systems.
Inefficient for Siloed Architectures: Some databases need frequent cache resets, while others need long-term caching. A one-size-fits-all approach is limiting.
Lack of Cache Identity Control: Currently, Dapper automatically determines the cache key (Identity) without allowing users to define custom cache strategies, leading to potential inefficiencies when working with different caching requirements. Proposed Solution
Introduce configurable settings to allow dynamic tuning of cache behavior.
public static class DapperCacheSettings
{
public static int CollectPerItems { get; set; } = 1000; // Default remains 1000
public static int CollectHitCountMin { get; set; } = 0; // Default remains 0
public static Func<SqlMapper.Identity, SqlMapper.Identity>? CustomIdentityResolver { get; set; } = null; // Optional custom cache key
}
Then modify SqlMapper to use these values instead of hardcoded constants:
private static int _collectPerItems => DapperCacheSettings.CollectPerItems;
private static int _collectHitCountMin => DapperCacheSettings.CollectHitCountMin;
private static SqlMapper.Identity ResolveCacheIdentity(SqlMapper.Identity original)
{
return DapperCacheSettings.CustomIdentityResolver?.Invoke(original) ?? original;
}
Benefits of This Change
✅ Prevents Uncontrolled Cache Growth: Users can set a higher COLLECT_HIT_COUNT_MIN to avoid caching rarely used queries. ✅ Improves Performance for Large Workloads: Allows tuning COLLECT_PER_ITEMS to optimize cache cleaning frequency. ✅ Better Support for Microservices and Siloed Databases: Different services can have different cache behaviors. ✅ Custom Cache Identity Control: Developers can define their own Identity resolution strategy to fine-tune cache behavior based on query structure or business needs. ✅ Backward Compatibility: Defaults remain the same, so existing applications won’t break.
Alternative Considerations
If introducing global settings is not ideal, consider allowing users to pass cache settings via CommandDefinition or SqlMapper.Settings.
Would the Dapper team be open to a PR implementing this enhancement?
IMO we can do a better job here by removing the need for this cache, which is what the DapperAOT work achieves. This is not 100% feature parity yet, which we should improve - but if this is currently causing you pain, I would encourage you to try the AOT pieces, and report any gaps that are causing pain. In particular, I think we should be able to create a basic hybrid mode where vanilla Dapper can spot and use AOT features, without needing the full AOT rewrite to be enabled.