.NET:可扩展的单据编号生成器 之 顺序号(防止重复)

背景

我在上篇文章“.NET:可扩展的单据编号生成器 + 简单的解释器”中介绍了一个简单的单据编号框架。有朋友留言问如何实现“顺序号,且不能重复”,本篇文章就针对这个问题用上篇介绍的框架进行实现。

思路

顺序号 = 上次顺序号 + 步长

根据上面的公式,问题可以化解为:如何获取上次顺序号?获取上次顺序号有两种方式:

    1. 扫描单据表,找出最新的一条记录。
    2. 引入种子表,种子表记录了最新的顺序号。

因为生成的顺序号不能重复,这里就有了并发的要求,为了最大限度的提高并发性,我选择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,有机会再写文章介绍了。

标签