Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/entity-framework/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Entity framework 实体框架存在部分自动生成的复合键问题_Entity Framework_Sql Server 2008 R2 - Fatal编程技术网

Entity framework 实体框架存在部分自动生成的复合键问题

Entity framework 实体框架存在部分自动生成的复合键问题,entity-framework,sql-server-2008-r2,Entity Framework,Sql Server 2008 R2,我已经找了好一阵子想弄明白这一点。我正在尝试创建一个具有复合主键的表。键的第一部分也是父表的外键。第二部分是在SQL Server上自动生成的。所以,我有一张桌子,应该是这样的: ParentId ChildId -------- ------- 1 1 1 2 1 3 2 1 2 2 2 3 2 4 insert [dbo].[ChildTable]([ParentId], [Na

我已经找了好一阵子想弄明白这一点。我正在尝试创建一个具有复合主键的表。键的第一部分也是父表的外键。第二部分是在SQL Server上自动生成的。所以,我有一张桌子,应该是这样的:

ParentId ChildId
-------- -------
 1        1
 1        2
 1        3
 2        1
 2        2
 2        3
 2        4
insert [dbo].[ChildTable]([ParentId], [Name])
values (@0, @1)
select [ChildId]
from [dbo].[ChildTable]
where @@ROWCOUNT > 0 and [ParentId] = @0 and [Id] = scope_identity()
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
ChildId列仅在ParentId的上下文中是唯一的。这些值是在服务器上使用INSERT而不是INSERT触发器自动生成的,因此每个ChildId都有自己的序列

我的问题是,虽然这在SQL Server和经典的ADO.NET
SqlCommand
语句中很有效,但Entity Framework不希望使用它

如果我将ChildId列的StoreGeneratedPattern设置为标识,则EF将生成如下所示的SQL:

ParentId ChildId
-------- -------
 1        1
 1        2
 1        3
 2        1
 2        2
 2        3
 2        4
insert [dbo].[ChildTable]([ParentId], [Name])
values (@0, @1)
select [ChildId]
from [dbo].[ChildTable]
where @@ROWCOUNT > 0 and [ParentId] = @0 and [Id] = scope_identity()
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
这只会生成一个错误:

System.Data.Entity.Infrastructure.DbUpdateConcurrencyException:存储 update、insert或delete语句影响了意外数量的 行(0)。实体可能已被修改或删除,因为实体 都装了。刷新ObjectStateManager条目。
----> System.Data.OptimisticConcurrency异常:存储更新、插入或 delete语句影响了意外的行数(0)。实体 自加载实体后可能已被修改或删除。刷新 ObjectStateManager条目

但是,如果我使用基于GUID的键创建测试表,并将StoreGeneratedPattern设置为标识,则生成的SQL如下所示:

ParentId ChildId
-------- -------
 1        1
 1        2
 1        3
 2        1
 2        2
 2        3
 2        4
insert [dbo].[ChildTable]([ParentId], [Name])
values (@0, @1)
select [ChildId]
from [dbo].[ChildTable]
where @@ROWCOUNT > 0 and [ParentId] = @0 and [Id] = scope_identity()
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值进行更新

因此,这意味着该列不必是标识列,实体框架才能获取返回的值,但是,因为它使用逻辑表
inserted
ChildId的值不会是触发器更改为的值。此外,
插入的
表不能应用更新操作将值推回触发器内部(尝试过,它说“插入和删除的逻辑表不能更新”)


我觉得我已经把自己逼到了一个角落,但在我重新思考设计之前,有没有办法通过实体框架将ChildId值重新引入应用程序?

我发现这篇文章提供了一个建议:

TL;DR版本是,
而不是INSERT
在末尾执行
选择
,以返回钥匙。这篇文章是为触发器导致的
SCOPE\u 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
ALTER TRIGGER dbo.IOINS\u ChildTable
关于dbo.ChildTable
而不是插入
作为
开始
不计数;
--获取锁,以便其他人不能同时生成密钥。
--如果事务失败,锁将自动释放。
--如果采集时间超过15秒,则会出现错误。
声明@resint;
EXEC@res=sp_getapplock@Resource='IOINS\u ChildTable'
@LockMode='Exclusive'、@LockOwner='Transaction'、@LockTimeout='15000',
@DbPrincipal='public'
如果(@res<0)
开始
RAISERROR('无法获取锁以更新子表',16,1);
结束
--计算出每个正在执行的父级的当前最大ID
--插入此操作中。
声明@baseId表(baseId int,ParentId int);
插入@baseId
选择MAX(ISNULL(c.Id,0))作为BaseId,i.ParentId
从插入i
i.ParentId=c.ParentId上的左外部联接子表c
按i.ParentId分组
--替换插入操作
声明@keys表(Id INT);
插入到子表中
将输出插入到@keys中
挑选
i、 父ID,
(按i.ParentId划分,按i.ParentId排序)+b.BaseId上的行数()
作为身份证,
名称
从插入i
b.ParentId=i.ParentId上的内部联接@baseId b
--松开锁。
EXEC@res=sp\u releaseapplock@Resource='IOINS\u ChildTable',
@DbPrincipal='public',@LockOwner='Transaction'
从@keys中选择Id
结束
去
实体模型的Id列的
StoreGeneratedPattern
设置为
Identity
。这意味着,当实体框架尝试读取
SCOPE\u IDENTITY()
时,它将从提供的触发器中的
SELECT
语句获得值,而不是它自己的
SELECT。。。提供了SCOPE_IDENTITY()
,它现在位于EF不期望的下一个结果集中,将被忽略

这有一些明显的问题

因为触发器现在选择要从触发器返回的数据,这意味着插入一些数据并执行自己选择的其他代码(例如存储的进程)将从自己选择的代码中推出数据。因此,如果您的代码只需要数据库操作的一个结果集,那么它现在就有了一个额外的结果集

如果您只打算使用实体框架,那么这一切都可以。然而,我不能说未来会发生什么,所以我对这个解决方案并不完全满意