Home

Awesome

Ulid

GitHub Actions Releases

Fast C# Implementation of ULID for .NET Core and Unity. Ulid is sortable, random id generator. This project aims performance by fastest binary serializer(MessagePack-CSharp) technology. It achives faster generate than Guid.NewGuid.

image

NuGet: Ulid or download .unitypackage from Ulid/Releases page.

Install-Package Ulid
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

Table of Contents

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

How to use

Similar api to Guid.

Allow to convert Guid.

Performance

Guid is standard corelib guid. Ulid is this library. NUlid is competitor RobThree/NUlid.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_73.13 nsNA1.00----
Ulid_65.41 nsNA0.89----
NUlid_209.89 nsNA2.870.0162--104 B

Ulid.NewUlid() is faster than Guid.NewGuid() and zero allocation.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_197.98 nsNA1.00----
Ulid_28.67 nsNA0.14----
NUlid_161.03 nsNA0.810.0441--280 B

from string(Base32) to ulid, Ulid.Parse(string) is fastest and zero allocation.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_57.73 nsNA1.000.0163--104 B
Ulid_38.77 nsNA0.670.0126--80 B
NUlid_96.76 nsNA1.680.0583--368 B

to string representation(Base32), Ulid.ToString() is fastest and less allocation.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_205.7 nsNA1.000.0162--104 B
Ulid_110.2 nsNA0.540.0125--80 B
NUlid_301.7 nsNA1.470.0744--472 B

case of get the string representation immediately, Ulid is twice faster than Guid.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_0.9706 nsNA1.00----
Ulid_1.0329 nsNA1.06----
NUlid_20.6175 nsNA21.240.0063--40 B

GetHashCode is called when use dictionary's key. Ulid is fast and zero allocation.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_1.819 nsNA1.00----
Ulid_2.023 nsNA1.11----
NUlid_29.875 nsNA16.430.0126--80 B

Equals is called when use dictionary's key. Ulid is fast and zero allocation.

MethodMeanErrorRatioGen 0/1k OpGen 1/1k OpGen 2/1k OpAllocated Memory/Op
Guid_5.409 nsNA1.00----
Ulid_3.838 nsNA0.71----
NUlid_17.126 nsNA3.170.0063--40 B

CompareTo is called when use sort. Ulid is fastest and zero allocation.

Cli

You can install command-line to generate ulid string by .NET Core Global Tool.

dotnet tool install --global ulid-cli

after installed, you can call like here.

$ dotnet ulid
01D7CB31YQKCJPY9FDTN2WTAFF

$ dotnet ulid -t "2019/03/25" -min
01D6R3EBC00000000000000000

$ dotnet ulid -t "2019/03/25" -max
01D6R3EBC0ZZZZZZZZZZZZZZZZ

$ dotnet ulid -t "2019/03/25" -max -base64
AWmwNy2A/////////////w==
argument list:
-t, -timestamp: [default=null]timestamp(converted to UTC, ISO8601 format recommended)
-r, -randomness: [default=null]randomness bytes(formatted as Base32, must be 16 characters, case insensitive)
-b, -base64: [default=False]output as base64 format, or output base32 if false
-min, -minRandomness: [default=False]min-randomness(use 000...)
-max, -maxRandomness: [default=False]max-randomness(use ZZZ...)

This CLI tool is powered by ConsoleAppFramework.

Integrate

System.Text.Json

NuGet: Ulid.SystemTextJson

You can use custom Ulid converter - Cysharp.Serialization.Json.UlidJsonConverter.

var options = new JsonSerializerOptions()
{
    Converters =
    {
        new Cysharp.Serialization.Json.UlidJsonConverter()
    }
};

JsonSerializer.Serialize(Ulid.NewUlid(), options);

If application targetframework is netcoreapp3.0, converter is builtin, does not require to add Ulid.SystemTextJson package, and does not require use custom options.

MessagePack-CSharp

NuGet: Ulid.MessagePack

You can use custom Ulid formatter - Cysharp.Serialization.MessagePack.UlidMessagePackFormatter and resolver - Cysharp.Serialization.MessagePack.UlidMessagePackResolver.

var resolver = MessagePack.Resolvers.CompositeResolver.Create(
    Cysharp.Serialization.MessagePack.UlidMessagePackResolver.Instance,
    MessagePack.Resolvers.StandardResolver.Instance);
var options = MessagePackSerializerOptions.Standard.WithResolver(resolver);

MessagePackSerializer.Serialize(Ulid.NewUlid(), options);

If you want to use this custom formatter on Unity, download UlidMessagePackFormatter.cs.

Dapper

For Dapper or other ADO.NET database mapper, register custom converter from Ulid to binary or Ulid to string.

public class BinaryUlidHandler : TypeHandler<Ulid>
{
    public override Ulid Parse(object value)
    {
        return new Ulid((byte[])value);
    }

    public override void SetValue(IDbDataParameter parameter, Ulid value)
    {
        parameter.DbType = DbType.Binary;
        parameter.Size = 16;
        parameter.Value = value.ToByteArray();
    }
}

public class StringUlidHandler : TypeHandler<Ulid>
{
    public override Ulid Parse(object value)
    {
        return Ulid.Parse((string)value);
    }

    public override void SetValue(IDbDataParameter parameter, Ulid value)
    {
        parameter.DbType = DbType.StringFixedLength;
        parameter.Size = 26;
        parameter.Value = value.ToString();
    }
}

// setup handler
Dapper.SqlMapper.AddTypeHandler(new BinaryUlidHandler());

Entity Framework Core

to use in EF, create ValueConverter and bind it.

public class UlidToBytesConverter : ValueConverter<Ulid, byte[]>
{
    private static readonly ConverterMappingHints defaultHints = new ConverterMappingHints(size: 16);

    public UlidToBytesConverter() : this(null)
    {
    }

    public UlidToBytesConverter(ConverterMappingHints mappingHints = null)
        : base(
                convertToProviderExpression: x => x.ToByteArray(),
                convertFromProviderExpression: x => new Ulid(x),
                mappingHints: defaultHints.With(mappingHints))
    {
    }
}

public class UlidToStringConverter : ValueConverter<Ulid, string>
{
    private static readonly ConverterMappingHints defaultHints = new ConverterMappingHints(size: 26);

    public UlidToStringConverter() : this(null)
    {
    }

    public UlidToStringConverter(ConverterMappingHints mappingHints = null)
        : base(
                convertToProviderExpression: x => x.ToString(),
                convertFromProviderExpression: x => Ulid.Parse(x),
                mappingHints: defaultHints.With(mappingHints))
    {
    }
}

To use those converters, you can either specify individual properties of entities in OnModelCreating method of your context:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<MyEntity>()
        .Property(e => e.MyProperty)
        .HasConversion<UlidToStringConverter>()
        .HasConversion<UlidToBytesConverter>();
}

or use model bulk configuration for all properties of type Ulid. To do this, overload ConfigureConventions method of your context:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
        .Properties<Ulid>()
        .HaveConversion<UlidToStringConverter>()
        .HaveConversion<UlidToBytesConverter>();
}

License

This library is under the MIT License.