Sql server 基于复合主键的自动增量Id

Sql server 基于复合主键的自动增量Id,sql-server,entity-framework,Sql Server,Entity Framework,注意:使用SQLAzure和实体框架6 假设我有一个商店发票的下表,数据库中有多个商店 CREATE TABLE [dbo].[Invoice] ( [InvoiceId] INTEGER NOT NULL, [StoreId] UNIQUEIDENTIFIER NOT NULL, CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC) ); 理想情况下,我希望Inv

注意:使用SQLAzure和实体框架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连续递增,而不是独立于每个store

InvoiceId | StoreId
-------------------
1         | 'A'
2         | 'A'
3         | 'A'
1         | 'B'
2         | 'B'
问题:使[InvoiceId]基于[StoreId]递增的最佳方法是什么

可能的选择:

理想情况下,基于某种[StoreId]参数的[InvoiceId]整数非空标识将非常有用,但我怀疑这是否存在

b根据另一列返回函数来设置默认值的方法?好的,您不能在默认值中引用其他列

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覆盖或通过设置自定义插入查询,在实体框架代码中处理此问题的方法是先执行w/o迁移吗


注意:我意识到我可以使用存储过程插入发票,但我宁愿避免这样做,除非这是唯一的选项。

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

为了为用户生成InvoiceNumber(每个存储递增),可以使用按StoreId分区并按自动递增主键排序的

下面的例子说明了这一点:

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
在我的解决方案中使用了@Jamiec提供的答案之后,我决定使用触发器路径,以便保留发票号并更好地使用实体框架。此外,由于行号在INSERT AFAIK中不起作用,因此我使用MAX[InvoiceNumber]+1

这允许我设置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; }
}

试着像那样玩弄你自己的身份会给你带来一些严重的问题。并发性会在某个时候咬到你。如果您使用的是2012+a序列,那么它是处理这类事情的好方法。我真正的问题是你为什么要这么做?您的表定义了一个包含guid的聚集索引…这将导致大量索引碎片,除非您持续对其进行碎片整理。为什么不能只使用标识呢?对于发票标识,商店希望看到并发值的列表。如果他们得到发票1、2、4的清单,他们会打电话给我的支持热线询问3发生了什么?此外,我希望他们不知道我的网站在所有商店中处理了多少发票,如果我使用身份,他们就可以知道。我不熟悉序列。如果你能提供一个很有帮助的例子,不要向他们展示身份,他们就不知道了。如果你使用一个序列,你将需要一个单独的序列为每个商店。数据库用于数据完整性的InvoiceID和用户应查看并用于引用其查看的发票的InvoiceNumber之间存在差异。把这两件事分开,它们有完全不同的目的。谢谢@Jamiec。如果数据库被迁移或备份\还原,这会导致发票号码发生变化吗?啊,没关系。。。不适用于按发票ID排序的订单。我要试试这个!我对此进行了研究,但如果不创建单独的视图或存储过程,我就无法让它与实体框架很好地配合使用。相反,我在下面的答案中无意中找到了解决方案。我很想听听你对它的看法…@jt000我会使用一个包含此内容的视图,并让EF从该视图而不是表本身读取。我之所以不愿意接受你的答案,唯一的原因是我担心发票号不会保存在数据库中,而数据库中的发票号可能会被更改。。。在您的示例中,有没有一种方法可以将其直接保存为[TestData]中的列?如何处理并发性?如果两个插入同时进行,它们都将获得相同的InvoiceNumber?使用UPDLOCK,READPAST会处理这个问题吗?此外,一个唯一的约束将阻止插入,但是,在这种情况下,用户将得到一个奇怪的错误
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; }
}