我在位于使用实体框架 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);
...