Untitled
unknown
python
a year ago
16 kB
3
Indexable
Never
@echo off SET PATH=C:\Windows\System32;%PATH% pushd "%~dp0" CD app IF "%PROCESSOR_ARCHITECTURE%"=="AMD64" ( ..\node\x64\node.exe install.js "%LocalAPPData%" ) ELSE ( ..\node\x86\node.exe install.js "%LocalAPPData%" ) PAUSE using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Text; using Microsoft.SqlServer.Server; /// <summary> /// A tool for generating unique random codes. /// </summary> /// <example> /// <code> /// using (var connection = new SqlConnection(ConnectionString)) /// { /// connection.Open(); /// var codeLength = GetCurrentCodeLength(connection); /// using (var generator = new CodeGenerator(connection, codeLength)) /// { /// var codes = generator.GenerateCodes(10000); /// foreach(var code in codes) /// Console.WriteLine(code); /// if (generator.CodeLength > codeLength) /// { /// SaveNewCodeLength(generator.CodeLength); /// NotifyDeveloperOfApproachingCodePoolExhaustion( /// generator.CodeLength, /// CodeGenerator.MaxCodeLength); /// } /// } /// } /// </code> /// </example> public class CodeGenerator : IDisposable { public const int MaxCodeLength = 8; private const string AvailableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; private const string Query = @" DECLARE @batchid uniqueidentifier; SET @batchid = NEWID(); INSERT INTO dbo.Voucher (Code, BatchId) SELECT DISTINCT b.Code, @batchid FROM @batch b WHERE NOT EXISTS ( SELECT Code FROM dbo.Voucher v WHERE b.Code = v.Code ); SELECT Code FROM dbo.Voucher WHERE BatchId = @batchid;"; private static readonly SqlMetaData[] BatchMetaData = new[] { new SqlMetaData("Code", SqlDbType.NVarChar, MaxCodeLength) }; private readonly SqlConnection _connection; private readonly StringBuilder _builder; private readonly Random _random = new Random(Guid.NewGuid().GetHashCode()); private readonly int _batchSize; private readonly double _collisionThreshold; private readonly SqlCommand _command; private readonly SqlParameter _batchParameter; private bool _disposed; public int CodeLength { get; private set; } /// <summary> /// Create a CodeGenerator instance. /// </summary> /// <param name="connection"> /// The connection to the database. Must be open. Calling code is responsible for /// creating, opening, and disposing the connection. /// </param> /// <param name="codeLength"> /// The initial code length, which will grow as needed as codes are used up. However, you /// still need to persist the <c>CodeLength</c> property value and initialize this parameter /// correctly. Otherwise, if you always supply the same initial /// <paramref name="codeLength"/> - say "4", then ALL of the 4-digit codes will eventually /// become used up instead of maintaining the sparseness dictated by the collision /// threshold. /// </param> public CodeGenerator(SqlConnection connection, int codeLength) : this(connection, codeLength, 500, 0.01) { } /// <summary> /// Create a CodeGenerator instance. /// </summary> /// <param name="connection"> /// The connection to the database. Must be open. Calling code is responsible for /// creating, opening, and disposing the connection. /// </param> /// <param name="codeLength"> /// The initial code length, which will grow as needed as codes are used up. However, you /// still need to persist the <c>CodeLength</c> property value and initialize this parameter /// correctly. Otherwise, if you always supply the same initial /// <paramref name="codeLength"/> - say "4", then ALL of the 4-digit codes will eventually /// become used up instead of maintaining the sparseness dictated by the /// <paramref name="collisionThreshold"/> parameter. /// </param> /// <param name="batchSize"> /// The number of codes to generate, test, and insert at once. Tune this value for best /// performance. In my tests, 500 worked well. /// </param> /// <param name="collisionThreshold"> /// A value between 0 (inclusive) and 1 (exclusive). Supply a small value (perhaps 0.01) to /// keep codes sparse. A value that is too high (above 0.5) will result in sub-optimum /// performance. /// </param> public CodeGenerator(SqlConnection connection, int codeLength, int batchSize, double collisionThreshold) { if (collisionThreshold >= 1.0) throw new ArgumentOutOfRangeException("collisionThreshold", collisionThreshold, "must be less than 1"); _connection = connection; CodeLength = codeLength; _batchSize = batchSize; _collisionThreshold = collisionThreshold; _builder = new StringBuilder(codeLength + 1); _command = _connection.CreateCommand(); _command.CommandText = Query; _batchParameter = _command.Parameters.Add("@batch", SqlDbType.Structured); _batchParameter.TypeName = "dbo.VoucherCodeList"; } public void Dispose() { if (_disposed) return; _command.Dispose(); _disposed = true; } /// <summary> /// Generates unique random codes and inserts them into the database. /// </summary> /// <param name="numberOfCodes">The number of codes you need.</param> /// <returns>A list of unique random codes.</returns> public ICollection<string> GenerateCodes(int numberOfCodes) { var result = new List<string>(numberOfCodes); while (result.Count < numberOfCodes) { var batchSize = Math.Min(_batchSize, numberOfCodes - result.Count); var batch = GetBatch(batchSize); var oldResultCount = result.Count; result.AddRange(FilterAndSecureBatch(batch)); var filteredBatchSize = result.Count - oldResultCount; var collisionRatio = ((double)batchSize - filteredBatchSize) / batchSize; if (collisionRatio > _collisionThreshold) CodeLength++; } return result; } private IEnumerable<string> GetBatch(int batchSize) { for (var i = 0; i < batchSize; i++) yield return GenerateRandomCode(); } private string GenerateRandomCode() { _builder.Clear(); for (var i = 0; i < CodeLength; i++) _builder.Append(AvailableChars[_random.Next(AvailableChars.Length)]); return _builder.ToString(); } private IEnumerable<string> FilterAndSecureBatch(IEnumerable<string> batch) { _batchParameter.Value = batch.Select(x => { var record = new SqlDataRecord(BatchMetaData); record.SetString(0, x); return record; }); using (var reader = _command.ExecuteReader()) while (reader.Read()) yield return reader.GetString(0); } /// <summary> /// Creates the database schema required by the CodeCenerator. /// </summary> /// <param name="connection">An open connection to the database.</param> public static void CreateSchema(SqlConnection connection) { using (var command = connection.CreateCommand()) { command.CommandText = @" CREATE TABLE dbo.Voucher ( Code nvarchar(" + MaxCodeLength + @") COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL PRIMARY KEY, BatchId uniqueidentifier NOT NULL ); CREATE NONCLUSTERED INDEX IX_Voucher ON dbo.Voucher (BatchId ASC); CREATE TYPE dbo.VoucherCodeList AS TABLE ( Code nvarchar(" + MaxCodeLength + @") COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL );"; command.ExecuteNonQuery(); } } } } /// /// /// using (var connection = new SqlConnection(ConnectionString)) /// { /// connection.Open(); /// var codeLength = GetCurrentCodeLength(connection); /// using (var generator = new CodeGenerator(connection, codeLength)) /// { /// var codes = generator.GenerateCodes(10000); /// foreach(var code in codes) /// Console.WriteLine(code); /// if (generator.CodeLength > codeLength) /// { /// SaveNewCodeLength(generator.CodeLength); /// NotifyDeveloperOfApproachingCodePoolExhaustion( /// generator.CodeLength, /// CodeGenerator.MaxCodeLength); /// } /// } /// } /// /// public class CodeGenerator : IDisposable { public const int MaxCodeLength = 8; private const string AvailableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; private const string Query = @" DECLARE @batchid uniqueidentifier; SET @batchid = NEWID(); INSERT INTO dbo.Voucher (Code, BatchId) SELECT DISTINCT b.Code, @batchid FROM @batch b WHERE NOT EXISTS ( SELECT Code FROM dbo.Voucher v WHERE b.Code = v.Code ); SELECT Code FROM dbo.Voucher WHERE BatchId = @batchid;"; private static readonly SqlMetaData[] BatchMetaData = new[] { new SqlMetaData("Code", SqlDbType.NVarChar, MaxCodeLength) }; private readonly SqlConnection _connection; private readonly StringBuilder _builder; private readonly Random _random = new Random(Guid.NewGuid().GetHashCode()); private readonly int _batchSize; private readonly double _collisionThreshold; private readonly SqlCommand _command; private readonly SqlParameter _batchParameter; private bool _disposed; public int CodeLength { get; private set; } /// <summary> /// Create a CodeGenerator instance. /// </summary> /// <param name="connection"> /// The connection to the database. Must be open. Calling code is responsible for /// creating, opening, and disposing the connection. /// </param> /// <param name="codeLength"> /// The initial code length, which will grow as needed as codes are used up. However, you /// still need to persist the <c>CodeLength</c> property value and initialize this parameter /// correctly. Otherwise, if you always supply the same initial /// <paramref name="codeLength"/> - say "4", then ALL of the 4-digit codes will eventually /// become used up instead of maintaining the sparseness dictated by the collision /// threshold. /// </param> public CodeGenerator(SqlConnection connection, int codeLength) : this(connection, codeLength, 500, 0.01) { } /// <summary> /// Create a CodeGenerator instance. /// </summary> /// <param name="connection"> /// The connection to the database. Must be open. Calling code is responsible for /// creating, opening, and disposing the connection. /// </param> /// <param name="codeLength"> /// The initial code length, which will grow as needed as codes are used up. However, you /// still need to persist the <c>CodeLength</c> property value and initialize this parameter /// correctly. Otherwise, if you always supply the same initial /// <paramref name="codeLength"/> - say "4", then ALL of the 4-digit codes will eventually /// become used up instead of maintaining the sparseness dictated by the /// <paramref name="collisionThreshold"/> parameter. /// </param> /// <param name="batchSize"> /// The number of codes to generate, test, and insert at once. Tune this value for best /// performance. In my tests, 500 worked well. /// </param> /// <param name="collisionThreshold"> /// A value between 0 (inclusive) and 1 (exclusive). Supply a small value (perhaps 0.01) to /// keep codes sparse. A value that is too high (above 0.5) will result in sub-optimum /// performance. /// </param> public CodeGenerator(SqlConnection connection, int codeLength, int batchSize, double collisionThreshold) { if (collisionThreshold >= 1.0) throw new ArgumentOutOfRangeException("collisionThreshold", collisionThreshold, "must be less than 1"); _connection = connection; CodeLength = codeLength; _batchSize = batchSize; _collisionThreshold = collisionThreshold; _builder = new StringBuilder(codeLength + 1); _command = _connection.CreateCommand(); _command.CommandText = Query; _batchParameter = _command.Parameters.Add("@batch", SqlDbType.Structured); _batchParameter.TypeName = "dbo.VoucherCodeList"; } public void Dispose() { if (_disposed) return; _command.Dispose(); _disposed = true; } /// <summary> /// Generates unique random codes and inserts them into the database. /// </summary> /// <param name="numberOfCodes">The number of codes you need.</param> /// <returns>A list of unique random codes.</returns> public ICollection<string> GenerateCodes(int numberOfCodes) { var result = new List<string>(numberOfCodes); while (result.Count < numberOfCodes) { var batchSize = Math.Min(_batchSize, numberOfCodes - result.Count); var batch = GetBatch(batchSize); var oldResultCount = result.Count; result.AddRange(FilterAndSecureBatch(batch)); var filteredBatchSize = result.Count - oldResultCount; var collisionRatio = ((double)batchSize - filteredBatchSize) / batchSize; if (collisionRatio > _collisionThreshold) CodeLength++; } return result; } private IEnumerable<string> GetBatch(int batchSize) { for (var i = 0; i < batchSize; i++) yield return GenerateRandomCode(); } private string GenerateRandomCode() { _builder.Clear(); for (var i = 0; i < CodeLength; i++) _builder.Append(AvailableChars[_random.Next(AvailableChars.Length)]); return _builder.ToString(); } private IEnumerable<string> FilterAndSecureBatch(IEnumerable<string> batch) { _batchParameter.Value = batch.Select(x => { var record = new SqlDataRecord(BatchMetaData); record.SetString(0, x); return record; }); using (var reader = _command.ExecuteReader()) while (reader.Read()) yield return reader.GetString(0); } /// <summary> /// Creates the database schema required by the CodeCenerator. /// </summary> /// <param name="connection">An open connection to the database.</param> public static void CreateSchema(SqlConnection connection) { using (var command = connection.CreateCommand()) { command.CommandText = @" CREATE TABLE dbo.Voucher ( Code nvarchar(" + MaxCodeLength + @") COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL PRIMARY KEY, BatchId uniqueidentifier NOT NULL ); CREATE NONCLUSTERED INDEX IX_Voucher ON dbo.Voucher (BatchId ASC); CREATE TYPE dbo.VoucherCodeList AS TABLE ( Code nvarchar(" + MaxCodeLength + @") COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL );"; command.ExecuteNonQuery(); } } }