如何使用Linq to Sql作为序列号生成器来避免Gaps?

时间:2013-04-18 03:12:51

标签: linq-to-sql

创建了以下Linq to SQL事务,尝试创建无间隙的发票编号。 假设2个表:

表1:InvoiceNumbers。 - 列ID,序列号,增量 - 示例:1,10001,1

表2:发票。 - 列:ID,InvoiceNumber,名称 - 示例:1,10001,“Bob Smith”

        Dim db As New Invoices.InvoicesDataContext
        Dim lastInvoiceNumber = (From n In db.InvoiceNumbers Order By n.LastSerialNumber Descending
                          Select n.LastSerialNumber, n.Increment).First
        Dim nextInvoiceNumber As Integer = lastInvoiceNumber.LastSerialNumber + lastInvoiceNumber.Increment
        Dim newInvoiceNumber = New Invoices.InvoiceNumber With {.LastSerialNumber = nextInvoiceNumber, .Increment = lastInvoiceNumber.Increment}
        Dim newInvoice = New Invoices.Invoice With {.InvoiceNumber = nextInvoiceNumber, .Name = "Test" + nextInvoiceNumber.ToString}
        db.InvoiceNumbers.InsertOnSubmit(newInvoiceNumber)
        db.Invoices.InsertOnSubmit(newInvoice)
    db.SubmitChanges()

一切正常但可以使用这种方法,如果2个用户同时点击交易时可能会收到相同的发票号码吗? 如果是这样,是否有更好的方法使用Linq到Sql?

2 个答案:

答案 0 :(得分:0)

在处理交易数据库时,序列中的差距不可避免

首先,您不能使用SELECT max(id)+1,因为它可能会为同时执行的2个事务提供相同的id。这意味着您必须使用数据库本机自动增量列(MySQL,SQLite)或数据库序列(PostgreSQL,MSSQL,Oracle)来获取下一个可用的id

但即使使用自动增量序列也无法解决此问题。 想象一下,您有2个数据库连接,几乎同时启动了2个并行事务。第一个从自动增量序列中获得了一些id,它变成了之前使用的值+1。一纳秒之后,第二次交易获得了下一个id,现在是+2。现在假设第一个事务由于某种原因回滚(遇到错误,你的代码决定中止它,程序崩溃 - 你说出来)。之后,使用id +2提交第二笔交易,在id编号中创建了一个空白。

但是,如果此类并行交易的数量超过2,该怎么办?您无法预测,也无法告诉当前正在运行的事务重用已被放弃的id

理论上可以重复使用废弃的ID。但是,在实践中,它在数据库上非常昂贵,并且当多个会话尝试执行相同的操作时会产生更多问题。

TDLR:停止对抗它,使用过的ID中的空隙完全正常。

答案 1 :(得分:0)

您始终可以使用lock()确保事务未在多个线程中同时运行:

private static object myLockObject = new object();
....
....
public class MyClass
{
...
public void TransactionAndStuff()
{
    lock(myLockObject) 
    {
        // your linq query
    }
}