.NET:可扩展的单据编号生成器 之 顺序号(防止重复)
背景
我在上篇文章“.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,有机会再写文章介绍了。