C# ABP项目中生成基于各种数据库的顺序GUID,有效减少索引碎片,提高查询效率

/// <summary>
/// Used to generate Ids.
/// </summary>
public interface IGuidGenerator
{
    /// <summary>
    /// Creates a GUID.
    /// </summary>
    Guid Create();
}
using System;
using System.Security.Cryptography;

/// <summary>
/// Implements <see cref="IGuidGenerator"/> by creating sequential Guids.
/// This code is taken from https://github.com/jhtodd/SequentialGuid/blob/master/SequentialGuid/Classes/SequentialGuid.cs
/// </summary>
public class SequentialGuid : IGuidGenerator
{
    /// <summary>
    /// Gets the singleton <see cref="SequentialGuid"/> instance.
    /// </summary>
    public static SequentialGuid Instance { get; } = new SequentialGuid();

    private static readonly RandomNumberGenerator Rng = RandomNumberGenerator.Create();

    public SequentialGuidDatabaseType DatabaseType { get; set; }

    /// <summary>
    /// Prevents a default instance of the <see cref="SequentialGuid"/> class from being created.
    /// Use <see cref="Instance"/>.
    /// </summary>
    private SequentialGuid()
    {
        DatabaseType = SequentialGuidDatabaseType.SqlServer;
    }

    public Guid Create()
    {
        return Create(DatabaseType);
    }

    public Guid Create(SequentialGuidDatabaseType databaseType)
    {
        switch (databaseType)
        {
            case SequentialGuidDatabaseType.SqlServer:
                return Create(SequentialGuidType.SequentialAtEnd);
            case SequentialGuidDatabaseType.Oracle:
                return Create(SequentialGuidType.SequentialAsBinary);
            case SequentialGuidDatabaseType.MySql:
                return Create(SequentialGuidType.SequentialAsString);
            case SequentialGuidDatabaseType.PostgreSql:
                return Create(SequentialGuidType.SequentialAsString);
            default:
                throw new InvalidOperationException();
        }
    }

    public Guid Create(SequentialGuidType guidType)
    {
        // We start with 16 bytes of cryptographically strong random data.
        var randomBytes = new byte[10];
        Rng.Locking(r => r.GetBytes(randomBytes));

        // An alternate method: use a normally-created GUID to get our initial
        // random data:
        // byte[] randomBytes = Guid.NewGuid().ToByteArray();
        // This is faster than using RNGCryptoServiceProvider, but I don't
        // recommend it because the .NET Framework makes no guarantee of the
        // randomness of GUID data, and future versions (or different
        // implementations like Mono) might use a different method.

        // Now we have the random basis for our GUID.  Next, we need to
        // create the six-byte block which will be our timestamp.

        // We start with the number of milliseconds that have elapsed since
        // DateTime.MinValue.  This will form the timestamp.  There's no use
        // being more specific than milliseconds, since DateTime.Now has
        // limited resolution.

        // Using millisecond resolution for our 48-bit timestamp gives us
        // about 5900 years before the timestamp overflows and cycles.
        // Hopefully this should be sufficient for most purposes. :)
        long timestamp = DateTime.UtcNow.Ticks / 10000L;

        // Then get the bytes
        byte[] timestampBytes = BitConverter.GetBytes(timestamp);

        // Since we're converting from an Int64, we have to reverse on
        // little-endian systems.
        if (BitConverter.IsLittleEndian)
        {
            Array.Reverse(timestampBytes);
        }

        byte[] guidBytes = new byte[16];

        switch (guidType)
        {
            case SequentialGuidType.SequentialAsString:
            case SequentialGuidType.SequentialAsBinary:

                // For string and byte-array version, we copy the timestamp first, followed
                // by the random data.
                Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
                Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);

                // If formatting as a string, we have to compensate for the fact
                // that .NET regards the Data1 and Data2 block as an Int32 and an Int16,
                // respectively.  That means that it switches the order on little-endian
                // systems.  So again, we have to reverse.
                if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
                {
                    Array.Reverse(guidBytes, 0, 4);
                    Array.Reverse(guidBytes, 4, 2);
                }

                break;

            case SequentialGuidType.SequentialAtEnd:

                // For sequential-at-the-end versions, we copy the random data first,
                // followed by the timestamp.
                Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
                Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
                break;
        }

        return new Guid(guidBytes);
    }

    /// <summary>
    /// Database type to generate GUIDs.
    /// </summary>
    public enum SequentialGuidDatabaseType
    {
        SqlServer,

        Oracle,

        MySql,

        PostgreSql,
    }

    /// <summary>
    /// Describes the type of a sequential GUID value.
    /// </summary>
    public enum SequentialGuidType
    {
        /// <summary>
        /// The GUID should be sequential when formatted using the
        /// <see cref="Guid.ToString()" /> method.
        /// </summary>
        SequentialAsString,

        /// <summary>
        /// The GUID should be sequential when formatted using the
        /// <see cref="Guid.ToByteArray" /> method.
        /// </summary>
        SequentialAsBinary,

        /// <summary>
        /// The sequential portion of the GUID should be located at the end
        /// of the Data4 block.
        /// </summary>
        SequentialAtEnd
    }
}
using System;

/// <summary>
/// Extension methods to make locking easier.
/// </summary>
public static class LockExtensions
{
    /// <summary>
    /// Executes given <paramref name="action"/> by locking given <paramref name="source"/> object.
    /// </summary>
    /// <param name="source">Source object (to be locked)</param>
    /// <param name="action">Action (to be executed)</param>
    public static void Locking(this object source, Action action)
    {
        lock (source)
        {
            action();
        }
    }

    /// <summary>
    /// Executes given <paramref name="action"/> by locking given <paramref name="source"/> object.
    /// </summary>
    /// <typeparam name="T">Type of the object (to be locked)</typeparam>
    /// <param name="source">Source object (to be locked)</param>
    /// <param name="action">Action (to be executed)</param>
    public static void Locking<T>(this T source, Action<T> action) where T : class
    {
        lock (source)
        {
            action(source);
        }
    }

    /// <summary>
    /// Executes given <paramref name="func"/> and returns it's value by locking given <paramref name="source"/> object.
    /// </summary>
    /// <typeparam name="TResult">Return type</typeparam>
    /// <param name="source">Source object (to be locked)</param>
    /// <param name="func">Function (to be executed)</param>
    /// <returns>Return value of the <paramref name="func"/></returns>
    public static TResult Locking<TResult>(this object source, Func<TResult> func)
    {
        lock (source)
        {
            return func();
        }
    }

    /// <summary>
    /// Executes given <paramref name="func"/> and returns it's value by locking given <paramref name="source"/> object.
    /// </summary>
    /// <typeparam name="T">Type of the object (to be locked)</typeparam>
    /// <typeparam name="TResult">Return type</typeparam>
    /// <param name="source">Source object (to be locked)</param>
    /// <param name="func">Function (to be executed)</param>
    /// <returns>Return value of the <paramnref name="func"/></returns>
    public static TResult Locking<T, TResult>(this T source, Func<T, TResult> func) where T : class
    {
        lock (source)
        {
            return func(source);
        }
    }
}

使用方法:

for (int i = 0; i < 100; i++)
{    
Console.WriteLine(SequentialGuid.Instance.Create(SequentialGuid.SequentialGuidDatabaseType.SqlServer).ToString()
+ " " + i + " " + DateTime.Now.ToFileTime()); Thread.Sleep(1); }

注意:此处生成的基于sqlserver的guid在控制台里面查看并不是顺序的,但是在数据库中是可以进行顺序排序的,因为sqlserver对guid的排序是有特定规则的,具体可以参考下面的文章

https://www.cnblogs.com/tdfblog/p/SequentialGuid.html

  1. .Net中GUID的排序规则是从左到右依次进行排序,与数字排序规则一致;
  2. Sql Server数据库提供对GUID类型的支持,在数据库中称为UniqueIdentifier类型,但是排序规则比较复杂:
    • 先按每1-8从左到右进行排序;
    • 接着按第9-10位从右到左进行排序;
    • 最后按后11-16位从右到左进行排序;
  3. Oracle数据库未提供对GUID类型的支持,使用的是raw bytes类型保存数据raw(16),具体类型为,排序规则与GUID在.Net中规则一致;
  4. MySql数据未提供对GUID类型的支持,使用的是字符串的类型保存数据,使用是的char(36)类型,由于使用的是字符串类型,排序规则与GUID在.Net中的规则一致。
posted @ 2018-06-22 22:03  事理  阅读(2858)  评论(0编辑  收藏  举报