Skip to main content

RSCG_idempotency by Ignat Andrei

NuGet / site data​

Nuget GitHub last commit GitHub Repo stars

Details​

Info​

info

Name: RSCG_idempotency

Automatic generating Idempotency for function parameters

Author: Ignat Andrei

NuGet: https://www.nuget.org/packages/RSCG_idempotency/

You can find more details at https://github.com/ignatandrei/RSCG_idempotency

Source: https://github.com/ignatandrei/RSCG_idempotency

Author​

note

Ignat Andrei Alt text

Original Readme​

note

RSCG Idempotency Generator - Usage Guide

Installation​

Install the package RSCG_Idempotency NuGet from nuget .

Overview​

The RSCG (Roslyn Source Code Generator) Idempotency module provides automatic duplicate detection and prevention for method parameters. It uses the [Idempotent] attribute to mark parameters that should be tracked, generating helper infrastructure to ensure idempotent behavior.

Key Concepts​

What is Idempotency?​

Idempotency means that performing the same operation multiple times with identical inputs produces the same result as performing it once. This generator helps enforce this by:

  • Tracking which parameter values have been processed before
  • Providing methods to check for duplicates
  • Cleaning up old cached entries automatically

Generated Components​

  1. IdempotentAttribute - Marks parameters for tracking
  2. TimeProvider - Supplies UTC timestamps for cache entries
  3. Helper Methods - Generated per method to manage the duplicate tracking

Usage​

Step 1: Mark Parameters with [Idempotent]​

In your C# method, apply the [Idempotent] attribute to parameters you want to track:

using RSCG_IdemPotency;

public partial class YourClass
{
public void ProcessData([Idempotent] string input, int retry)
{
// Your implementation here
// The generator will create tracking infrastructure for 'input'
}
}

Step 2: Generated Helper Methods​

For each method with [Idempotent] parameters, the generator creates:

Check for Duplicates​

private bool MethodName_ExistsBefore(T parameter)
{
// Returns true if the parameter was processed before
// Returns false if it's the first occurrence
}

Example:

public void ProcessData([Idempotent] string input, int retry)
{
if (ProcessData_ExistsBefore(input))
{
// This input was already processed
Console.WriteLine("Duplicate detected, skipping");
return;
}

// Process new input
Console.WriteLine($"Processing: {input}");
}

Clean Up Old Entries​

private void MethodName_DeletePreviousData(DateTimeOffset dt)
{
// Removes cached entries older than the specified date
}

Example:

public void CleanupCache()
{
var oneHourAgo = DateTimeOffset.UtcNow.AddHours(-1);
ProcessData_DeletePreviousData(oneHourAgo);
Console.WriteLine("Cleaned up entries older than 1 hour");
}

Generated Infrastructure Details​

Caching Mechanism​

For each method, a static ConcurrentDictionary is created:

static ConcurrentDictionary<ParameterType, DateTimeOffset> MethodName_Dict = [];
  • Key: The parameter value being tracked (e.g., string, int)
  • Value: UTC timestamp when first encountered

Thread Safety​

The generator uses ConcurrentDictionary to ensure thread-safe operations in multi-threaded scenarios.

Complete Example​

Original Method Definition​

using RSCG_IdemPotency;

public partial class PaymentProcessor
{
public void ProcessPayment([Idempotent] string transactionId, int maxRetries)
{
if (ProcessPayment_ExistsBefore(transactionId))
{
Console.WriteLine($"Transaction {transactionId} already processed");
return;
}

// Process new transaction
Console.WriteLine($"Processing payment for transaction: {transactionId}");
// ... payment logic ...
}

public void MaintenanceCleanup()
{
// Remove transactions processed more than 30 days ago
var thirtyDaysAgo = DateTimeOffset.UtcNow.AddDays(-30);
ProcessPayment_DeletePreviousData(thirtyDaysAgo);
}
}

Generated Code (Auto-created by RSCG)​

partial class PaymentProcessor
{
static ConcurrentDictionary<string, DateTimeOffset> ProcessPayment_Dict = [];

private bool ProcessPayment_ExistsBefore(string transactionId)
{
var tp = IdemPotentTimeProvider.timeProv;
var utcDate = tp.GetUtcNow();
var existing = ProcessPayment_Dict.GetOrAdd(transactionId, utcDate);
if (utcDate == existing) return false; // First time
return true; // Seen before
}

private void ProcessPayment_DeletePreviousData(DateTimeOffset dt)
{
var values = ProcessPayment_Dict.Where(it => it.Value < dt).ToArray();
if (values.Length == 0) return;
foreach (var item in values)
{
ProcessPayment_Dict.TryRemove(item.Key, out _);
}
}
}

Best Practices​

1. Use Partial Classes​

The generated code modifies partial classes, so declare your class as partial:

public partial class MyService
{
public void MyMethod([Idempotent] string input, int retry)
{
// Implementation
}
}

2. Check for Duplicates Early​

Always call the *_ExistsBefore() method at the start of your logic:

public void ProcessRequest([Idempotent] string requestId)
{
if (ProcessRequest_ExistsBefore(requestId))
return; // Skip duplicate

// Main logic
}

3. Implement Cache Cleanup​

For long-running applications, periodically clean old entries:

public void PeriodicMaintenance()
{
var oneDayAgo = DateTimeOffset.UtcNow.AddDays(-1);
MyMethod_DeletePreviousData(oneDayAgo);
}

4. Multiple Parameters​

If multiple parameters need tracking, apply [Idempotent] to each:

public void Transfer([Idempotent] string sourceAccount, [Idempotent] string destAccount, decimal amount)
{
if (Transfer_ExistsBefore(sourceAccount) || Transfer_ExistsBefore(destAccount))
return;
// Process transfer
}

5. Memory Considerations​

The tracking dictionary persists for the lifetime of the application. For high-volume scenarios:

  • Implement aggressive cleanup schedules
  • Consider application restart strategies
  • Monitor cache size in production

Time Provider​

The generator uses IdemPotentTimeProvider.timeProv (set to TimeProvider.System by default) for UTC timestamps. This enables:

  • Accurate timestamp recording
  • Testability (can be mocked in tests)
  • System-independent time handling

Summary​

ComponentPurpose
[Idempotent] attributeMarks parameters for duplicate tracking
*_ExistsBefore() methodChecks if a parameter was previously processed
*_DeletePreviousData() methodCleans up old cache entries
*_Dict static dictionaryStores tracked parameter values and timestamps
IdemPotentTimeProviderSupplies UTC timestamps

This generator simplifies idempotent operation implementation, reducing the boilerplate needed for duplicate detection and prevention.

About​

note

Generating code for idempotency

How to use​

Example (source csproj, source files)​

This is the CSharp Project that references RSCG_idempotency

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
<PackageReference Include="RSCG_idempotency" Version="10.2026.127.2000" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

</Project>

Generated Files​

Those are taken from $(BaseIntermediateOutputPath)\GX


//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool. Name: https://github.com/ignatandrei/RSCG_idempotency
// Runtime Version: 2026-01-27T18:12:56.0000000
// ( name : Ivo Andric is feeling cut in Vientiane )
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
//</auto-generated>
//------------------------------------------------------------------------------
using System.Linq;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Concurrent;
namespace IdempotencyDemo {
partial class Purchase {


static ConcurrentDictionary<string , DateTimeOffset> PurchaseNow_Dict=[];
/// <summary>
/// Verify if the argument was before
///</summary>
///<param name="input">the data</param>
/// <returns>true if exists before;false if encountered first time</returns>
[global::System.CodeDom.Compiler.GeneratedCode("RSCG_idempotency", "2026.10127.11812")]
private bool PurchaseNow_ExistsBefore(
string UniqueId) {

var tp=IdemPotentTimeProvider.timeProv;
var utcDate = tp.GetUtcNow();
var existing = PurchaseNow_Dict.GetOrAdd(UniqueId,utcDate);
if(utcDate == existing) return false;//it was inserted now
return true;

}
/// <summary>
/// clear the data before a date
///</summary>
///<param name="dt">date to remove previous</param>
private void PurchaseNow_DeletePreviousData(DateTimeOffset dt)
{
var values = PurchaseNow_Dict.Where(it => it.Value < dt).ToArray();
if (values.Length == 0) return;
foreach (var item in values)
{
PurchaseNow_Dict.TryRemove(item.Key,out _);
}
}

}

}

Useful​

Download Example (.NET C#)​

Share RSCG_idempotency​

https://ignatandrei.github.io/RSCG_Examples/v2/docs/RSCG_idempotency

Category "Idempotency" has the following generators:​

1 RSCG_idempotency Nuget GitHub Repo stars 2026-01-28

See category​

Idempotency