using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; namespace Processor; internal static class Utils { private static readonly Random RNG = new(); /// /// Generates a code out of this name. /// /// The name where the code based on /// The data class /// The selector to check, if that key already exists. The string parameter is the current code to check. /// The generated code internal static async Task GenerateCode(string name, DbSet db, Expression> selector) where TDbSet : class { // Filter all non-alphanumeric characters from the name by allowing only A-Z, a-z, 0-9, and spaces from the ASCII table: name = new string(name.Where(c => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or >= '0' and <= '9' or ' ').ToArray()); // Remove any whitespaces from the element name, regardless of how many e.g. spaces the user typed: var code = string.Join('_', name.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)).ToUpperInvariant(); // // The Any() query want's an Expression, but we have an Expression, though. // Therefore, we have to currying the string away: var typeDbSet = Expression.Parameter(typeof(TDbSet), null); var curriedSelector = Expression.Lambda>( Expression.Invoke(selector, typeDbSet, Expression.Constant(code)), typeDbSet ); // Check, if this key already exists. If so, add a random number to the end of the key: if (await db.AnyAsync(curriedSelector)) while (await db.AnyAsync(curriedSelector)) { code += $"_{RNG.Next(1, 10_000)}"; // Due to the changed code & since the string is a constant, we have to re-currying the string away: curriedSelector = Expression.Lambda>( Expression.Invoke(selector, typeDbSet, Expression.Constant(code)), typeDbSet ); } return code; } }