基于复合主键自动递增Id

时间:2015-01-14 15:44:59

标签: sql-server entity-framework

注意:使用Sql Azure&实体框架6

说我有一张商店发票的下表(数据库中有多个商店)......

CREATE TABLE [dbo].[Invoice] (
    [InvoiceId] INTEGER NOT NULL,
    [StoreId] UNIQUEIDENTIFIER NOT NULL,

    CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC)
);

理想情况下,我希望InvoiceId为每个StoreId连续递增,而不是独立于每个商店......

InvoiceId | StoreId
-------------------
1         | 'A'
2         | 'A'
3         | 'A'
1         | 'B'
2         | 'B'

问题:基于[InvoiceId][StoreId]增加的最佳方法是什么?

可能的选择:

a)理想情况下,某种[InvoiceId] INTEGER NOT NULL IDENTITY_BASED_ON([StoreId])参数确实很有帮助,但我怀疑这是否存在...

b)从基于另一列的函数返回设置默认值的方法? (AFAIK,您无法在默认情况下引用其他列)

CREATE FUNCTION [dbo].[NextInvoiceId]
(
    @storeId UNIQUEIDENTIFIER
)
RETURNS INT
AS
BEGIN
    DECLARE @nextId INT;
    SELECT @nextId = MAX([InvoiceId])+1 FROM [Invoice] WHERE [StoreId] = @storeId;
    IF (@nextId IS NULL)
        RETURN 1;

    RETURN @nextId;
END

CREATE TABLE [dbo].[Invoice] (
    [InvoiceId] INTEGER NOT NULL DEFAULT NextInvoiceId([StoreId]),
    [StoreId] UNIQUEIDENTIFIER NOT NULL,

    CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC)
);

c)使用DbContext.SaveChangesAsync覆盖或设置自定义插入查询在Entity Framework中处理此问题的方法(代码首先没有迁移)?

注意:我意识到我可以使用存储过程来插入发票,但除非是唯一的选择,否则我宁愿避免这种情况。

2 个答案:

答案 0 :(得分:4)

您应该坚持使用自动递增的整数主键,这比处理复合主键要简单得多,尤其是在将事物与发票相关联时。

为了为每个商店增加的用户生成InvoiceNumber,您可以使用由StoreId分区的ROW_NUMBER function并按自动递增主键排序。

以下示例演示了这一点:

WITH TestData(InvoiceId, StoreId) AS
(
    SELECT 1,'A'
    UNION SELECT 2,'A'
    UNION SELECT 3,'A'
    UNION SELECT 4,'B'
    UNION SELECT 5,'B'
)
Select InvoiceId,
        StoreId,
        ROW_NUMBER() OVER(PARTITION BY StoreId ORDER BY InvoiceId) AS InvoiceNumber
FROM TestData

结果:

InvoiceId | StoreId | InvoiceNumber
1         | A       | 1  
2         | A       | 2  
3         | A       | 3  
4         | B       | 1  
5         | B       | 2  

答案 1 :(得分:0)

在解决了@Jamiec在我的解决方案中提供的答案之后,我决定采用TRIGGER路线来保留发票号并更好地使用Entity Framework。此外,由于ROW_NUMBER在INSERT(AFAIK)中不起作用,我改为使用MAX([InvoiceNumber])+1

CREATE TABLE [dbo].[Invoice] (
    [InvoiceId] INTEGER NOT NULL,
    [StoreId] UNIQUEIDENTIFIER NOT NULL,
    [InvoiceNumber] INTEGER NOT NULL,

    CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC)
);

CREATE TRIGGER TGR_InvoiceNumber 
    ON [Invoice]
INSTEAD OF INSERT
AS
BEGIN
  INSERT INTO [Invoice] ([InvoiceId], [StoreId], [InvoiceNumber])
       SELECT [InvoiceId], 
              [StoreId], 
              ISNULL((SELECT MAX([InvoiceNumber]) + 1 FROM [Invoice] AS inv2 WHERE inv2.[StoreId] = inv1.[StoreId]), 1)
       FROM inserted as inv1;
END;

这允许我设置我的EF类:

public class Invoice
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int InvoiceId { get; set; }

    public Guid StoreId { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public int InvoiceNumber { get; set; }
}