在我的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
}
在此表中,每当我添加新代码时,我希望ReferenceCode
列为唯一,并且随机AlphaNumeric(仅包含字母和数字)字符串具有特定长度。例如,这将允许用户引用特定的票证。
这些是一些包含10个字符长度的示例:TK254X26W1
,W2S564Z111
,1STT135PA5
...
现在,我能够生成具有给定长度的随机字符串。但是,我不确定如何保证他们的唯一性。我是这样做的:
db.Tickets.Add(new Ticket()
{
ReferenceCode = GenerateRandomCode(10),
//...
});
确切地说,我希望GenerateRandomCode
函数或其他方法能够确保生成的字符串未用于其他记录。
我可以使用for
循环来检查每个生成的代码,但我认为这不是一个好主意。特别是过了一会儿,桌子上会有数千条记录。
答案 0 :(得分:6)
您可以使用Guid来生成唯一的(但在安全性方面不是随机的)密钥。
从this SO question拉<:p>
Guid g = Guid.NewGuid();
string GuidString = Convert.ToBase64String(g.ToByteArray());
GuidString = GuidString.Replace("=","");
GuidString = GuidString.Replace("+","");
GuidString = GuidString.ToUpper();
将生成一个唯一的键,以满足您ReferenceCode
属性需求但更长(22个字符)。折叠它并使用X字符将不再保证其唯一性。
OZVV5TPP4U6XJTHACORZEQ
答案 1 :(得分: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;
}
快速了解代码的作用。你传入你的int id - 然后以这样的方式散列它看起来随机,但保证永远不会重复前168万个条目的数字。
然后获取此散列的int值,并将其转换为4位数代码;这是“唯一性部分” - 在第一个168万个ID(互质数字的魔力)的开头保证有不同的4位数代码。
还剩下6个角色。只需用随机字符填充它们 - 这使得整个10位数代码非常难以猜测。
这解决了你的两个问题。它保证是第一百万+记录的独特之处。客户端并不是真的可以“猜测”,因为即使他们猜到算法,他们也会为他们想破解的任何ID提供20亿种不同的可能性。
答案 2 :(得分:3)
这是我保证唯一性并引入一些随机性的方法。
IDENTITY
列的值。您也可以在C#代码中增加应用程序级别值来实现此目的。 Random.Next()
和任何种子,甚至是前一步骤中生成的数字来完成。 EncodeInt32AsString
将前两个步骤中的整数转换为两个字符串(一个是唯一字符串,一个是随机字符串)。该方法返回仅由方法中指定的允许字符组成的字符串。此方法的逻辑类似于不同基数之间的数字转换的方式(例如,将允许的字符串更改为仅0-9,或仅将0-9A-F更改为获取小数/十六进制表示)。因此,结果是&#34;数字&#34;由&#34;数字组成&#34;在allowedList
。 通过保留整个唯一字符串,可确保最终结果的唯一性。 通过使用随机字符串,这引入了随机性。如果目标字符串的长度非常接近唯一字符串的长度,则无法保证随机性。
在我的测试中,为EncodeInt32AsString
调用Int32.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();
}
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();
}
示例输出
为各种输入值调用上述方法会导致:
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
如上所示,唯一字符串对于序号是可预测的,因为它只是在不同基数中表示的相同数字。但是,随机字符串会带来一些熵以防止用户猜测后续数字。此外,通过交错&#34;数字&#34;对于唯一字符串和随机字符串,用户观察任何模式都会变得更加困难。
在上面的示例中,唯一字符串的长度设置为6(因为这允许它表示Int32.MaxValue
),但随机字符串的长度< / em>设置为4,因为OP希望总长度为10个字符。
答案 3 :(得分:2)
您可以使用 rpcrt4.dll 中的 UuidCreateSequential 方法(处理Uuid)在机器上实现绝对唯一性,如下所示。从microsoft检查此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("-","");
答案 4 :(得分:1)
在DB列上放置一个唯一索引,并继续生成,直到DB接受它而没有唯一约束。碰撞将非常罕见。
答案 5 :(得分:1)
我们需要在以前的项目中实现类似的目的。我们只是将一些唯一标识符预生成到一个新表中(我们称之为表A),然后当我们想要在表B中插入一条新记录时,我们只是在触发器中添加了表A中的前1条记录。 p>
答案 6 :(得分:1)
使用您的代码
{{1}}
答案 7 :(得分:0)
试试这个。这对我有用
var buffer = new byte[5];
new Random().NextBytes(buffer);
Console.WriteLine(string.Join("", buffer.Select(b => b.ToString("X2"))));
答案 8 :(得分:0)
使用您的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;
}
键值将确保生成的代码相同,并且随机类没有足够的时间来担心单一性。
答案 9 :(得分:0)
试试这个:
Guid.NewGuid().ToString("N").Substring(0, 10)
我在从C#codebase的SQL表中为ID生成随机字符串时使用了它。这取决于C#Guid的随机性,每次你得到一个新的字母数字字符串。
答案 10 :(得分:0)
使用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());
}
答案 11 :(得分:-4)
也许这会对你有所帮助
DECLARE @userReportId BIGINT
SET @userReportId = FLOOR(RAND()*(10000000000000-1)+ 1);