2012-12-29 60 views
0

我一直在寻找一段时间试图弄清楚这一点。我正在尝试创建一个包含复合主键的表。密钥的第一部分也是父表的外键。第二部分是在SQL Server上自动生成的。所以,我有一个应该看起来像这样的表:实体框架与部分自动生成的复合关键码有关

ParentId ChildId 
-------- ------- 
1  1 
1  2 
1  3 
2  1 
2  2 
2  3 
2  4 

ChildId列在ParentId的上下文中是唯一的。这些值是使用INSTEAD OF INSERT触发器在服务器上自动生成的,以便每个ChildId都有自己的序列。

我的问题是,尽管这在SQL Server和经典的ADO.NET SqlCommand语句中工作得很好,但实体框架并不想使用它。

如果我设置了childID的列的StoreGeneratedPattern是一个身份则EF生成SQL,看起来像这样:

insert [dbo].[ChildTable]([ParentId], [Name]) 
values (@0, @1) 
select [ChildId] 
from [dbo].[ChildTable] 
where @@ROWCOUNT > 0 and [ParentId] = @0 and [Id] = scope_identity() 

这只是产生一个错误:

System.Data.Entity.Infrastructure.DbUpdateConcurrencyException : Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
----> System.Data.OptimisticConcurrencyException : Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.

但是,如果我创建了一个使用基于GUID的密钥测试表,并将StoreGeneratedPattern设置为Identity,然后生成的SQL如下所示:

declare @generated_keys table([Id] uniqueidentifier) 
insert [dbo].[GuidTable]([Name]) 
output inserted.[Id] into @generated_keys 
values (@0) 
select t.[Id] 
from @generated_keys as g join [dbo].[GuidTable] as t on g.[Id] = t.[Id] 
where @@ROWCOUNT > 0 

而我的应用程序中的实体更新为SQL Server生成的GUID的值。

因此,这表明该列不必是一个IDENTITY列,以便实体框架获取值,但是,因为它使用逻辑表inserted,ChildId的值不会是值它被触发器改变了。此外,inserted表不能更新操作应用于它将值推回到触发器内(试图说,“逻辑表INSERTED和DELETED不能更新。”)

我觉得我已经有些人在这里支持自己到了一个角落,但在我重新思考设计之前,是否有任何方法通过Entity Framework将ChildId值返回到应用程序中?

回答

0

我发现这篇文章,其提供了一个建议:http://wiki.alphasoftware.com/Scope_Identity+in+SQL+Server+with+nested+and+INSTEAD+OF+triggers

的TL; DR版本是,INSTEAD OF INSERT在年底进行SELECT返回键。该文章是因为触发器而损失了SCOPE_IDENTITY()值,但它也适用于此。

所以,我所做的就是这样的:

的触发立刻读取

ALTER TRIGGER dbo.IOINS_ChildTable 
ON  dbo.ChildTable 
    INSTEAD OF INSERT 
AS  
BEGIN 
SET NOCOUNT ON; 
-- Acquire the lock so that no one else can generate a key at the same time. 
-- If the transaction fails then the lock will automatically be released. 
-- If the acquisition takes longer than 15 seconds an error is raised. 
DECLARE @res INT; 
EXEC @res = sp_getapplock @Resource = 'IOINS_ChildTable',  
    @LockMode = 'Exclusive', @LockOwner = 'Transaction', @LockTimeout = '15000', 
    @DbPrincipal = 'public' 
IF (@res < 0) 
BEGIN 
    RAISERROR('Unable to acquire lock to update ChildTable.', 16, 1); 
END 

-- Work out what the current maximum Ids are for each parent that is being 
-- inserted in this operation. 
DECLARE @baseId TABLE(BaseId int, ParentId int); 
INSERT INTO @baseId 
SELECT MAX(ISNULL(c.Id, 0)) AS BaseId, i.ParentId 
    FROM  inserted i 
    LEFT OUTER JOIN ChildTable c ON i.ParentId = c.ParentId 
    GROUP BY i.ParentId 

-- The replacement insert operation 
DECLARE @keys TABLE (Id INT); 
INSERT INTO ChildTable 
OUTPUT inserted.Id INTO @keys 
SELECT 
    i.ParentId, 
    ROW_NUMBER() OVER(PARTITION BY i.ParentId ORDER BY i.ParentId) + b.BaseId 
    AS Id, 
    Name 
FROM inserted i 
INNER JOIN @baseId b ON b.ParentId = i.ParentId 

-- Release the lock. 
EXEC @res = sp_releaseapplock @Resource = 'IOINS_ChildTable', 
    @DbPrincipal = 'public', @LockOwner = 'Transaction' 

SELECT Id FROM @keys 
END 
GO 

实体模型具有标识列的StoreGeneratedPattern设置为是Identity。这意味着当实体框架尝试读取SCOPE_IDENTITY()时,它将在提供的触发器中获得值SELECT,而不是提供的值为自己的SELECT ... SCOPE_IDENTITY(),该值现在位于EF未预期并将忽略的下一个结果集中。

这有一些明显的问题。

由于触发器现在选择要从触发器返回的数据,这意味着其他代码(例如存储过程)会插入一些数据并执行自己的选择,将使其自己选择的数据被推出。所以如果你的代码只需要一个来自数据库操作的结果集,它现在有一个额外的结果集。

如果你只是要使用实体框架,那么这可能都没问题。但是,我不能说未来会发生什么,所以我对这个解决方案并不满意。

相关问题