Identifiant unique court .NET

91

J'ai besoin d'un identifiant unique dans .NET (je ne peux pas utiliser le GUID car il est trop long pour ce cas).

Les gens pensent-ils que l'algorithme utilisé ici est un bon candidat ou avez-vous d'autres suggestions?

Noel
la source
3
Combien court? Et comment unique? Le GUID est garanti unique s'il est basé sur l'adresse matérielle d'une carte Ethernet; tout ce qui est produit purement mathématiquement ne peut jamais être prouvé unique - juste probablement unique (avec une probabilité astronomiquement élevée).
Jon
15 de longueur, et aussi unique (probablement) que possible
Noel
3
var aléatoire = 4; // bien assez
KristoferA
3
15 quelle longueur? 15 octets? Si tel est le cas, pourquoi ne pas simplement retirer un octet d'un guid ..?
KristoferA
3
@KristoferA enlever un octet dans un guid augmente astronomiquement vos chances de collisions clés. Si vous supprimez le mauvais octet ordonné, il peut être certain qu'il entre en collision.
Chris Marisic

Réponses:

96

Celui-ci est bon - http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

et aussi ici GUID de type YouTube

Vous pouvez utiliser Base64:

string base64Guid = Convert.ToBase64String(Guid.NewGuid().ToByteArray());

Cela génère une chaîne comme E1HKfn68Pkms5zsZsvKONw ==. Comme un GUID est toujours de 128 bits, vous pouvez omettre le == dont vous savez qu'il sera toujours présent à la fin et qui vous donnera une chaîne de 22 caractères. Ce n'est pas aussi court que YouTube.

Dor Cohen
la source
11
Une note courte: si cela est nécessaire pour les URL, quiconque l'utilise peut également vouloir nettoyer les caractères «+» et «/»
pootzko
3
Blog de madskristensen
user3613932
1
Je voulais un identifiant unique de 23 caractères maximum pour UnnyNet dans Unity, et j'étais coincé avec mes grands GUID stupides, et vous m'avez rendu si heureux :)
nipunasudha
38

J'utilise une approche similaire à celle de Dor Cohen, mais en supprimant certains caractères spéciaux:

var uid = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");     

Cela ne produira que des caractères alphanumériques. Il n'est pas garanti que les UID aient toujours la même longueur. Voici un exemple d'exécution:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg
Jaime
la source
6
Vous perdrez certaines propriétés garanties par les GUID en jetant des informations comme celle-ci. Je recommanderais de remplacer les caractères avec lesquels vous n'êtes pas à l'aise par des caractères différents tout en préservant la bijection entre les GUID et leur format de sérialisation.
Lukáš Lánský
3
C'est un bon choix à utiliser si vous ne voulez PAS convertir en GUID mais avez juste besoin de caractères aléatoires pour autre chose ... par exemple le suivi des travailleurs dans plusieurs threads, ou le préfixe de journalisation pour les objets threadés, etc.
Piotr Kula
22
var ticks = new DateTime(2016,1,1).Ticks;
var ans = DateTime.Now.Ticks - ticks;
var uniqueId = ans.ToString("x");

Conservez une date de référence (qui dans ce cas est le 1er janvier 2016) à partir de laquelle vous commencerez à générer ces identifiants. Cela rendra vos identifiants plus petits.

Numéro généré: 3af3c14996e54

adeel41
la source
millisecondsest toujours 0 pour cet DateTimeobjet
Teejay
Supprimez également la dernière phrase.
Teejay
1
oneliner: var uniqueId = (DateTime.Now.Ticks - nouveau DateTime (2016, 1, 1) .Ticks) .ToString ("x");
Sgedda
4
Pas bon pour la génération d'identifiants presque en même temps comme dans un for-loop.eg dotnetfiddle.net/L3MIgZ
Jaider
16

Paquet utilisable simple. Je l'utilise pour le générateur d'identifiant de demande temporelle.

https://www.nuget.org/packages/shortid

https://github.com/bolorundurowb/shortid

Les usages System.Random

string id = ShortId.Generate();
// id = KXTR_VzGVUoOY

(à partir de la page github)

Si vous souhaitez contrôler le type d'identifiant généré en spécifiant si vous voulez des nombres, des caractères spéciaux et la longueur, appelez la méthode Generate et passez trois paramètres, le premier un booléen indiquant si vous voulez des nombres, le second un booléen indiquant si vous le souhaitez caractères spéciaux, le dernier un nombre indiquant votre préférence de longueur.

string id = ShortId.Generate(true, false, 12);
// id = VvoCDPazES_w
BozoJoe
la source
10

Pour autant que je sache, il n'est pas garanti que le simple fait de retirer une partie d'un GUID soit unique - en fait, c'est loin d'être unique.

La chose la plus courte que je connaisse qui garantit l'unicité mondiale est présentée dans ce billet de blog de Jeff Atwood . Dans l'article lié, il discute de plusieurs façons de raccourcir un GUID et, à la fin, le réduit à 20 octets via le codage Ascii85 .

Cependant, si vous avez absolument besoin d'une solution ne dépassant pas 15 octets, je crains que vous n'ayez pas d'autre choix que d'utiliser quelque chose qui n'est pas garanti d'être unique au monde.

Christian Specht
la source
6

Les valeurs IDENTITY doivent être uniques dans une base de données, mais vous devez être conscient des limites ... par exemple, cela rend les insertions de données en masse pratiquement impossibles, ce qui vous ralentira si vous travaillez avec un très grand nombre d'enregistrements.

Vous pouvez également utiliser une valeur de date / heure. J'ai vu plusieurs bases de données où ils utilisent la date / heure pour être le PK, et même si ce n'est pas super propre, cela fonctionne. Si vous contrôlez les insertions, vous pouvez effectivement garantir que les valeurs seront uniques dans le code.

David
la source
6

Pour mon application locale, j'utilise cette approche basée sur le temps:

/// <summary>
/// Returns all ticks, milliseconds or seconds since 1970.
/// 
/// 1 tick = 100 nanoseconds
/// 
/// Samples:
/// 
/// Return unit     value decimal           length      value hex       length
/// --------------------------------------------------------------------------
/// ticks           14094017407993061       17          3212786FA068F0  14
/// milliseconds    1409397614940           13          148271D0BC5     11
/// seconds         1409397492              10          5401D2AE        8
///
/// </summary>
public static string TickIdGet(bool getSecondsNotTicks, bool getMillisecondsNotTicks, bool getHexValue)
{
    string id = string.Empty;

    DateTime historicalDate = new DateTime(1970, 1, 1, 0, 0, 0);

    if (getSecondsNotTicks || getMillisecondsNotTicks)
    {
        TimeSpan spanTillNow = DateTime.UtcNow.Subtract(historicalDate);

        if (getSecondsNotTicks)
            id = String.Format("{0:0}", spanTillNow.TotalSeconds);
        else
            id = String.Format("{0:0}", spanTillNow.TotalMilliseconds);
    }
    else
    {
        long ticksTillNow = DateTime.UtcNow.Ticks - historicalDate.Ticks;
        id = ticksTillNow.ToString();
    }

    if (getHexValue)
        id = long.Parse(id).ToString("X");

    return id;
}
Pollitzer
la source
3

ici ma solution, n'est pas sûre pour la concurrence, pas plus de 1000 GUID par seconde et thread-safe.

public static class Extensors
{

    private static object _lockGuidObject;

    public static string GetGuid()
    {

        if (_lockGuidObject == null)
            _lockGuidObject = new object();


        lock (_lockGuidObject)
        {

            Thread.Sleep(1);
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            var epochLong = Convert.ToInt64((DateTime.UtcNow - epoch).TotalMilliseconds);

            return epochLong.DecimalToArbitrarySystem(36);

        }

    }

    /// <summary>
    /// Converts the given decimal number to the numeral system with the
    /// specified radix (in the range [2, 36]).
    /// </summary>
    /// <param name="decimalNumber">The number to convert.</param>
    /// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
    /// <returns></returns>
    public static string DecimalToArbitrarySystem(this long decimalNumber, int radix)
    {
        const int BitsInLong = 64;
        const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        if (radix < 2 || radix > Digits.Length)
            throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());

        if (decimalNumber == 0)
            return "0";

        int index = BitsInLong - 1;
        long currentNumber = Math.Abs(decimalNumber);
        char[] charArray = new char[BitsInLong];

        while (currentNumber != 0)
        {
            int remainder = (int)(currentNumber % radix);
            charArray[index--] = Digits[remainder];
            currentNumber = currentNumber / radix;
        }

        string result = new String(charArray, index + 1, BitsInLong - index - 1);
        if (decimalNumber < 0)
        {
            result = "-" + result;
        }

        return result;
    }

code non optimisé, juste un échantillon !.

ur3an0
la source
Bien qu'une solution intéressante, il n'y a aucune garantie que UtcNowrenvoie une valeur tique unique pour chaque milliseconde: par les remarques , la résolution dépend de la minuterie du système. De plus, vous feriez mieux de vous assurer que l'horloge système ne change pas en arrière! (Étant donné que la réponse de user13971889 a placé cette question en haut de mon flux et que j'ai critiqué cette réponse, je suppose que je devrais répéter cette critique ici.)
Joe Sewell
3

Si votre application n'a pas quelques MILLIION personnes, en utilisant cela génère une courte chaîne unique au même MILLISECOND, vous pouvez penser à utiliser la fonction ci-dessous.

private static readonly Object obj = new Object();
private static readonly Random random = new Random();
private string CreateShortUniqueString()
{
    string strDate = DateTime.Now.ToString("yyyyMMddhhmmssfff");
    string randomString ;
    lock (obj)
    {
        randomString = RandomString(3);
    }
    return strDate + randomString; // 16 charater
}
private string RandomString(int length)
{

    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxy";
    var random = new Random();
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

remplacez aaaa par aa si vous devez simplement utiliser votre application au cours des 99 prochaines années.
Mise à jour 20160511 : Fonction aléatoire correcte
- Ajouter un objet Lock
- Déplacer une variable aléatoire hors de la fonction RandomString
Ref

Chris Phan
la source
1
C'est génial bien que vous ne devriez pas initialiser un nouveau Random à chaque fois - la raison en lockest de vous permettre de réutiliser la même Randominstance. Je pense que vous avez oublié de supprimer cette ligne!
NibblyPig
1

Je sais que c'est assez loin de la date de publication ... :)

J'ai un générateur qui ne produit que 9 caractères hexa , par exemple: C9D6F7FF3, C9D6FB52C

public class SlimHexIdGenerator : IIdGenerator
{
    private readonly DateTime _baseDate = new DateTime(2016, 1, 1);
    private readonly IDictionary<long, IList<long>> _cache = new Dictionary<long, IList<long>>();

    public string NewId()
    {
        var now = DateTime.Now.ToString("HHmmssfff");
        var daysDiff = (DateTime.Today - _baseDate).Days;
        var current = long.Parse(string.Format("{0}{1}", daysDiff, now));
        return IdGeneratorHelper.NewId(_cache, current);
    }
}


static class IdGeneratorHelper
{
    public static string NewId(IDictionary<long, IList<long>> cache, long current)
    {
        if (cache.Any() && cache.Keys.Max() < current)
        {
            cache.Clear();
        }

        if (!cache.Any())
        {
            cache.Add(current, new List<long>());
        }

        string secondPart;
        if (cache[current].Any())
        {
            var maxValue = cache[current].Max();
            cache[current].Add(maxValue + 1);
            secondPart = maxValue.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            cache[current].Add(0);
            secondPart = string.Empty;
        }

        var nextValueFormatted = string.Format("{0}{1}", current, secondPart);
        return UInt64.Parse(nextValueFormatted).ToString("X");
    }
}
Hazjack
la source
1

Basé sur la réponse de @ dorcohen et le commentaire de @ pootzko. Vous pouvez utiliser ceci. C'est sûr sur le fil.

var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());
Un humain
la source
Résultat si quelqu'un se demande: Jzhw2oVozkSNa2IkyK4ilA2ou essayez vous-même sur dotnetfiddle.net/VIrZ8j
chriszo111
1

Sur la base de quelques autres, voici ma solution qui fournit un guid encodé différent qui est sûr pour les URL (et Docker) et ne perd aucune information:

Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("=", "").Replace("+", "-").Replace("/", "_");

Les exemples de sorties sont:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig
Roemer
la source
1

En C #, une longvaleur a 64 bits, qui si elle est encodée avec Base64, il y aura 12 caractères, dont 1 remplissage =. Si nous coupons le remplissage =, il y aura 11 caractères.

Une idée folle ici est que nous pourrions utiliser une combinaison d'époque Unix et d'un compteur pour une valeur d'époque pour former une longvaleur. L'époque Unix en C # DateTimeOffset.ToUnixEpochMillisecondsest au longformat, mais les 2 premiers octets des 8 octets sont toujours 0, car sinon la valeur de la date et de l'heure sera supérieure à la valeur maximale de la date et de l'heure. Cela nous donne donc 2 octets pour placer un ushortcompteur.

Ainsi, au total, tant que le nombre de génération d'ID ne dépasse pas 65536 par milliseconde, nous pouvons avoir un ID unique:

// This is the counter for current epoch. Counter should reset in next millisecond
ushort currentCounter = 123;

var epoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Because epoch is 64bit long, so we should have 8 bytes
var epochBytes = BitConverter.GetBytes(epoch);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    epochBytes = epochBytes.Reverse().ToArray();
}

// The first two bytes are always 0, because if not, the DateTime.UtcNow is greater 
// than DateTime.Max, which is not possible
var counterBytes = BitConverter.GetBytes(currentCounter);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    counterBytes = counterBytes.Reverse().ToArray();
}

// Copy counter bytes to the first 2 bytes of the epoch bytes
Array.Copy(counterBytes, 0, epochBytes, 0, 2);

// Encode the byte array and trim padding '='
// e.g. AAsBcTCCVlg
var shortUid = Convert.ToBase64String(epochBytes).TrimEnd('=');
weichch
la source
1
    public static string ToTinyUuid(this Guid guid)
    {
        return Convert.ToBase64String(guid.ToByteArray())[0..^2]  // remove trailing == padding 
            .Replace('+', '-')                          // escape (for filepath)
            .Replace('/', '_');                         // escape (for filepath)
    }

Usage

Guid.NewGuid().ToTinyUuid()

Ce n'est pas sorcier de se reconvertir, donc je vous laisse autant.

Billy Jake O'Connor
la source
0

Si vous n'avez pas besoin de taper la chaîne, vous pouvez utiliser ce qui suit:

static class GuidConverter
{
    public static string GuidToString(Guid g)
    {
        var bytes = g.ToByteArray();
        var sb = new StringBuilder();
        for (var j = 0; j < bytes.Length; j++)
        {
            var c = BitConverter.ToChar(bytes, j);
            sb.Append(c);
            j++;
        }
        return sb.ToString();
    }

    public static Guid StringToGuid(string s) 
        => new Guid(s.SelectMany(BitConverter.GetBytes).ToArray());
}

Cela convertira le Guid en une chaîne de 8 caractères comme ceci:

{b77a49a5-182b-42fa-83a9-824ebd6ab58d} -> "䦥 띺 ᠫ 䋺 ꦃ 亂 檽 趵"

{c5f8f7f5-8a7c-4511-b667-8ad36b446617} -> " 엸 詼 䔑 架 펊 䑫 ᝦ"

Whopperle
la source
0

Voici ma petite méthode pour générer un identifiant unique aléatoire et court. Utilise un rng cryptographique pour la génération sécurisée de nombres aléatoires. Ajoutez les caractères dont vous avez besoin à la charschaîne.

private string GenerateRandomId(int length)
{
    char[] stringChars = new char[length];
    byte[] randomBytes = new byte[length];
    using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(randomBytes);
    }

    string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";           

    for (int i = 0; i < stringChars.Length; i++)
    {
        stringChars[i] = chars[randomBytes[i] % chars.Length];
    }

    return new string(stringChars);
}
Ryan
la source
0

pour ne pas perdre de caractères (+ / -) et si vous voulez utiliser votre guid dans une url, il faut le transformer en base32

pour 10 000 000 pas de clé en double

    public static List<string> guids = new List<string>();
    static void Main(string[] args)
    {
        for (int i = 0; i < 10000000; i++)
        {
            var guid = Guid.NewGuid();
            string encoded = BytesToBase32(guid.ToByteArray());
            guids.Add(encoded);
            Console.Write(".");
        }
        var result = guids.GroupBy(x => x)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key);

        foreach (var res in result)
            Console.WriteLine($"Duplicate {res}");

        Console.WriteLine($"*********** end **************");
        Console.ReadLine();
    }

    public static string BytesToBase32(byte[] bytes)
    {
        const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        string output = "";
        for (int bitIndex = 0; bitIndex < bytes.Length * 8; bitIndex += 5)
        {
            int dualbyte = bytes[bitIndex / 8] << 8;
            if (bitIndex / 8 + 1 < bytes.Length)
                dualbyte |= bytes[bitIndex / 8 + 1];
            dualbyte = 0x1f & (dualbyte >> (16 - bitIndex % 8 - 5));
            output += alphabet[dualbyte];
        }

        return output;
    }
Philippe Auriou
la source
-1
private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{       
    lock(_getUniqueIdLock)
    {
        System.Threading.Thread.Sleep(1);
        return DateTime.UtcNow.Ticks.ToString("X");
    }
}
user13971889
la source
1
Bien que ce soit une solution intéressante, il n'y a aucune garantie qui UtcNowrenvoie une valeur de graduation unique pour chaque milliseconde: selon les remarques , la résolution dépend de la minuterie du système. De plus, vous feriez mieux de vous assurer que l'horloge système ne change pas en arrière! (La réponse de ur3an0 a également ces problèmes.)
Joe Sewell
D'accord. C'est une approche de pauvre et ne devrait pas être utilisée au-delà de votre propre environnement bien contrôlé.
user13971889
-2

vous pouvez utiliser

code = await UserManager.GenerateChangePhoneNumberTokenAsync(input.UserId, input.MobileNumber);

ses 6beaux caractères seulement, 599527,143354

et lorsque l'utilisateur le virifie simplement

var result = await UserManager.VerifyChangePhoneNumberTokenAsync(input.UserId, input.Token, input.MobileNumber);

j'espère que cela vous aidera

Basheer AL-MOMANI
la source
Je garde toujours mes mots de passe simples, faciles à retenir
Toolkit
-6

J'utilise Guid.NewGuid().ToString().Split('-')[0], il obtient le premier élément du tableau séparé par le '-'. Il suffit de représenter une clé unique.

Diogo
la source
3
Ceci est une erreur. Voir ce lien .
Kasey Speakman