带有过滤索引的实体框架 - “无法在对象中插入重复的键行”

时间:2016-02-02 10:09:39

标签: c# sql-server asp.net-mvc entity-framework

我在使用Entity Framework 6的MVC Web应用程序后面的数据库中有以下简化表定义和过滤的唯一索引。

//package.json
{
  "name": "http-server",
  "main": "server.js",
  "dependencies": {
    "express": "^4.13.4"
  }
}


//server.js
var express = require('express');
var app = express();
var path = require('path');

app.get('/', function (res, req) {
  res.sendFile(path.join(__dirname + '/index.html'));
});

app.listen(1337);
console.log('Visit me at http://localhost:1337');

唯一索引会阻止多个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]位标志。

在MVC控制器中,我有一组[Primary]视图模型,我正在更新EF模型,如下所示:

ItemImage

当调用... 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(); 时,我得到以下异常:

  

无法在对象'dbo.ItemImage'中插入具有唯一索引'UXF_ItemImage_ItemId_Primary'的重复键行。重复键值是(146)。   声明已经终止。

我在更新之前有逻辑阻止db.SaveChangesAsync()有多个“主要”Item

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

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

2 个答案:

答案 0 :(得分:0)

不确定这是否是最有效的方法,但它现在正在使用以下方法。

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,提交更改,然后更新新的“主要”。

答案 1 :(得分:0)

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

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);   
...