数据库表中每条记录的唯一字符串

Ali*_*ori 7 c# sql-server entity-framework

在我的Asp.Net MVC 5项目中,我首先使用Entity Framework代码来处理MS SQL数据库.假设这是表:

public class Ticket
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string ReferenceCode { get; set; }

    //Rest of the table
}
Run Code Online (Sandbox Code Playgroud)

在此表中,每当我添加新代码时,我希望该ReferenceCode列是具有特定长度的唯一且随机的AlphaNumeric(仅包含字母和数字)字符串.这将允许用户引用特定的票证.

这些都是一些例子有10个字符长度:TK254X26W1,W2S564Z111,1STT135PA5...

现在,我能够生成给定长度的随机字符串.但是,我不确定如何保证它们的独特性.我是这样做的:

db.Tickets.Add(new Ticket()
{
   ReferenceCode = GenerateRandomCode(10),
   //...
});
Run Code Online (Sandbox Code Playgroud)

确切地说,我希望GenerateRandomCode函数或其他方法能够确保生成的字符串未被用于其他记录.

我可以使用for循环来检查每个生成的代码,但我认为这不是一个好主意.特别是一段时间后,表会有数千条记录.

pij*_*olu 6

您可以使用Guid来生成唯一的(但在安全性方面不是随机的)密钥.

这个SO问题中拉出来:

Guid g = Guid.NewGuid();
string GuidString = Convert.ToBase64String(g.ToByteArray());
GuidString = GuidString.Replace("=","");
GuidString = GuidString.Replace("+","");
GuidString = GuidString.ToUpper();
Run Code Online (Sandbox Code Playgroud)

将生成一个唯一的键,以满足您的ReferenceCode财产需求,但更长(22个字符).折叠它并使用X字符将不再保证其唯一性.

OZVV5TPP4U6XJTHACORZEQ


Kev*_*vin 6

想一个不受欢迎的解决方案?你有两个需求,我可以看到:

  • 随机性.你不能拥有"确定性"功能,因为如果有人能猜出算法,他们就可以找出每个人的门票号码.

  • 唯一性.你不能有任何重复的票证 - 这使得Random有点困难(你必须考虑碰撞和重试.)

但是没有理由你不能同时做到这两点 - 你有足够的位空间36 ^ 10.您可以将4个字节专用于Uniqueness,将6个字节专用于Randomness.这是一些示例代码:

public partial class Form1 : Form
{

  private static Random random = new Random();
  private static int largeCoprimeNumber = 502277;
  private static int largestPossibleValue = 1679616;  // 36 ^ 4

  private static char[] Base36Alphabet = new char[] { '0','1','2','3','4','5','6','7','8','9',
    'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' };

  public static string GetTicket(int id)
  {
    int adjustedID = id * largeCoprimeNumber % largestPossibleValue;
    string ticket = IntToString(adjustedID);
    while (ticket.Length < 4) ticket = "0" + ticket;
    return ticket + new string(Enumerable.Repeat(Base36Alphabet, 6) .Select(s => s[random.Next(s.Length)]).ToArray());
  }

  private static string IntToString(int value)
  {
    string result = string.Empty;
    int targetBase = Base36Alphabet.Length;

    do
    {
        result = Base36Alphabet[value % targetBase] + result;
        value = value / targetBase;
    }
    while (value > 0);

    return result;
}
Run Code Online (Sandbox Code Playgroud)

快速了解代码的作用.你传入你的int id - 然后以一种看起来随机的方式进行哈希,但保证永远不会重复前168万条目的数字.

然后它获取这个散列的int值,并将其转换为4位代码; 这是"唯一性部分" - 在第一个168万个ID(互质数字的魔力)的开头保证有不同的4位数代码.

这留下了6个字符.只需用随机字符填充它们 - 这使得整个10位数代码非常难以猜测.

这解决了你的两个问题.它保证是第一百万+记录的独特之处.而且它并不是客户真正"可猜测"的,因为即使他们猜到算法,他们也会为他们想要破解的任何给定ID提供20亿种不同的可能性.


Phy*_*lyp 5

这是我保证唯一性并引入一些随机性的方法。

  1. 使用保证给出唯一编号序列生成器。由于您使用的是 SQL Server,因此这可以是IDENTITY列的值。您也可以在 C# 代码中增加应用程序级别的值来实现这一点。
  2. 生成一个随机整数以给结果带来一些随机性。这可以用Random.Next()任何种子来完成,甚至是在上一步中生成的数字。
  3. 使用一种方法EncodeInt32AsString将前两步的整数转换成两个字符串(一个是唯一字符串,一个是随机字符串)。该方法返回一个仅由方法中指定的允许字符组成的字符串。这种方法的逻辑类似于不同基数之间的数字转换是如何发生的(例如,将允许的字符串更改为仅 0-9,或仅更改为 0-9A-F 以获得十进制/十六进制表示)。因此,结果是由 中的“数字”组成的“数字” allowedList
  4. 连接返回的字符串。保持整个唯一字符串原样(以保证唯一性)并从随机字符串中添加尽可能多的字符以将总长度填充到所需长度。如果需要,这种连接可以是奇特的,通过将随机字符串中的字符在随机点注入唯一字符串。

通过保留整个唯一字符串,这确保了最终结果的唯一性。通过使用随机字符串,这引入了随机性。如果目标字符串的长度非常接近唯一字符串的长度,则无法保证随机性。

在我的测试中,调用EncodeInt32AsStringforInt32.MaxValue返回一个唯一的 6 个字符长的字符串:

2147483647:ZIK0ZJ

在此基础上,目标字符串长度为 12 将是理想的,尽管 10 也是合理的。

EncodeInt32AsString方法

    /// <summary>
    /// Encodes the 'input' parameter into a string of characters defined by the allowed list (0-9, A-Z) 
    /// </summary>
    /// <param name="input">Integer that is to be encoded as a string</param>
    /// <param name="maxLength">If zero, the string is returned as-is. If non-zero, the string is truncated to this length</param>
    /// <returns></returns>
    static String EncodeInt32AsString(Int32 input, Int32 maxLength = 0)
    {
        // List of characters allowed in the target string 
        Char[] allowedList = new Char[] {
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X', 'Y', 'Z' };
        Int32 allowedSize = allowedList.Length;
        StringBuilder result = new StringBuilder(input.ToString().Length);

        Int32 moduloResult;
        while (input > 0)
        {
            moduloResult = input % allowedSize;
            input /= allowedSize;
            result.Insert(0, allowedList[moduloResult]);
        }

        if (maxLength > result.Length)
        {
            result.Insert(0, new String(allowedList[0], maxLength - result.Length));
        }

        if (maxLength > 0)
            return result.ToString().Substring(0, maxLength);
        else
            return result.ToString();
    }
Run Code Online (Sandbox Code Playgroud)

GetRandomizedString方法

现在,前面的方法只负责对字符串进行编码。为了实现唯一性和随机性属性,可以使用以下逻辑(或类似)。

在评论中,Kevin 指出了该EncodeInt32AsString方法的实施存在以下风险:

需要调整代码以使其返回固定长度的字符串。否则,您永远无法保证最终结果是唯一的。如果有帮助,请想象一个值生成 ABCDE(唯一)+ F8CV1(随机)...然后再生成另一个值生成 ABCDEF(唯一)+ 8CV1(随机)。两个值都是 ABCDEF8CV1

这是一个非常有效的观点,以下GetRandomizedString方法已通过指定唯一字符串和随机字符串的长度来解决此问题。该EncodeInt32AsString方法也被修改为将返回值填充到指定的长度。

    // Returns a string that is the encoded representation of the input number, and a random value 
    static String GetRandomizedString(Int32 input)
    {
        Int32 uniqueLength = 6; // Length of the unique string (based on the input) 
        Int32 randomLength = 4; // Length of the random string (based on the RNG) 
        String uniqueString;
        String randomString;
        StringBuilder resultString = new StringBuilder(uniqueLength + randomLength);

        // This might not be the best way of seeding the RNG, so feel free to replace it with better alternatives. 
        // Here, the seed is based on the ratio of the current time and the input number. The ratio is flipped 
        // around (i.e. it is either M/N or N/M) to ensure an integer is returned. 
        // Casting an expression with Ticks (Long) to Int32 results in truncation, which is fine since this is 
        // only a seed for an RNG 
        Random randomizer = new Random(
                (Int32)(
                    DateTime.Now.Ticks + (DateTime.Now.Ticks > input ? DateTime.Now.Ticks / (input + 1) : input / DateTime.Now.Ticks)
                )
            );

        // Get a random number and encode it as a string, limit its length to 'randomLength' 
        randomString = EncodeInt32AsString(randomizer.Next(1, Int32.MaxValue), randomLength); 
        // Encode the input number and limit its length to 'uniqueLength' 
        uniqueString = EncodeInt32AsString(input, uniqueLength);

        // For debugging/display purposes alone: show the 2 constituent parts 
        resultString.AppendFormat("{0}\t {1}\t ", uniqueString, randomString);

        // Take successive characters from the unique and random strings and 
        // alternate them in the output 
        for (Int32 i = 0; i < Math.Min(uniqueLength, randomLength); i++)
        {
            resultString.AppendFormat("{0}{1}", uniqueString[i], randomString[i]);
        }
        resultString.Append((uniqueLength < randomLength ? randomString : uniqueString).Substring(Math.Min(uniqueLength, randomLength)));

        return resultString.ToString();
    }
Run Code Online (Sandbox Code Playgroud)

样本输出

对各种输入值调用上述方法会导致:

   Input Int     Unique String  Random String       Combined String 
------------ ----------------- -------------- --------------------- 
         -10            000000           CRJM            0C0R0J0M00
           0            000000           33VT            03030V0T00
           1            000001           DEQK            0D0E0Q0K01
        2147            0001NN           6IU8            060I0U18NN
       21474            000GKI           VNOA            0V0N0OGAKI
      214748            004LP8           REVP            0R0E4VLPP8
     2147483            01A10B           RPUM            0R1PAU1M0B
    21474836            0CSA38           RNL5            0RCNSLA538
   214748364            3JUSWC           EP3U            3EJPU3SUWC
  2147483647            ZIK0ZJ           BM2X            ZBIMK20XZJ
           1            000001           QTAF            0Q0T0A0F01
           2            000002           GTDT            0G0T0D0T02
           3            000003           YMEA            0Y0M0E0A03
           4            000004           P2EK            0P020E0K04
           5            000005           17CT            01070C0T05
           6            000006           WH12            0W0H010206
           7            000007           SHP0            0S0H0P0007
           8            000008           DDNM            0D0D0N0M08
           9            000009           192O            0109020O09
          10            00000A           KOLD            0K0O0L0D0A
          11            00000B           YUIN            0Y0U0I0N0B
          12            00000C           D8IO            0D080I0O0C
          13            00000D           KGB7            0K0G0B070D
          14            00000E           HROI            0H0R0O0I0E
          15            00000F           AGBT            0A0G0B0T0F
Run Code Online (Sandbox Code Playgroud)

从上面可以看出,唯一字符串对于序列号是可预测的,因为它只是以不同的基数表示的相同数字。但是,随机字符串带来了一些熵,以防止用户猜测后续数字。此外,通过交织唯一字符串随机字符串的“数字”,用户观察任何模式变得稍微困难​​一些。

在上面的示例中,唯一字符串的长度设置为 6(因为允许它表示Int32.MaxValue),但随机字符串的长度设置为 4,因为 OP 想要总长度为 10 个字符。