具有过滤索引的实体框架 - “无法在对象中插入重复的键行”
Posted
技术标签:
【中文标题】具有过滤索引的实体框架 - “无法在对象中插入重复的键行”【英文标题】:Entity Framework with filtered index - "Cannot insert duplicate key row in object" 【发布时间】:2016-02-02 10:09:38 【问题描述】:我在使用 Entity Framework 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()
被调用时,我得到以下异常:
无法在具有唯一索引“UXF_ItemImage_ItemId_Primary”的对象“dbo.ItemImage”中插入重复的键行。重复键值为 (146)。 声明已终止。
我在更新之前有防止Item
拥有多个“主要”ItemImages
的逻辑。
我认为这是因为当实体框架试图更新数据库中的 ItemImages 集合时,它会在取消设置当前设置的行之前将另一行的 [Primary]
标志设置为 1
。
有没有办法在实体框架中强制更新顺序?或者有什么我可以实施的解决方法?
【问题讨论】:
【参考方案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
,提交更改,然后更新新的“主要”。
【讨论】:
这看起来是正确的方法,AFAIK,您无法控制命令的顺序。我会将这一切都包装在一个事务中(使用db.Database.BeginTransaction()
)只是为了防止在出现其他任何问题时进行半生不熟的更新【参考方案2】:
更有效的方法是通过存储过程将更新推送到数据库层。
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);
...
【讨论】:
以上是关于具有过滤索引的实体框架 - “无法在对象中插入重复的键行”的主要内容,如果未能解决你的问题,请参考以下文章