基于复合主键自增Id
Auto-increment Id based on composite primary key
注意:使用 Sql Azure & Entity Framework 6
假设我有以下 table 个商店的发票(数据库中有多个商店)...
CREATE TABLE [dbo].[Invoice] (
[InvoiceId] INTEGER NOT NULL,
[StoreId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC)
);
理想情况下,我希望每个 StoreId 的 InvoiceId 连续递增,而不是独立于每个商店...
InvoiceId | StoreId
-------------------
1 | 'A'
2 | 'A'
3 | 'A'
1 | 'B'
2 | 'B'
问题:让[InvoiceId]
基于[StoreId]
递增的最佳方法是什么?
可能的选项:
a) 理想情况下,某种 [InvoiceId] INTEGER NOT NULL IDENTITY_BASED_ON([StoreId])
参数会非常有用,但我怀疑它是否存在...
b) 一种从基于另一列的函数的 return 设置默认值的方法? (据我所知,您不能在默认情况下引用另一列)
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) 在 Entity Framework(代码优先 w/o 迁移)中使用 DbContext.SaveChangesAsync
覆盖或通过设置自定义插入查询来处理此问题的方法?
注意:我知道我可以使用存储过程来插入发票,但我宁愿避免这样做,除非它是唯一的选择。
您应该坚持使用自动递增的整数主键,这比处理复合主键要简单得多,尤其是在将事物关联回发票时。
为了为用户生成一个按商店递增的 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
在尝试了@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 class:
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; }
}
注意:使用 Sql Azure & Entity Framework 6
假设我有以下 table 个商店的发票(数据库中有多个商店)...
CREATE TABLE [dbo].[Invoice] (
[InvoiceId] INTEGER NOT NULL,
[StoreId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([InvoiceId] ASC, [StoreId] ASC)
);
理想情况下,我希望每个 StoreId 的 InvoiceId 连续递增,而不是独立于每个商店...
InvoiceId | StoreId
-------------------
1 | 'A'
2 | 'A'
3 | 'A'
1 | 'B'
2 | 'B'
问题:让[InvoiceId]
基于[StoreId]
递增的最佳方法是什么?
可能的选项:
a) 理想情况下,某种 [InvoiceId] INTEGER NOT NULL IDENTITY_BASED_ON([StoreId])
参数会非常有用,但我怀疑它是否存在...
b) 一种从基于另一列的函数的 return 设置默认值的方法? (据我所知,您不能在默认情况下引用另一列)
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) 在 Entity Framework(代码优先 w/o 迁移)中使用 DbContext.SaveChangesAsync
覆盖或通过设置自定义插入查询来处理此问题的方法?
注意:我知道我可以使用存储过程来插入发票,但我宁愿避免这样做,除非它是唯一的选择。
您应该坚持使用自动递增的整数主键,这比处理复合主键要简单得多,尤其是在将事物关联回发票时。
为了为用户生成一个按商店递增的 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
在尝试了@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 class:
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; }
}