Identificador único corto de .NET

91

Necesito un identificador único en .NET (no puedo usar GUID porque es demasiado largo para este caso).

¿Piensa la gente que el algoritmo utilizado aquí es un buen candidato o tiene alguna otra sugerencia?

Navidad
fuente
3
¿Qué corto? ¿Y qué único? Se garantiza que GUID es único si se basa en la dirección de hardware de un adaptador ethernet; cualquier cosa producida puramente matemáticamente nunca puede ser demostrablemente única, solo probablemente única (con una probabilidad astronómicamente alta).
Jon
15 de largo, y lo más único (probablemente) posible
Noel
4
var random = 4; // suficientemente bueno
KristoferA
4
15 ¿ de qué longitud? 15 bytes? Si es así, ¿por qué no eliminar un byte de una guía?
KristoferA
3
@KristoferA eliminar un byte en una guía aumenta astronómicamente las posibilidades de colisiones clave. Si elimina el byte ordenado incorrectamente, es posible que colisione.
Chris Marisic

Respuestas:

100

Este es uno bueno: http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

y también aquí GUID similar a YouTube

Podrías usar Base64:

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

Eso genera una cadena como E1HKfn68Pkms5zsZsvKONw ==. Dado que un GUID es siempre de 128 bits, puede omitir el == que sabe que siempre estará presente al final y que le dará una cadena de 22 caracteres. Sin embargo, esto no es tan corto como YouTube.

Dor Cohen
fuente
11
Una nota breve: si esto es necesario para las URL, quien lo use puede querer desinfectar los caracteres '+' y '/' también
pootzko
3
Blog de
madskristensen
1
Yo quería un máximo de 23 Char Identificación larga única para UnnyNet en la unidad, y me quedé con mis grandes GUID mudos, y que me hizo tan feliz :)
nipunasudha
Curiosamente (si está trabajando con la API de Zoom), es casi seguro que así es como generan sus UUID. Tienen 22 caracteres de longitud y están codificados en base64.
vgel
39

Utilizo un enfoque similar al de Dor Cohen pero eliminando algunos caracteres especiales:

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

Esto generará solo caracteres alfanuméricos. No se garantiza que los UID tengan siempre la misma longitud. Aquí hay una muestra de ejecución:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg
Jaime
fuente
6
Perderá algunas propiedades garantizadas por GUID al desechar información como esta. Recomendaría reemplazar los caracteres con los que no se siente cómodo por diferentes caracteres mientras se conserva la biyección entre los GUID y su formato de serialización.
Lukáš Lánský
3
Esta es una buena opción para usar si NO desea volver a convertir a un GUID, pero solo necesita caracteres aleatorios para otra cosa ... por ejemplo, rastrear trabajadores en múltiples subprocesos o Prefijo de registro para objetos con subprocesos, etc.
Piotr Kula
24
var ticks = new DateTime(2016,1,1).Ticks;
var ans = DateTime.Now.Ticks - ticks;
var uniqueId = ans.ToString("x");

Mantenga una fecha de referencia (que en este caso es el 1 de enero de 2016) a partir de la cual comenzará a generar estos identificadores. Esto hará que sus identificadores sean más pequeños.

Número generado: 3af3c14996e54

adeel41
fuente
millisecondses siempre 0 para ese DateTimeobjeto
Teejay
También elimine la última oración.
Teejay
1
oneliner: var uniqueId = (DateTime.Now.Ticks - nuevo DateTime (2016, 1, 1) .Ticks) .ToString ("x");
Sgedda
4
No es bueno para la generación de identificadores casi al mismo tiempo, como en un for-loop.eg dotnetfiddle.net/L3MIgZ
Jaider
17

Paquete utilizable simple. Lo uso para el generador de ID de solicitud temporal.

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

https://github.com/bolorundurowb/shortid

Usos System.Random

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

(de la página de github)

Si desea controlar el tipo de identificación generada especificando si desea números, caracteres especiales y la longitud, llame al método Generate y pase tres parámetros, el primero un booleano que indique si desea números, el segundo un booleano que indique si desea caracteres especiales, el último un número que indica su preferencia de longitud.

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

Hasta donde yo sé, no se garantiza que simplemente eliminar una parte de un GUID sea único ; de hecho, está lejos de ser único.

Lo más breve que sé que garantiza la singularidad global se presenta en esta publicación de blog de Jeff Atwood . En la publicación vinculada, analiza varias formas de acortar un GUID y, al final, lo reduce a 20 bytes a través de la codificación Ascii85 .

Sin embargo, si absolutamente necesita una solución que no tenga más de 15 bytes, me temo que no tiene otra opción que usar algo que no esté garantizado como único a nivel mundial.

Christian Specht
fuente
6

Los valores de IDENTIDAD deben ser únicos en una base de datos, pero debe tener en cuenta las limitaciones ... por ejemplo, hace que las inserciones de datos masivos sean básicamente imposibles, lo que lo ralentizará si está trabajando con una gran cantidad de registros.

También puede utilizar un valor de fecha / hora. He visto varias bases de datos donde usan la fecha / hora para ser el PK, y aunque no es súper limpio, funciona. Si controla las inserciones, puede garantizar efectivamente que los valores serán únicos en el código.

David
fuente
6

Para mi aplicación local, estoy usando este enfoque basado en el tiempo:

/// <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
fuente
3

aquí mi solución, no es segura para la concurrencia, no más de 1000 GUID por segundo y seguro para subprocesos.

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

código no optimizado, solo muestra !.

ur3an0
fuente
Si bien es una solución interesante, no hay garantía de que UtcNowdevuelva un valor de tic único por cada milisegundo: según los comentarios , la resolución depende del temporizador del sistema. Además, ¡será mejor que se asegure de que el reloj del sistema no cambie hacia atrás! (Dado que la respuesta del usuario 13971889 colocó esta pregunta en la parte superior de mi feed y critiqué esa respuesta, creo que debería repetir esa crítica aquí).
Joe Sewell
3

Si su aplicación no tiene unos pocos MILLONES de personas, usando esa cadena de generación única y corta en el MISMO MILISEGUNDO, puede pensar en usar la siguiente función.

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

cambie yyyy a yy si solo necesita usar su aplicación en los próximos 99 años.
Actualización 20160511 : Función aleatoria correcta
- Agregar objeto de bloqueo
- Mover variable aleatoria fuera de la función RandomString
Ref

Chris Phan
fuente
2
Esto es genial, aunque no debe inicializar un nuevo Random cada vez; el motivo lockes permitirle reutilizar la misma Randominstancia. ¡Creo que olvidaste borrar esa línea!
NibblyPig
1

Sé que está bastante lejos de la fecha de publicación ... :)

Tengo un generador que produce solo 9 caracteres Hexa , por ejemplo: 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
fuente
1

Basado en la respuesta de @ dorcohen y el comentario de @ pootzko. Puedes usar esto. Es seguro sobre el cable.

var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());
Un humano
fuente
Resultado si alguien se pregunta: Jzhw2oVozkSNa2IkyK4ilA2o pruebe usted mismo en dotnetfiddle.net/VIrZ8j
chriszo111
1

Basado en algunos otros, aquí está mi solución que proporciona un guid codificado diferente que es seguro para URL (y Docker) y no pierde ninguna información:

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

Los resultados de ejemplo son:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig
Roemer
fuente
1

En C #, un longvalor tiene 64 bits, que si se codifica con Base64, habrá 12 caracteres, incluido 1 relleno =. Si recortamos el relleno =, habrá 11 caracteres.

Una idea loca aquí es que podríamos usar una combinación de Unix Epoch y un contador para un valor de época para formar un longvalor. El Unix Epoch en C # DateTimeOffset.ToUnixEpochMillisecondsestá en longformato, pero los primeros 2 bytes de los 8 bytes son siempre 0, porque de lo contrario el valor de fecha y hora será mayor que el valor máximo de fecha y hora. Entonces eso nos da 2 bytes para colocar un ushortcontador.

Entonces, en total, siempre que el número de generación de ID no exceda 65536 por milisegundo, podemos tener una ID única:

// 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
fuente
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)
    }

Uso

Guid.NewGuid().ToTinyUuid()

No es ciencia espacial volver a convertir, así que les dejo eso.

Billy Jake O'Connor
fuente
0

Si no necesita escribir la cadena, puede usar lo siguiente:

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

Esto convertirá el Guid en una cadena de 8 caracteres como esta:

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

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

Whopperle
fuente
0

Aquí está mi pequeño método para generar una identificación única aleatoria y corta. Utiliza un rng criptográfico para la generación segura de números aleatorios. Agregue los caracteres que necesite a la charscadena.

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
fuente
0

para no perder caracteres (+ / -) y si quieres usar tu guid en una url, debes transformarlo en base32

por 10000000 sin llave duplicada

    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
fuente
-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");
    }
}
usuario13971889
fuente
1
Si bien es una solución interesante, no hay garantía de que UtcNowdevuelva un valor de tic único por cada milisegundo: según los comentarios , la resolución depende del temporizador del sistema. Además, ¡será mejor que se asegure de que el reloj del sistema no cambie hacia atrás! (La respuesta de ur3an0 también tiene estos problemas.)
Joe Sewell
Convenido. Este es el enfoque de un hombre pobre y no debe usarse más allá de su propio entorno bien controlado.
user13971889
-2

puedes usar

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

solo sus 6personajes agradables 599527,143354

y cuando el usuario lo virifica simplemente

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

espero que esto te ayude

Basheer AL-MOMANI
fuente
Siempre mantengo mis contraseñas simples, fáciles de recordar
Kit de herramientas