Unique string for each record in the database table
在我的ASP.NET MVC 5项目中,我首先使用实体框架代码来处理MS SQL数据库。假设这是桌子:
1 2 3 4 5 6 7 8 9 10 | public class Ticket { [Key] public int Id { get; set; } [Required] public string ReferenceCode { get; set; } //Rest of the table } |
在这个表中,每当我添加一个新代码时,我都希望
这些例子有10个字符长度:
现在,我可以生成具有给定长度的随机字符串。但是,我不知道如何保证它们的独特性。我是这样做的:
1 2 3 4 5 |
确切地说,我希望
我可以使用
介意一个不寻常的解决方案吗?你有两个需要,我可以看到:
随机性。你不能有一个"确定性"函数,因为如果有人能猜出算法,他们就能算出每个人的票号。
唯一性。您不能有任何重复的票据编号-这会使随机有点困难(您必须考虑到碰撞并重试)。
但你没有理由不能两者兼得——你有足够的空间来容纳36^10。您可以将4个字节用于唯一性,6个字节用于随机性。下面是一些示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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位数的代码很难猜测。
这就解决了你的两个问题。它保证在前100多万张唱片中是独一无二的。客户机并不能真正"猜测",因为即使他们猜测了算法,对于他们想要破解的任何给定ID,他们也有20亿种不同的可能性。
您可以使用guid来生成唯一(但在安全性方面不是随机的)密钥。
从这个问题出发:
1 2 3 4 5 | Guid g = Guid.NewGuid(); string GuidString = Convert.ToBase64String(g.ToByteArray()); GuidString = GuidString.Replace("=",""); GuidString = GuidString.Replace("+",""); GuidString = GuidString.ToUpper(); |
将生成一个唯一的密钥来满足您的
OZVV5TPP4U6XJTHACORZEQ
这是我的方法,它保证了独特性并引入了一些随机性。
通过保留整个唯一字符串,可以确保最终结果的唯一性。通过使用随机字符串,这引入了随机性。如果目标字符串的长度非常接近唯一字符串的长度,则无法保证随机性。
在我的测试中,为
2147483647: ZIK0ZJ
在此基础上,目标字符串长度12是理想的,尽管10也是合理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /// <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(); } |
现在,前面的方法只负责对字符串进行编码。为了获得唯一性和随机性属性,可以使用以下逻辑(或类似逻辑)。
在评论中,Kevin指出了实施
The code needs to be tweaked so that it returns a fixed-length string.
Otherwise, you can never be guaranteed of the final result is unique.
If it helps, picture one value generating ABCDE (Unique) +
F8CV1 (Random)... and then later on, another value generating
ABCDEF (Unique) + 8CV1 (Random). Both values are ABCDEF8CV1
这是一个非常有效的点,在下面的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | // 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(); } |
样本输出
为各种输入值调用上述方法会导致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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(因为这允许它表示
您可以使用rpcrt4.dll中的uuidcreatesequential方法(处理uuid)在机器上实现绝对唯一性,如下所示。请检查来自Microsoft的此链接以确保唯一性。在您的计算机或您上传网站的主机上,您将永远不会两次获得相同的ID。
以下代码的输出格式是ASP.NET MVC用于为ASPNetUsers表创建唯一ID的格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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); } } |
用途:
1 2 | Guid gid = SqlGuidUtil.NewSequentialId(); String sid = SqlGuidUtil.NewSequentialId().ToString(); |
样品输出:
637E3E78-23F5-E611-8278-506313F91120
此格式与aspnet标识用户ID格式完全相同。
您还可以删除破折号(不是一个好主意),如下所示:
1 | String sid = SqlGuidUtil.NewSequentialId().ToString().Replace("-",""); |
使用您的代码
1 2 3 4 5 6 7 | string referenceCode=Guid.NewGuid().ToString(); referenceCode=referenceCode.Replace('-', ''); db.Tickets.Add(new Ticket() { ReferenceCode = referenceCode; //... }); |
在我们以前的项目中,我们需要为不同的目的实现类似的功能。我们刚刚在一个新表中预生成了许多唯一标识符(我们称之为表A),然后当我们想要在表B中插入一个新记录时,我们只是在触发器中添加了表A中的前1条记录。
在db列上放置唯一索引,并继续生成,直到db在没有唯一约束的情况下接受它。碰撞将非常罕见。
我使用guid创建了一个函数来生成唯一的字符串。当然,guid会发生冲突,所以我用新的guid修改中间的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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()); } |
试试这个:
1 | Guid.NewGuid().ToString("N").Substring(0, 10) |
我在从C代码库为SQL表中的ID生成随机字符串时使用了这个方法。这取决于c guid的随机性,每次您都会得到一个新的字母数字字符串。
使用您的ID授予唯一性和系统。Random类为您提供随机性,您可能期望如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } |
键值将确保生成的代码相同,并且随机类没有足够的时间来担心单一性。
试试这个。这是我的工作
1 2 3 |
也许这对你有帮助
声明@userreportid bigint
设置@userreportid=floor(rand()*(1000000000000-1)+1);