【问题标题】:Unique string for each record in the database table数据库表中每条记录的唯一字符串
【发布时间】:2017-07-27 01:23:51
【问题描述】:

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

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

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

    //Rest of the table
}

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

以下是一些长度为 10 个字符的示例:TK254X26W1W2S564Z1111STT135PA5...

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

db.Tickets.Add(new Ticket()
{
   ReferenceCode = GenerateRandomCode(10),
   //...
});

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

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

【问题讨论】:

  • 这个问题太宽泛了(考虑到你有超过 6k 的代表,你现在应该知道这一点了。)
  • @DavidG 好吧,我认为它已经足够具体了,但我肯定会编辑。
  • 为什么不直接使用 Ticket.Id 呢?
  • @Evk 我不太确定,但我认为获得重复的机会以及有权访问该记录的用户远远低于能够对生成代码的算法进行逆向工程。在那种情况下,使用随机代码不是更好吗?你怎么看?
  • 总而言之,如果你想要随机字符串 - 只需在 ReferenceCode 上放置唯一索引,生成它,插入并捕获唯一索引冲突错误(当然这将是非常罕见的)。如果被捕获 - 生成新字符串并再次插入。

标签: c# sql-server entity-framework


【解决方案1】:

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

this SO question拉取:

Guid g = Guid.NewGuid();
string GuidString = Convert.ToBase64String(g.ToByteArray());
GuidString = GuidString.Replace("=","");
GuidString = GuidString.Replace("+","");
GuidString = GuidString.ToUpper();

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

OZVV5TPP4U6XJTHACORZEQ

【讨论】:

    【解决方案2】:

    介意一个不走寻常路的解决方案吗?你有两个需求,我可以看到:

    • 随机性。你不能有一个“确定性”的函数,因为如果有人能猜出算法,他们就能算出其他人的票号。

    • 独特性。您不能有任何重复的票号 - 这使得 Random 有点困难(您必须考虑冲突并重试。)

    但是你没有理由不能同时做这两个 - 你有足够的 36^10 位空间。您可以将 4 个字节用于唯一性,将 6 个字节用于随机性。下面是一些示例代码:

    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;
    }
    

    快速了解代码的作用。您正在传递您的 int id - 然后它以一种 看起来 随机的方式散列,但保证不会在前 168 万个条目中重复一个数字。

    然后它将这个散列的 int 值转换为 4 位代码;这是“唯一性部分”——保证在前 168 万个 ID 的开头使用不同的 4 位代码(互质数的魔力。)

    剩下 6 个角色可以玩。只需用随机字符填充它们 - 这使得整个 10 位代码非常难以猜测。

    这解决了你的两个问题。对于前一百万多条记录,它保证是唯一的。而且客户端也不是真的“可以猜到”的,因为即使他们猜到了算法,对于他们想要破解的任何给定 ID,他们也有 20 亿种不同的可能性。

    【讨论】:

    • 您的方法与 Phylyp 的方法非常相似。但是我选择了那个,因为它在我的需要方面更灵活一些。我也很乐意为您提供代表,但不幸的是该网站不会让我这样做。感谢您花时间发布您的答案。我真的很感激。
    • 嗯,不用担心。但在继续之前,请确保解决了固定长度问题 - 因为否则,生成重复票证的几率与仅生成 10 个随机字符的几率相同。
    【解决方案3】:

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

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

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

    在我的测试中,为Int32.MaxValue 调用EncodeInt32AsString 返回一个唯一的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();
        }
    

    GetRandomizedString 方法

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

    在 cmets 中,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();
        }
    

    样本输出

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

       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
    

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

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

    【讨论】:

    • 谢谢。根据您的代码,我可以修改现有代码以获取记录的Id,然后根据该代码返回一个唯一字符串。另外,我可以设置自己的标准。太棒了。
    • @AlirezaNoori - 我在allowedList 数组的内容中犯了一个小错误,我现在已经在上面的答案中更正了。它不会改变整体答案本身。
    • 代码需要调整,使其返回一个固定长度的字符串。否则,你永远无法保证最终的结果是独一无二的。如果有帮助,请想象一个值生成 [ABCDE](Unique) + [F8CV1](Random)... 然后稍后,另一个值生成 [ABCDEF](Unique) + [8CV1](Random)。两个值都是 ABCDEF8CV1。
    • 非常好的收获,凯文,感谢您指出这一点。我用GetRandomizedString 方法修改了答案,确保唯一和随机字符串具有特定长度。
    【解决方案4】:

    您可以使用 rpcrt4.dll 中的 UuidCreateSequential 方法(处理 Uuid)在机器上实现绝对唯一性,如下所示。检查来自微软的link 以确保唯一性。您永远不会在您的机器或您上传网站的主机上两次获得相同的 ID。

    以下代码的输出格式是 Asp.Net MVC 用来为 AspNetUsers 表创建唯一 id 的格式:

    using System;
    using System.Runtime.InteropServices;
    
    public class SqlGuidUtil
        {
            [DllImport("rpcrt4.dll", SetLastError = true)]
            static extern int UuidCreateSequential(out Guid guid);
    
            public static Guid NewSequentialId()
            {
                Guid guid;
                UuidCreateSequential(out guid);
                var s = guid.ToByteArray();
                var t = new byte[16];
                t[3] = s[0];
                t[2] = s[1];
                t[1] = s[2];
                t[0] = s[3];
                t[5] = s[4];
                t[4] = s[5];
                t[7] = s[6];
                t[6] = s[7];
                t[8] = s[8];
                t[9] = s[9];
                t[10] = s[10];
                t[11] = s[11];
                t[12] = s[12];
                t[13] = s[13];
                t[14] = s[14];
                t[15] = s[15];
                return new Guid(t);
            }
        }
    

    用法:

    Guid gid = SqlGuidUtil.NewSequentialId();
    String sid = SqlGuidUtil.NewSequentialId().ToString();
    

    示例输出:

    637E3E78-23F5-E611-8278-506313F91120

    此格式与 AspNet Identity 用户 ID 格式完全相同。

    您还可以删除破折号(不是一个好主意),如下所示:

    String sid = SqlGuidUtil.NewSequentialId().ToString().Replace("-","");
    

    【讨论】:

    • 这是一个不错的方法,但是字符串的长度太长了。例如,我可能需要长度为 6 的字符串。
    • 是的,您可能无法控制长度。由于您坚持唯一性,因此这是 Windows 功能和最快的无循环生成器。祝你好运
    【解决方案5】:

    在 DB 列上放置一个唯一索引,并继续生成直到 DB 接受它而没有唯一约束。碰撞将非常罕见。

    【讨论】:

      【解决方案6】:

      我们需要在以前的项目中实现类似的目的,以实现不同的目的。我们只是在一个新表(我们称之为表 A)中预先生成了一些唯一标识符,然后当我们想在表 B 中插入一条新记录时,我们只是在触发器中添加了表 A 中的前 1 条记录。

      【讨论】:

        【解决方案7】:

        像这样使用你的代码

        string referenceCode=Guid.NewGuid().ToString();
        referenceCode=referenceCode.Replace('-', '');
        db.Tickets.Add(new Ticket()
        {
           ReferenceCode = referenceCode;
           //...
        });
        

        【讨论】:

          【解决方案8】:

          试试这个。这对我有用

          var buffer = new byte[5];
          new Random().NextBytes(buffer);
          Console.WriteLine(string.Join("", buffer.Select(b => b.ToString("X2"))));
          

          【讨论】:

            【解决方案9】:

            使用您的 ID 来授予唯一性,并使用 System.Random 类来获得您可能期望的随机性:

            private string GenerateRandomCode(int Key)
            {
                Random rnd = new Random(Key);
                char[] values = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToArray();
            
                string result = string.Empty;
                for(int i = 0; i < 10; i++)
                {
                     result += values[rnd.Next(0, values.Length)];
                }
            
                return result;
            }
            

            键值会保证生成的代码相同,随机类没有足够的时间来担心唯一性。

            【讨论】:

              【解决方案10】:

              试试这个:

              Guid.NewGuid().ToString("N").Substring(0, 10)
              

              我在从 C# 代码库的 SQL 表中为 ID 生成随机字符串时使用了它。这依赖于 C# Guid 的随机性,每次你都会得到一个新的字母数字字符串。

              【讨论】:

                【解决方案11】:

                使用 Guid 我创建了一个函数来生成唯一的字符串。当然 GUID 可能会发生冲突,所以我正在用新的 Guid 更改中间的字符串。

                     static string GenerateRandomCode(){
                        string guid = Guid.NewGuid().ToString("N");     
                        List<char> lst = new List<char>();
                        int count = 1;
                
                        foreach(char c in guid){                
                            if(count==11) break;                
                            if(count % 2 ==0){
                                lst.Add(Guid.NewGuid().ToString().ToCharArray()[1]);
                            }
                            else{
                                lst.Add(c);
                            }
                            count++;
                        }           
                        return string.Join("",lst.ToArray());
                    }
                

                【讨论】:

                  【解决方案12】:

                  也许这会对你有所帮助

                  声明@userReportId BIGINT

                  SET @userReportId = FLOOR(RAND()*(10000000000000-1) + 1);

                  【讨论】:

                  • 这不会生成字母数字字符串。
                  • 使用该查询:) 更改数据类型
                  猜你喜欢
                  • 2013-11-01
                  • 2019-09-11
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多