SetRangeInHash exception

public class DisableMultipleQueuedItemsAttribute : JobFilterAttribute, IClientFilter, IServerFilter, IElectStateFilter
{
    private static readonly TimeSpan FingerprintTimeout = TimeSpan.FromHours(1);
    private static readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(10);

    void IClientFilter.OnCreated(CreatedContext filterContext)
    {
    }

    void IServerFilter.OnPerforming(PerformingContext filterContext)
    {
    }

    public void OnCreating(CreatingContext filterContext)
    {
        if (!AddFingerprintIfNotExists(filterContext.Connection, filterContext.Job))
        {
            filterContext.Canceled = true;
        }
    }

    public void OnPerformed(PerformedContext filterContext)
    {
        RemoveFingerprint(filterContext.Connection, filterContext.BackgroundJob);
    }

    public void OnStateElection(ElectStateContext context)
    {
        // If for any reason we delete a job from queue we release the lock.
        if (context.CandidateState.Name == "Deleted")
        {
            RemoveFingerprint(context.Connection, context.BackgroundJob);
        }
    }

    private static bool AddFingerprintIfNotExists(IStorageConnection connection, Job job)
    {
        using (connection.AcquireDistributedLock(GetFingerprintLockKey(job), LockTimeout))
        {
            var fingerprint = connection.GetAllEntriesFromHash(GetFingerprintKey(job));

            DateTimeOffset timestamp;

            if (fingerprint != null &&
                fingerprint.ContainsKey("Timestamp") &&
                DateTimeOffset.TryParse(fingerprint["Timestamp"], null, DateTimeStyles.RoundtripKind, out timestamp) &&
                DateTimeOffset.UtcNow <= timestamp.Add(FingerprintTimeout))
            {
                // Actual fingerprint found, returning.
                return false;
            }

            try
            {
                // Fingerprint does not exist, it is invalid (no `Timestamp` key),
                // or it is not actual (timeout expired).
                connection.SetRangeInHash(
                    GetFingerprintKey(job),
                    new Dictionary<string, string>
                {
                { "Timestamp", DateTimeOffset.UtcNow.ToString("o") }
                });
            }
            catch (Exception ex)
            {

                throw;
            }
        

            return true;
        }
    }

    private static string GetFingerprint(Job job)
    {
        string parameters = string.Empty;
        if (job.Args != null)
        {
            parameters = string.Join(".", job.Args);
        }

        if (job.Type == null || job.Method == null)
        {
            return string.Empty;
        }

        var fingerprint = $"{job.Type.FullName}.{job.Method.Name}.{parameters}";

        return fingerprint;
    }

    private static string GetFingerprintKey(Job job)
    {
        return $"Fingerprint:{GetFingerprint(job)}";
    }

    private static string GetFingerprintLockKey(Job job)
    {
        return $"{GetFingerprintKey(job)}:lock";
    }

    private static void RemoveFingerprint(IStorageConnection connection, BackgroundJob job)
    {
        RemoveFingerprint(connection, job.Job);
    }

    private static void RemoveFingerprint(IStorageConnection connection, Job job)
    {
        using (connection.AcquireDistributedLock(GetFingerprintLockKey(job), LockTimeout))
        {
            using (var transaction = connection.CreateWriteTransaction())
            {
                transaction.RemoveHash(GetFingerprintKey(job));
                transaction.Commit();
            }
        }
    }
}

This is the code I found and am using to disable multiple queued jobs at the same time. What happens is that sometimes

 connection.SetRangeInHash(
     GetFingerprintKey(job),
      new Dictionary<string, string>
      {
      { "Timestamp", DateTimeOffset.UtcNow.ToString("o") }
      });

this code fails with exception: “String or binary data would be truncated. The statement has been terminated.”

Call to queue job:

BackgroundJob.Enqueue<TestClass>(x => x.Run(model.From, model.To));

and

GetFingerprintKey(job) 

returns
“Fingerprint:SomeNamespace.Jobs.Hangfire.Jobs.TestClass.Run.13.4.2016 00:00:00.13.4.2016 00:00:00”
and everything is ok. But if I add another string param to method Run() then the fingerprint looks like
“Fingerprint:SomeNamespace.Jobs.Hangfire.Jobs.TestClass.Run.13.4.2016 00:00:00.13.4.2016 00:00:00.x”
and I get the exception.

This is all on local configuration with SQL server. On production it’s even weirder, I can queue job with date 12.4.2016 but not with 13.4.2016. Seems like it’s using en-us culture but culture is set for whole app and even jobs have the right culture when I queue them so I have no idea what is causing it.

Is there a bug in SetRangeInHash?
Or am I missing an obvious bug?
Or is there a better way to disable multiple same queued jobs?
Or am I just crazy?

I found the issue, this writes to HangFire.Hash table and column Key is nvarchar(100). My key is 102 long :smiley:
WIll change the way fingerprint is generated.

Just extra question, would anything break if I went and manually changed that column to nvarchar(max) in my database? I suspect it would work.

You cannot change it to nvarchar(max), as it is indexed column. Total size of all colums in sql server index must not exceed 900 bytes or so.
Slightly increasing it shouldn’t break it though.

But having hash keys too long decreases the lookup speed and increases storage size. Calculate a hash (like sha256 or sha512) from your arbitrary-length string and use it as key, or something.