实体框架自动增量密钥

时间:2014-08-25 15:30:48

标签: c# sql entity-framework concurrency

我在并发方案中遇到了重复增量字段的问题。

我正在使用EF作为ORM工具,尝试插入一个具有充当增量INT字段的字段的实体。基本上这个字段称为“SequenceNumber”,其中插入前的每个新记录将使用MAX读取数据库以获取最后一个SequenceNumber,向其追加+1,并保存更改。

在获取最后SequenceNumber和保存之间,这就是并发发生的地方。

我没有将ID用于SequenceNumber,因为它不是唯一约束,并且可能会在某些条件下重置,例如每月,每年等。

InvoiceNumber       | SequenceNumber | DateCreated
INV00001_08_14      | 1              | 25/08/2014
INV00001_08_14      | 1              | 25/08/2014 <= (concurrency is creating two SeqNo 1)
INV00002_08_14      | 2              | 25/08/2014
INV00003_08_14      | 3              | 26/08/2014
INV00004_08_14      | 4              | 27/08/2014
INV00005_08_14      | 5              | 29/08/2014
INV00001_09_14      | 1              | 01/09/2014 <= (sequence number reset)

发票编号的格式基于SequenceNumber

经过一些研究后,我最终得到了这些可能的解决方案,但想知道最佳实践

  1. 悲观并发,将表从任何读取锁定,直到当前事务完成(这个想法并不花哨,因为我认为性能会产生很大的影响?)

  2. 仅为此目的创建一个存储过程,选择并插入单个语句,因为并发性最小(如果可能,更喜欢基于EF的方法)

  3. ==============================

    修改

    我想到的另一个解决方案是将 InvoiceNumber 设为唯一约束,因此对于重复条目的情况,它会抛出Unique Constraint Violation错误,catch它,并重新尝试获取新的SequenceNumber和InvoiceNumber以重新插入数据库。

3 个答案:

答案 0 :(得分:4)

使用带有身份密钥的假表/实体。

在假表中插入发票插入以获取ID之前。 使用ID,不是作为FK而是作为SequenceNumber

重置:截断假表。

可预测的问题:序列中可能有漏洞,具体取决于插入错误。

从sql server 2012开始就要注意CREATE SEQUENCE。顺便说一下,如果你认为这个想法很好,那么upvote on datavoice

=====

sql序列可以通过约束用作默认值:

ALTER TABLE Invoice
ADD CONSTRAINT DefSequence DEFAULT (NEXT VALUE FOR InvoiceSeq) 
    FOR SequenceNumber;
GO

通过将SequenceNumber设置为DatabaseGeneratedOption.Computed,它可能是完美的。

答案 1 :(得分:1)

乐观并发意味着没有锁定。在操作结束时检查对DB的更改。如果不是其他人修改了影响操作的东西,那就意味着它没问题。如果其他人修改了影响操作的内容,则会抛出异常。 (这个名字来自这样一个事实:你很乐观,希望在你使用它的时候没有别的东西可以修改数据)。

存储过程是一个很好的解决方案。为什么不使用它? - 请记住,您必须将过程代码包含在事务中。如果没有,你就不会受到并发问题的保护。

You can modify the data inside a transaction directly from EF

触发器也可以。但是,我不建议使用它:如果您不知道它存在,则很难检测到它存在。

在任何情况下,如果您只执行读取行的非常简单的操作并写入其他操作,则事务锁定不应表示性能问题。在存在之前不要尝试解决性能问题。

修改 性能问题是由长时间运行的事务创建的,即需要很长时间才能执行的事务,例如因为它们读取或写入大量数据(例如,更新包含具有许多相关行的子表的聚合的字段,或者在没有可用索引的数百万行的巨大表中查找数据。如果将序列保存在单行的单行中,读取它的速度非常快,并且希望在另一个表中写一行,也非常非常快,特别是如果聚簇索引(默认情况下是主键)聚集索引)允许在表的末尾写入行,就像在拥挤的表中一样。为确保数据在表的末尾处,每个新行应该是聚集索引中的最后一行。

答案 2 :(得分:1)

对于任何可能面临同样挑战的人,我最终确定的是使InvoiceNumber成为约束,并处理UniqueConstraintException并重新尝试获取InvoiceNumber。

为了避免无限循环(如果它曾经发生过),添加了一个“重试”计数器。

        int retry = 5;
        try
        {
            // Get new purchase order number from service

            Invoice.InvoiceNumber = GetNewPurchaseOrderNumber();

            // Explicit set state as Added
            Invoice.ObjectState = ObjectState.Added;

            _uow.Repository<Invoice>().InsertGraph(Invoice);
            _uow.Save();
        }
        catch (DbUpdateException ex)
        {
            retry--;
            if (null == ex.InnerException) throw;

            var innerException = ex.InnerException.InnerException
                                   as System.Data.SqlClient.SqlException;
            if (innerException != null &&
                   (
                       innerException.Number == 2627 ||
                       innerException.Number == 2601)
                       && retry > 0
                   )
            {
                // Get new Invoice Number from service
                Invoice.InvoiceNumber = GetNewPurchaseOrderNumber();

                _uow.Save();
            }
            else
            {
                throw;
            }
        }