具有筛选索引的实体框架 -“无法在对象中插入重复的键行”

本文关键字:插入 对象 索引 筛选 实体 框架 | 更新日期: 2023-09-27 18:32:54

我在位于使用实体框架 6 的 MVC Web 应用后面的数据库中具有以下简化的表定义和筛选的唯一索引。

CREATE TABLE [dbo].[ItemImage](
    [ItemId] [int] NOT NULL,
    [stream_id] [uniqueidentifier] ROWGUIDCOL NOT NULL,
    [Primary] [bit] NOT NULL,
    [Caption] [nvarchar](1000) NULL,
    CONSTRAINT [PK_ItemImage_ItemId_stream_id] PRIMARY KEY CLUSTERED ([ItemId] ASC, [stream_id] ASC)
    CONSTRAINT [FK_ItemImage_ItemId_Item] FOREIGN KEY([ItemId]) REFERENCES [dbo].[Item] ([ItemId])
);
GO
CREATE UNIQUE NONCLUSTERED INDEX [UXF_ItemImage_ItemId_Primary]
    ON [dbo].[ItemImage] ([ItemId] ASC)
    WHERE ([Primary] = 1);
GO

唯一索引可防止多个[ItemId]设置[Primary]位标志。

在 MVC 控制器中,我有一个ItemImage视图模型的集合,我正在使用这些模型更新 EF 模型,如下所示:

...
foreach (var img in itemViewModel.ItemImages)
{
    var itemImage = item.ItemImages.First(i => i.stream_id == img.stream_id);
    itemImage.Primary = img.Primary;
    itemImage.Caption = img.Caption;
}
...
await db.SaveChangesAsync();

调用db.SaveChangesAsync()时,我得到以下异常:

无法在对象'dbo'中插入重复的键行。具有唯一索引"UXF_ItemImage_ItemId_Primary"的项目图像"。重复的键值为 (146(。 该语句已终止。

我在更新之前有逻辑可以防止Item具有多个"主要"ItemImages

我认为发生这种情况是因为当实体框架尝试更新数据库中 ItemImages 的集合时,它会在取消设置当前设置的行之前将另一行的 [Primary] 标志设置为1

有没有办法在实体框架中强制更新顺序?或者是否有我可以实施的解决方法?

具有筛选索引的实体框架 -“无法在对象中插入重复的键行”

不确定这是否是最有效的方法,但现在可以使用以下命令工作。

foreach (var img in itemViewModel.ItemImages.Where(i => ! i.Primary))
{
    var itemImage = item.ItemImages.First(i => i.stream_id == img.stream_id);
    itemImage.Primary = img.Primary;
    itemImage.Caption = img.Caption;
}
await db.SaveChangesAsync();
var primaryImage = itemViewModel.ItemImages.First(i => i.Primary);
var itemImagePrimary = item.ItemImages.First(i => i.stream_id == primaryImage.stream_id);
itemImagePrimary.Primary = primaryImage.Primary;
itemImagePrimary.Caption = primaryImage.Caption;
await db.SaveChangesAsync();

从本质上讲,我只是首先批量更新所有"非主要"ItemImages,提交更改,然后更新新的"主要"。

更有效的方法是通过存储过程将更新推送到数据库层。

CREATE PROCEDURE [dbo].[uspItemImage_SetPrimary]
    @itemId INT
,   @stream_id UNIQUEIDENTIFIER
AS
BEGIN
    SET NOCOUNT ON;
    IF NOT EXISTS ( SELECT NULL FROM [dbo].[ItemImage] WHERE [ItemId] = @itemId AND [stream_id] = @stream_id )
    BEGIN
        DECLARE @stream_id_vc NVARCHAR(36) = CONVERT(NVARCHAR(36), NEWID());
        RAISERROR(  N'No ItemImage exists with ItemId = %d and stream_id = ''%s''',
                11,
                1,
                @itemId,
                @stream_id_vc);
    END;
    BEGIN TRANSACTION;
    UPDATE
        [dbo].[ItemImage]
    SET
        [Primary] = 0
    WHERE
        [ItemId] = @itemId;
    UPDATE
        [dbo].[ItemImage]
    SET
        [Primary] = 1
    WHERE
        [ItemId] = @itemId AND
        [stream_id] = @stream_id;
    COMMIT TRANSACTION;
    RETURN 0;
END;

将存储过程作为SetPrimaryItemImage导入实体框架模型后,我可以简单地执行以下操作:

...
foreach (var img in itemViewModel.Images)
{
    var itemImage = item.ItemImages.First(i => i.stream_id == img.stream_id);
    itemImage.Caption = img.Caption;
}
await db.SaveChangesAsync();
db.SetPrimaryItemImage(item.ItemId, itemViewModel.Images.First(i => i.Primary).stream_id);   
...