diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs index 5f57032272..8cb88d38f0 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Domain/Repositories/EntityFrameworkCore/EfCoreRepository.cs @@ -89,19 +89,14 @@ namespace Volo.Abp.Domain.Repositories.EntityFrameworkCore public override TEntity Update(TEntity entity) { - //TODO: This code is got from UserStore.UpdateAsync and revised Update method based on that, but we should be sure that it's valid - //Context.Attach(user); - //user.ConcurrencyStamp = Guid.NewGuid().ToString(); - //Context.Update(user); - - DbContext.Attach(entity); //TODO: What is different for DbSet.Attach(entity)? + DbContext.Attach(entity); if (entity is IHasConcurrencyStamp) { (entity as IHasConcurrencyStamp).ConcurrencyStamp = Guid.NewGuid().ToString(); //TODO: Use IGuidGenerator! } - return DbContext.Update(entity).Entity; //TODO: or DbSet.Update(entity) ? + return DbContext.Update(entity).Entity; } public override void Delete(TEntity entity) diff --git a/src/Volo.Abp/Volo/Abp/Domain/Entities/DefaultIdGenerator.cs b/src/Volo.Abp/Volo/Abp/Domain/Entities/DefaultIdGenerator.cs new file mode 100644 index 0000000000..bb075812c1 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Domain/Entities/DefaultIdGenerator.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.Guids; +using Volo.DependencyInjection; + +namespace Volo.Abp.Domain.Entities +{ + public class DefaultIdGenerator : IIdGenerator, ITransientDependency + { + private readonly IGuidGenerator _guidGenerator; + + public DefaultIdGenerator(IGuidGenerator guidGenerator) + { + _guidGenerator = guidGenerator; + } + + public string GenerateStringId() + { + return GenerateGuid().ToString("D"); + } + + public Guid GenerateGuid() + { + return _guidGenerator.Create(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Domain/Entities/IdGenerator.cs b/src/Volo.Abp/Volo/Abp/Domain/Entities/IdGenerator.cs new file mode 100644 index 0000000000..4c629874c0 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Domain/Entities/IdGenerator.cs @@ -0,0 +1,11 @@ +using System; + +namespace Volo.Abp.Domain.Entities +{ + public interface IIdGenerator + { + string GenerateStringId(); + + Guid GenerateGuid(); + } +} diff --git a/src/Volo.Abp/Volo/Abp/Guids/IGuidGenerator.cs b/src/Volo.Abp/Volo/Abp/Guids/IGuidGenerator.cs new file mode 100644 index 0000000000..fcddc00e10 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Guids/IGuidGenerator.cs @@ -0,0 +1,15 @@ +using System; + +namespace Volo.Abp.Guids +{ + /// + /// Used to generate Ids. + /// + public interface IGuidGenerator + { + /// + /// Creates a new . + /// + Guid Create(); + } +} diff --git a/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidGenerator.cs b/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidGenerator.cs new file mode 100644 index 0000000000..22db24cde2 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidGenerator.cs @@ -0,0 +1,103 @@ +using System; +using System.Security.Cryptography; +using Microsoft.Extensions.Options; +using Volo.Abp.Threading; + +namespace Volo.Abp.Guids +{ + /* This code is taken from jhtodd/SequentialGuid https://github.com/jhtodd/SequentialGuid/blob/master/SequentialGuid/Classes/SequentialGuid.cs */ + + /// + /// Implements by creating sequential Guids. + /// Use to configure. + /// + public class SequentialGuidGenerator : IGuidGenerator + { + public SequentialGuidGeneratorOptions Options { get; } + + private static readonly RandomNumberGenerator RandomNumberGenerator = RandomNumberGenerator.Create(); + + public SequentialGuidGenerator(IOptions options) + { + Options = options.Value; + } + + public Guid Create() + { + return Create(Options.DefaultSequentialGuidType); + } + + public Guid Create(SequentialGuidType guidType) + { + // We start with 16 bytes of cryptographically strong random data. + var randomBytes = new byte[10]; + RandomNumberGenerator.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); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidGeneratorOptions.cs b/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidGeneratorOptions.cs new file mode 100644 index 0000000000..913a7a059b --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidGeneratorOptions.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.Guids +{ + public class SequentialGuidGeneratorOptions + { + /// + /// Default value: . + /// + public SequentialGuidType DefaultSequentialGuidType { get; set; } + + public SequentialGuidGeneratorOptions() + { + DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidType.cs b/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidType.cs new file mode 100644 index 0000000000..bc83923359 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Guids/SequentialGuidType.cs @@ -0,0 +1,28 @@ +using System; + +namespace Volo.Abp.Guids +{ + /// + /// Describes the type of a sequential GUID value. + /// + public enum SequentialGuidType + { + /// + /// The GUID should be sequential when formatted using the method. + /// Used by MySql and PostgreSql. + /// + SequentialAsString, + + /// + /// The GUID should be sequential when formatted using the method. + /// Used by Oracle. + /// + SequentialAsBinary, + + /// + /// The sequential portion of the GUID should be located at the end of the Data4 block. + /// Used by SqlServer. + /// + SequentialAtEnd + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Guids/SimpleGuidGenerator.cs b/src/Volo.Abp/Volo/Abp/Guids/SimpleGuidGenerator.cs new file mode 100644 index 0000000000..265cd52d81 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Guids/SimpleGuidGenerator.cs @@ -0,0 +1,15 @@ +using System; + +namespace Volo.Abp.Guids +{ + /// + /// Implements by using . + /// + public class SimpleGuidGenerator : IGuidGenerator + { + public virtual Guid Create() + { + return Guid.NewGuid(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Threading/LockExtensions.cs b/src/Volo.Abp/Volo/Abp/Threading/LockExtensions.cs new file mode 100644 index 0000000000..463cca7cc5 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Threading/LockExtensions.cs @@ -0,0 +1,68 @@ +using System; + +namespace Volo.Abp.Threading +{ + /// + /// Extension methods to make locking easier. + /// + public static class LockExtensions + { + /// + /// Executes given by locking given object. + /// + /// Source object (to be locked) + /// Action (to be executed) + public static void Locking(this object source, Action action) + { + lock (source) + { + action(); + } + } + + /// + /// Executes given by locking given object. + /// + /// Type of the object (to be locked) + /// Source object (to be locked) + /// Action (to be executed) + public static void Locking(this T source, Action action) where T : class + { + lock (source) + { + action(source); + } + } + + /// + /// Executes given and returns it's value by locking given object. + /// + /// Return type + /// Source object (to be locked) + /// Function (to be executed) + /// Return value of the + public static TResult Locking(this object source, Func func) + { + lock (source) + { + return func(); + } + } + + /// + /// Executes given and returns it's value by locking given object. + /// + /// Type of the object (to be locked) + /// Return type + /// Source object (to be locked) + /// Function (to be executed) + /// Return value of the + public static TResult Locking(this T source, Func func) where T : class + { + lock (source) + { + return func(source); + } + } + } +}