背景
我在上篇文章“.NET:可擴展的單據編號生成器 + 簡單的解釋器”中介紹了一個簡單的單據編號框架。有朋友留言問如何實現“順序號,且不能重復”,本篇文章就針對這個問題用上篇介紹的框架進行實現。
思路
順序號 = 上次順序號 + 步長
根據上面的公式,問題可以化解為:如何獲取上次順序號?獲取上次順序號有兩種方式:
-
- 掃描單據表,找出最新的一條記錄。
- 引入種子表,種子表記錄了最新的順序號。
因為生成的順序號不能重復,這里就有了並發的要求,為了最大限度的提高並發性,我選擇2(引入種子表)。
並發處理可以選擇:悲觀鎖或樂觀鎖,這里為了簡單,我選擇悲觀鎖。
實現
代碼下載:http://yunpan.cn/Q5KMUTA3qGPct。
種子表設計
1 CREATE TABLE [dbo].[CodeSeeds] ( 2 [Id] UNIQUEIDENTIFIER NOT NULL, 3 [Key] NVARCHAR (500) NOT NULL, 4 [Value] INT NOT NULL, 5 PRIMARY KEY CLUSTERED ([Id] ASC) 6 );
SeedCodeRuleProvider.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using System.Transactions; 8 using System.Text.RegularExpressions; 9 using System.Data.Entity.Infrastructure; 10 11 namespace EntityCodeRuleDemo 12 { 13 public class SeedCodeRuleProvider : ICodeRuleProvider 14 { 15 private readonly int _width; 16 17 public SeedCodeRuleProvider(int width) 18 { 19 _width = width; 20 } 21 22 public string Generate(object entity) 23 { 24 return GetSeedValue(entity).ToString().PadLeft(_width, '0'); 25 } 26 27 protected virtual string GetKey(object entity) 28 { 29 return entity.GetType().FullName; 30 } 31 32 private int GetSeedValue(object entity) 33 { 34 try 35 { 36 var value = 0; 37 var key = this.GetKey(entity); 38 39 using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, 40 new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead })) 41 { 42 using (var context = new TestContext()) 43 { 44 var seed = context.CodeSeeds.Where(x => x.Key == key).FirstOrDefault(); 45 if (seed == null) 46 { 47 seed = new CodeSeed { Id = Guid.NewGuid(), Key = key, Value = -1 }; 48 context.CodeSeeds.Add(seed); 49 } 50 51 seed.Value++; 52 value = seed.Value; 53 context.SaveChanges(); 54 } 55 56 ts.Complete(); 57 } 58 59 return value; 60 } 61 catch (DbUpdateException) 62 { 63 return this.GetSeedValue(entity); 64 } 65 } 66 67 public static SeedCodeRuleProvider SeedCodeRuleProviderFactory(string literal) 68 { 69 var match = new Regex("^<種子(:(?<寬度>.*?))?>$").Match(literal); 70 71 var width = match.Groups["寬度"].Value; 72 73 return new SeedCodeRuleProvider(string.IsNullOrEmpty(width) ? 5 : int.Parse(width)); 74 } 75 } 76 }
Program.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using System.Text.RegularExpressions; 8 9 namespace EntityCodeRuleDemo 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 CodeRuleInterpreter.RegistProviderFactory(new Regex("^<種子(:(?<寬度>.*?))?>$"), SeedCodeRuleProvider.SeedCodeRuleProviderFactory); 16 17 var employeeCode = CodeRuleInterpreter 18 .Interpret("前綴_<日期:yyyy_MM_dd>_<屬性:NamePinYin>_<種子:6>") 19 .Generate(new Employee { NamePinYin = "DUANGW" }); 20 21 Console.WriteLine(employeeCode); 22 } 23 } 24 }
運行結果
備注
有寫業務要求會強制編號的連續性或編號的隨機性,對於這兩種需求,還需要單獨開發Provider,有機會再寫文章介紹了。