ASP.NET Core 产生连续 Guid
1 前言
1.1 这篇文章面向的读者
本文不会过多解释 Guid 是什么,以及顺序 Guid 的作用,需要读者自行具备:
- 知道 Guid,并且清楚其作用与优势
- 清楚
Guid.NetGuid()
产生的 Guid 是混乱无序的,想要一种产生顺序 Guid 的算法来保证数据库的高效运行
1.2 连续 Guid 的原理
Guid 形如:
08da7241-170b-c8ef-a094-06224a651c6a
该 Guid 有16字节(byte)共128位(bit),可以包含时间戳,而顺序 Guid 主要是根据时间戳顺序来实现的,所以时间戳的部分,作为排序的决定性因素。
如示例中,前8个字节的内容为时间戳,将其转为十进制为:
637947921111435500
这是一个以时钟周期数(Tick)为单位的时间戳,为从公元1年1月1日0点至今的时钟周期数,1个 Tick 为 100ns(参考微软官方关于 Ticks 的介绍)。
注:上方示例的 Guid 并不符合 RFC 4122 标准,至于什么是 RFC 4122 标准,以及 Guid 的版本,这里不展开,读者自行参考什么是 GUID?。
1.3 本文思路
先大概讲解 ABP 产生连续 Guid 的源码,并提出其问题(高并发产生的 Guid 并不连续)。
接着就问题,以及 ABP 的源码提出解决方案,并给出修改后的源码。
并会就 Sql Server 数据库特殊的 Guid 排序方式,提出一种简单的处理方案,让 Sql Server 与 MySql 等数据库保持一致的排序。
2 ABP 连续 Guid 的实现
2.1 ABP 连续 Guid 源码
ABP产生连续 Guid 的源码,来源于:jhtodd/SequentialGuid.
该方式,产生的 Guid 有6个字节是时间戳(毫秒级),10个字节是随机数。
其中,顺序 Guid 主要是根据时间戳顺序来实现的,所以时间戳的部分,作为排序的决定性因素。
源码主要的部分摘录如下:
public class SequentialGuidGenerator : IGuidGenerator, ITransientDependency
{
public Guid Create(SequentialGuidType guidType)
{
// 获取 10 字节随机序列数组
var randomBytes = new byte[10];
RandomNumberGenerator.GetBytes(randomBytes);
// 获取 Ticks,并处理为毫秒级(1个Tick为100ns,1ms=1000us=1000000ns)
long timestamp = DateTime.UtcNow.Ticks / 10000L;
// 时间戳转为 byte 数组
byte[] timestampBytes = BitConverter.GetBytes(timestamp);
// 因为数组是从 int64 转化过来的,如果是在小端系统中(little-endian),需要翻转
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
byte[] guidBytes = new byte[16];
switch (guidType)
{
case SequentialGuidType.SequentialAsString:
case SequentialGuidType.SequentialAsBinary:
// 16位数组:前6位为时间戳,后10位为随机数
Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);
// .NET中,Data1 和 Data2块 分别视为 Int32 和 Int16
// 跟时间戳从 Int64 转 byte 数组后需要翻转一个理,在小端系统,需要翻转这两个块。
if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 0, 4);
Array.Reverse(guidBytes, 4, 2);
}
break;
case SequentialGuidType.SequentialAtEnd:
// 16位数组:前10位为随机数,后6位为时间戳
Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
break;
}
return new Guid(guidBytes);
}
}
RandomNumberGenerator
用于生成随机序列数组。
DateTime.UtcNow.Ticks
为获取从公元1年1月1日0点至今的时钟周期数,1个Tick为100ns(微软官方关于 Ticks 的介绍)。
SequentialGuidType
为产生连续 Guid 的类别,默认为 SequentialAtEnd
,定义如下:
public enum SequentialGuidType
{
/// <summary>
/// 用于 MySql 和 PostgreSql.当使用 Guid.ToString() 方法进行格式化时连续.
/// </summary>
SequentialAsString,
/// <summary>
/// 用于 Oracle.当使用 Guid.ToByteArray() 方法进行格式化时连续.
/// </summary>
SequentialAsBinary,
/// <summary>
/// 用以 SqlServer.连续性体现于 GUID 的第4块(Data4).
/// </summary>
SequentialAtEnd
}
如各个枚举属性的 summary 描述,主要是因为数据库关于 Guid 排序方式的不同。
至于代码中需要翻转 byte 数组的部分,这一部分,可以参考:Is there a .NET equivalent to SQL Server's newsequentialid()(Stack Overflow 这个问题,有一个回答介绍了时间戳高低位在 Guid 中的排布)。笔者也是看得一脸懵逼,就不在这里误人子弟了。
至于大端、小端,属于计算机组成原理的知识,如果不记得了,可以自行百度(或参考大端、小端基础知识)。
2.2 不同数据库 Guid 的排序方式
由于笔者只用过 MySql 和 Sql Server,测试也只用了这两种数据库测试,故而也只讲这两种。
richardtallent/RT.Comb这个仓库也介绍了这一部分内容。
(1)MySql
笔者的 MySql 版本为 8.0.26.
MySql 对 Guid 的处理为字符串方式,排序方式为从左到右的。
故而决定顺序的时间戳部分应该位于 Guid 的左侧,所以 ABP 的源码里 Guid 的16位数组:前6位为时间戳,后10位为随机数。
(2)Sql Server
笔者的 Sql Server 版本为 2019 Express.
Sql Server 关于 Guid 的排序方式比较特殊,属于分块排序。
先排 Data4
的后6个字节(即最后一块,也即从第10个字节开始的最后6个字节),块内依旧是从左到右排序。
接着排 Data4
的前2个字节(即倒数第2块,也即从第8个字节开始的2个字节),块内依旧是从左到右排序。
随后依次是 Data3
, Data2
, Data1
(其中,笔者验证了 Data3
的块内排序,并非从左到右,而是先排了块内第2个字节,后排第1个字节,可能是 Sql Server 认为 Data3
是 Int16
,而小端处理后将2个字节翻转了,显示虽然显示了 Mxxx
,但实际上是 xxMx
,排序也是按后者来排).
故而决定顺序的时间戳部分应该位于 Guid 的右侧,所以 ABP 的源码里 Guid 的16位数组:前10位为随机数,后6位为时间戳。
2.3 存在的问题
(1)毫秒级的时间戳
由于决定排序因素的部分为时间戳,而时间戳被处理成毫秒级。高并发的情况下,时间戳部分基本上一致,导致短时间内生成的 Guid 并不连续,是无序的。
// 获取 Ticks,并处理为毫秒级(1个Tick为100ns,1ms=1000us=1000000ns)
long timestamp = DateTime.UtcNow.Ticks / 10000L;
(2)非标准 Guid
这里还是大概介绍一下 RFC 4122 版本4的内容:
Guid 组成形如:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
其中 M 为 RFC 版本(version),版本4的话,值为4。
N 为变体(variant),值为 8, 9, A, B 其中一个。
版本4为保留版本号和变体,其他位均为随机。
显然,ABP 的方案,一部分是时间戳,余下的部分均为随机数,这样并不包含版本和变体,不属于任何一版本的 Guid,为非标准的 Guid。
3 连续 Guid 修改版本
3.1 解决高并发时间戳一致问题
(1)实现
基于上述的方案的问题1,由于问题是高并发的情况下时间戳一致的问题,那么尽量让时间戳的间隔再小一点,即可,如修改时间戳的代码为:
long timestamp = DateTime.UtcNow.Ticks;
直接将毫秒的处理去掉,让时间戳为纳秒级(ns)。
另外,还需要将时间戳原本只取6个字节,改成8个字节,让尾部的时间戳作用于 Guid 上。
完整的代码修改如下:
public static Guid Next(SequentialGuidType guidType)
{
// 原先 10 字节的随机序列数组,减少为 8 字节
var randomBytes = new byte[8];
_randomNumberGenerator.GetBytes(randomBytes);
// 时间戳保持纳秒级,不额外处理
long timestamp = DateTime.UtcNow.Ticks;
byte[] timestampBytes = BitConverter.GetBytes(timestamp);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
byte[] guidBytes = new byte[16];
switch (guidType)
{
case SequentialGuidType.SequentialAsString:
case SequentialGuidType.SequentialAsBinary:
// 16位数组:前8位为时间戳,后8位为随机数
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 0, 8);
Buffer.BlockCopy(randomBytes, 0, guidBytes, 8, 8);
// .NET中,Data1、Data2、Data3 块 分别视为 Int32、Int16、Int16
// 跟时间戳从 Int64 转 byte 数组后需要翻转一个理,在小端系统,需要翻转这3个块。
if (guidType == SequentialGuidType.AsString && BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 0, 4);
Array.Reverse(guidBytes, 4, 2);
Array.Reverse(guidBytes, 6, 2); // 翻转
}
break;
case SequentialGuidType.SequentialAtEnd:
// 16位数组:前8位为随机数,后8位为时间戳
Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 8);
// 方案1:正常拼接。这种方式只能连续1年+
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 8, 8);
// 方案2:将时间戳末尾的2个字节,放到 Data4 的前2个字节
Buffer.BlockCopy(timestampBytes, 6, guidBytes, 8, 2);
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 10, 6);
break;
}
return new Guid(guidBytes);
}
(2)测试
AsString 方式:
# 主要影响排序的,体现在 Guid 第8个字节。
08da7241-170b-c8ef-a094-06224a651c6a 0
08da7241-170b-d141-6ffc-5cdcecec5db9 1
08da7241-170b-d14e-d49e-81ce5efa6143 2
08da7241-170b-d150-8f59-836eab8d1939 3
08da7241-170b-d152-ac41-0c357a8aa4a1 4
08da7241-170b-d163-90a4-6083d462eeaf 5
08da7241-170b-d175-25b2-1d47ddd25939 6
08da7241-170b-d178-aa93-dc86e6391438 7
08da7241-170b-d185-619f-c24faf992806 8
08da7241-170b-d188-bd51-e36029ad9816 9
AtEnd 方式:
// 顺序体现在最后一个字节
983C1A57-8C2B-DE7D-08DA-724214AED77D 0
4F1389B8-59F6-7C78-08DA-724214AEDAB6 1
CF6D52B1-3BFA-272F-08DA-724214AEDABC 2
017C4F99-4499-67DB-08DA-724214AEDABE 3
4B0A0685-4355-2060-08DA-724214AEDAC0 4
D690E344-DDB4-16CB-08DA-724214AEDAC6 5
6E22CDBE-65FE-64DC-08DA-724214AEDAC8 6
72E67EB4-CA92-DF3A-08DA-724214AEDACA 7
AA93D914-5415-21C9-08DA-724214AEDACB 8
9D93FA3F-84B6-519D-08DA-724214AEDACD 9
3.2 产生符合 RFC 4122 标准的 Guid
笔者对于这一块内容,也是一脸懵逼。
大概的思路是:在 ABP 连续 Guid 的方案中,插入版本(M)和变体(N),那么牺牲1个字节(byte)共8个位(bit)的随机数即可,影响到时间戳的部分,则往后挪一挪。
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
修改后的代码比较复杂,如下:
public static Guid Next(SequentialGuidType guidType)
{
// see: What is a GUID? http://guid.one/guid
// see: https://github.com/richardtallent/RT.Comb#gory-details-about-uuids-and-guids
// According to RFC 4122:
// dddddddd-dddd-Mddd-Ndrr-rrrrrrrrrrrr
// - M = RFC 版本(version), 版本4的话,值为4
// - N = RFC 变体(variant),值为 8, 9, A, B 其中一个,这里固定为8
// - d = 从公元1年1月1日0时至今的时钟周期数(DateTime.UtcNow.Ticks)
// - r = 随机数(random bytes)
var randomBytes = new byte[8];
_randomNumberGenerator.GetBytes(randomBytes);
byte version = (byte)4;
byte variant = (byte)8;
byte filterHighBit = 0b00001111;
byte filterLowBit = 0b11110000;
long timestamp = DateTime.UtcNow.Ticks;
byte[] timestampBytes = BitConverter.GetBytes(timestamp);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
byte[] guidBytes = new byte[16];
switch (guidType)
{
case SequentialGuidType.SequentialAsString:
case SequentialGuidType.SequentialAsBinary:
// AsString: dddddddd-dddd-Mddd-Ndrr-rrrrrrrrrrrr
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 0, 6); // 时间戳前6个字节,共48位
// guidBytes[6]:高4位为版本 | 低4位取时间戳序号[6]的元素的高4位
guidBytes[6] = (byte)((version << 4) | ((timestampBytes[6] & filterLowBit) >> 4));
// guidBytes[7]:高4位取:[6]低4位 | 低4位取:[7]高4位
guidBytes[7] = (byte)(((timestampBytes[6] & filterHighBit) << 4) | ((timestampBytes[7] & filterLowBit) >> 4));
// guidBytes[8]:高4位为:变体 | 低4位取:[7]低4位
guidBytes[8] = (byte)((variant << 4) | (timestampBytes[7] & filterHighBit));
Buffer.BlockCopy(randomBytes, 0, guidBytes, 9, 7); // 余下7个字节由随机数组填充
// .NET中,Data1、Data2、Data3 块 分别视为 Int32、Int16、Int16,在小端系统,需要翻转这3个块。
if (guidType == SequentialGuidType.AsString && BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 0, 4);
Array.Reverse(guidBytes, 4, 2);
Array.Reverse(guidBytes, 6, 2);
}
break;
case SequentialGuidType.SequentialAtEnd:
// AtEnd: rrrrrrrr-rrrr-Mxdr-Nddd-dddddddddddd
// Block: 1 2 3 4 5
// Data4 = Block4 + Block5
// 排序顺序:Block5 > Block4 > Block3 > Block2 > Block1
// Data3 = Block3 被认为是 uint16,排序并不是从左到右,为消除影响,x 位取固定值
Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 6);
// Mx 高4位为版本 | 低4位取:全0
guidBytes[6] = (byte)(version << 4);
// dr 高4位为:时间戳[7]低4位 | 低4位取:随机数
guidBytes[7] = (byte)(((timestampBytes[7] & filterHighBit) << 4) | (randomBytes[7] & filterHighBit));
// Nd 高4位为:变体 | 低4位取:时间戳[6]高4位
guidBytes[8] = (byte)((variant << 4) | ((timestampBytes[6] & filterLowBit) >> 4));
// dd 高4位为:时间戳[6]低4位 | 低4位取:时间戳[7]高4位
guidBytes[9] = (byte)(((timestampBytes[6] & filterHighBit) << 4) | ((timestampBytes[7] & filterLowBit) >> 4));
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 10, 6); // 时间戳前6个字节
if (BitConverter.IsLittleEndian)
{
//Array.Reverse(guidBytes, 0, 4); // 随机数就不翻转了
//Array.Reverse(guidBytes, 4, 2);
Array.Reverse(guidBytes, 6, 2); // 包含版本号的 Data3 块需要翻转
}
break;
}
return new Guid(guidBytes);
}
4 Sql Server 关于 Guid 的处理方案
基于 Sql Server 特殊的 Guid 排序方式,这里提出一种解决方案:
不使用 Sql Server 默认的 [uniqueidentifier]
而改用 char(36)
,这样能让 Sql Server 的 Guid 处理成字符串,令其排序方式与字符串一致(与 MySql 和 C# 程序中的排序统一)。
具体处理可以在自定义的 DbContext 的 OnModelCreating 中配置:
// 获取所有注册的实体,遍历
foreach (var entityType in builder.Model.GetEntityTypes())
{
// 获取实体的所有属性,遍历
PropertyInfo[] propertyInfos = entityType.ClrType.GetProperties();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
string propertyName = propertyInfo.Name;
if (propertyInfo.PropertyType.FullName == "System.Guid")
{
// 将 Guid 类型设置为 char(36)
builder.Entity(entityType.ClrType).Property(propertyName).HasColumnType("char(36)");
}
}
}
5 完整的代码
这里将完整的 GuidHelper 给出:
通过 GuidHelper.Next()
生成连续的 Guid.
using System.Security.Cryptography;
public enum SequentialGuidType
{
/// <summary>
/// 用于 MySql 和 PostgreSql.
/// 当使用 <see cref="Guid.ToString()" /> 方法进行格式化时连续.
/// </summary>
AsString,
/// <summary>
/// 用于 Oracle.
/// 当使用 <see cref="Guid.ToByteArray()" /> 方法进行格式化时连续.
/// </summary>
AsBinary,
/// <summary>
/// 用以 SqlServer.
/// 连续性体现于 GUID 的第4块(Data4).
/// </summary>
AtEnd
}
public static class GuidHelper
{
private const byte version = (byte)4;
private const byte variant = (byte)8;
private const byte filterHighBit = 0b00001111;
private const byte filterLowBit = 0b11110000;
private static readonly RandomNumberGenerator _randomNumberGenerator = RandomNumberGenerator.Create();
/// <summary>
/// 连续 Guid 类型,默认:AsString.
/// </summary>
public static SequentialGuidType SequentialGuidType { get; set; } = SequentialGuidType.AsString;
/// <summary>
/// 生成连续 Guid.
/// </summary>
/// <returns></returns>
public static Guid Next()
{
return Next(SequentialGuidType);
}
/// <summary>
/// 生成连续 Guid(生成的 Guid 并不符合 RFC 4122 标准).
/// 来源:Abp. from jhtodd/SequentialGuid https://github.com/jhtodd/SequentialGuid/blob/master/SequentialGuid/Classes/SequentialGuid.cs .
/// </summary>
/// <param name="guidType"></param>
/// <returns></returns>
public static Guid NextOld(SequentialGuidType guidType)
{
var randomBytes = new byte[8];
_randomNumberGenerator.GetBytes(randomBytes);
long timestamp = DateTime.UtcNow.Ticks;
byte[] timestampBytes = BitConverter.GetBytes(timestamp);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
byte[] guidBytes = new byte[16];
switch (guidType)
{
case SequentialGuidType.AsString:
case SequentialGuidType.AsBinary:
// 16位数组:前8位为时间戳,后8位为随机数
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 0, 8);
Buffer.BlockCopy(randomBytes, 0, guidBytes, 8, 8);
// .NET中,Data1、Data2、Data3 块 分别视为 Int32、Int16、Int16,在小端系统,需要翻转这3个块。
if (guidType == SequentialGuidType.AsString && BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 0, 4);
Array.Reverse(guidBytes, 4, 2);
Array.Reverse(guidBytes, 6, 2);
}
break;
case SequentialGuidType.AtEnd:
// 16位数组:前8位为随机数,后8位为时间戳
Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 8);
Buffer.BlockCopy(timestampBytes, 6, guidBytes, 8, 2);
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 10, 6);
break;
}
return new Guid(guidBytes);
}
/// <summary>
/// 生成连续 Guid.
/// </summary>
/// <param name="guidType"></param>
/// <returns></returns>
public static Guid Next(SequentialGuidType guidType)
{
// see: What is a GUID? http://guid.one/guid
// see: https://github.com/richardtallent/RT.Comb#gory-details-about-uuids-and-guids
// According to RFC 4122:
// dddddddd-dddd-Mddd-Ndrr-rrrrrrrrrrrr
// - M = RFC 版本(version), 版本4的话,值为4
// - N = RFC 变体(variant),值为 8, 9, A, B 其中一个,这里固定为8
// - d = 从公元1年1月1日0时至今的时钟周期数(DateTime.UtcNow.Ticks)
// - r = 随机数(random bytes)
var randomBytes = new byte[8];
_randomNumberGenerator.GetBytes(randomBytes);
long timestamp = DateTime.UtcNow.Ticks;
byte[] timestampBytes = BitConverter.GetBytes(timestamp);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(timestampBytes);
}
byte[] guidBytes = new byte[16];
switch (guidType)
{
case SequentialGuidType.AsString:
case SequentialGuidType.AsBinary:
// AsString: dddddddd-dddd-Mddd-Ndrr-rrrrrrrrrrrr
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 0, 6); // 时间戳前6个字节,共48位
guidBytes[6] = (byte)((version << 4) | ((timestampBytes[6] & filterLowBit) >> 4)); // 高4位为版本 | 低4位取时间戳序号[6]的元素的高4位
guidBytes[7] = (byte)(((timestampBytes[6] & filterHighBit) << 4) | ((timestampBytes[7] & filterLowBit) >> 4)); // 高4位取:[6]低4位 | 低4位取:[7]高4位
guidBytes[8] = (byte)((variant << 4) | (timestampBytes[7] & filterHighBit)); // 高4位为:变体 | 低4位取:[7]低4位
Buffer.BlockCopy(randomBytes, 0, guidBytes, 9, 7); // 余下7个字节由随机数组填充
// .NET中,Data1、Data2、Data3 块 分别视为 Int32、Int16、Int16,在小端系统,需要翻转这3个块。
if (guidType == SequentialGuidType.AsString && BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 0, 4);
Array.Reverse(guidBytes, 4, 2);
Array.Reverse(guidBytes, 6, 2);
}
break;
case SequentialGuidType.AtEnd:
// AtEnd: rrrrrrrr-rrrr-Mxdr-Nddd-dddddddddddd
// Block: 1 2 3 4 5
// Data4 = Block4 + Block5
// 排序顺序:Block5 > Block4 > Block3 > Block2 > Block1
// Data3 = Block3 被认为是 uint16,排序并不是从左到右,为消除影响,x 位取固定值
Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 6);
guidBytes[6] = (byte)(version << 4); // Mx 高4位为版本 | 低4位取:全0
guidBytes[7] = (byte)(((timestampBytes[7] & filterHighBit) << 4) | (randomBytes[7] & filterHighBit)); ; // dr 高4位为:时间戳[7]低4位 | 低4位取:随机数
guidBytes[8] = (byte)((variant << 4) | ((timestampBytes[6] & filterLowBit) >> 4)); // Nd 高4位为:变体 | 低4位取:时间戳[6]高4位
guidBytes[9] = (byte)(((timestampBytes[6] & filterHighBit) << 4) | ((timestampBytes[7] & filterLowBit) >> 4)); // dd 高4位为:时间戳[6]低4位 | 低4位取:时间戳[7]高4位
Buffer.BlockCopy(timestampBytes, 0, guidBytes, 10, 6); // 时间戳前6个字节
if (BitConverter.IsLittleEndian)
{
Array.Reverse(guidBytes, 6, 2); // 包含版本号的 Data3 块需要翻转
}
break;
}
return new Guid(guidBytes);
}
}
6 其他全局唯一算法推荐
6.1 雪花算法
可以参考:Adnc 项目的文章:如何动态分配雪花算法的WorkerId.
Adnc 这个项目是风口旁的猪的,一个轻量级的微服务/分布式开发框架。
参考来源
DateTime.Ticks(微软官方关于 Ticks 的介绍,1个 Ticks 是100ns)
Guid Generator is not sequential generating multiple call in one request(ABP 的 issue)
Is there a .NET equivalent to SQL Server's newsequentialid()(Stack Overflow 这个问题,有一个回答介绍了时间戳高低位在 Guid 中的排布)
Pomelo.EntityFrameworkCore.MySql 连续 Guid 的源码(Furion 源码看到的,这个方案我看不懂,大概理解了一下,实际上原理应该差不多,生成的 Guid 的连续的字符串。不过,这里生成的 Guid 是符合 Guid 的 RFC 4122 Version 4 标准的)
不同数据库 Guid 的排序规则(讲了 MSSQL 即 Sql Server,还有 PostgreSQL)
.NET生成多数据库有序Guid(这篇贴出的源码与 Abp 没有太大区别,参考文章很齐全,可以看一看,这里不一一列出)
ASP.NET Core 产生连续 Guid的更多相关文章
- 使用Visual Studio Code开发Asp.Net Core WebApi学习笔记(六)-- 依赖注入
本篇将介绍Asp.Net Core中一个非常重要的特性:依赖注入,并展示其简单用法. 第一部分.概念介绍 Dependency Injection:又称依赖注入,简称DI.在以前的开发方式中,层与层之 ...
- Asp.Net Core微服务再体验
ASP.Net Core的基本配置 .在VS中调试的时候有很多修改Web应用运行端口的方法.但是在开发.调试微服务应用的时候可能需要同时在不同端口上开启多个服务器的实例,因此下面主要看看如何通过命令行 ...
- Asp.Net Core微服务初体验
ASP.Net Core的基本配置 .在VS中调试的时候有很多修改Web应用运行端口的方法.但是在开发.调试微服务应用的时候可能需要同时在不同端口上开启多个服务器的实例,因此下面主要看看如何通过命令行 ...
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- ASP.NET Core的路由[5]:内联路由约束的检验
当某个请求能够被成功路由的前提是它满足某个Route对象设置的路由规则,具体来说,当前请求的URL不仅需要满足路由模板体现的路径模式,请求还需要满足Route对象的所有约束.路由系统采用IRouteC ...
- ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系
ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间 ...
- ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起
我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...
- ASP.NET Core 中文文档 第四章 MVC(4.5)测试控制器逻辑
原文: Testing Controller Logic 作者: Steve Smith 翻译: 姚阿勇(Dr.Yao) 校对: 高嵩(Jack) ASP.NET MVC 应用程序的控制器应当小巧并专 ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
随机推荐
- 一探 Vue 数据响应式原理
一探 Vue 数据响应式原理 本文写于 2020 年 8 月 5 日 相信在很多新人第一次使用 Vue 这种框架的时候,就会被其修改数据便自动更新视图的操作所震撼. Vue 的文档中也这么写道: Vu ...
- Web Api源码(路由注册)
这篇文章只是我学习Web API框架的输出,学习方法还是输出倒逼输入比较行得通,所以不管写的好不好,坚持下去,肯定有收获.篇幅比较长,仔细思考阅读下来大约需要几分钟. 做.NET开发有好几年时间了,从 ...
- 一、深入学习c++先要练好的内功
掌握进程虚拟地址空间区域的划分 课程讲的内容建立在x86 32位的Linux系统下. 任何的编程语言会产生两种东西:指令和数据.磁盘上的可执行文件在启动时都会加载到内存当中,但是不会加载到物理内存中, ...
- EFCore常规操作生成的SQL语句一览
前言 EFCore的性能先不说,便捷性绝对是.Net Core平台下的ORM中最好用的,主要血统还百分百纯正. EFCore说到底还是对数据库进行操作,无论你是写Lamda还是Linq最后总归都是要生 ...
- 好客租房46-react组件进阶目标
1能够使用props接收数据 2能够使用父子组件之间的通讯 3能够实现兄弟组件之间的通讯 4能够给组件添加props校验 5能够说出生命周期常用的钩子函数 6能够知道高阶组件的作用 组件通讯介绍 组件 ...
- Helloworld 驱动模块加载
介绍 本文引用<linux设备驱动开发>书中部分解释,记录开篇第一章helloworld程序 以下内容需要掌握如下基础信息linux模块概念.链接编译.c语言基础 内容 helloworl ...
- 蓝牙、WiFi、ZigBee三大无线通信技术协议模块哪一个是最好的?
曾经,在2015年极客公园创新大会上,小米首次在非官方平台发布了新款产品小米智能家庭套装.自此,Zigbee便常出现在大众视野中. 如今,小米在IoT物联网应用开发者平台上明确说明,不再推广Zigbe ...
- JS:typeof
想要弄明白某一个变量中保存的数据到底是什么数据类型,我们可以使用到typeof操作符. typeof操作符:检测变量的数据类型. 看例子! var a = "abc"; var b ...
- 所有人都说Python 简单易学,为何我觉得难?
来谈谈心 记得刚学Python的时候,几乎所有人都说Python 简单易学,而对于编程零基础,只掌握Word和Excel的人来说,感觉真的好难. 学习之前网上的教材看了,Python的书也看了,包括& ...
- Android Studio 的初次使用
记录我第一次使用Android Studio时遇到的问题以及一些简单的笔记. 我所使用的是Android Studio 2.2版本 遇到的问题 创建一个Hello World!项目无疑是相当简单的,我 ...