0

私はこれを理解しようとしばらく探していました。複合主キーを使用してテーブルを作成しようとしています。キーの最初の部分は、親テーブルへの外部キーでもあります。2番目の部分は、SQLServerで自動生成されます。だから、私はこのように見えるはずのテーブルを持っています:

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.NETSqlCommandステートメント内でうまく機能しますが、EntityFrameworkはこれを使用したくないということです。

ChildId列のStoreGeneratedPatternをIdentityに設定すると、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:ストアの更新、挿入、または削除ステートメントが予期しない行数(0)に影響しました。エンティティがロードされてから、エンティティが変更または削除された可能性があります。ObjectStateManagerエントリを更新します。
----> System.Data.OptimisticConcurrencyException:ストアの更新、挿入、または削除ステートメントが予期しない行数(0)に影響しました。エンティティがロードされてから、エンティティが変更または削除された可能性があります。ObjectStateManagerエントリを更新します。

ただし、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

また、アプリケーションのエンティティは、SQLServerが生成したGUIDの値で更新されます。

つまり、Entity Frameworkが値を取得するために、列がIDENTITY列である必要はないことを示しています。ただし、論理テーブルを使用しているためinserted、ChildIdの値は変更された値にはなりません。トリガーによって。また、insertedテーブルにUPDATE操作を適用して、値をトリガー内にプッシュバックすることはできません(「論理テーブルINSERTEDおよびDELETEDは更新できません」と表示されました)。

私はここの隅に戻ったような気がしますが、設計を再考する前に、Entity Frameworkを介してChildId値をアプリケーションに戻す方法はありますか?

4

1 に答える 1

0

提案を提供するこの記事を見つけました:http://wiki.alphasoftware.com/Scope_Identity+in+SQL+Server+with+nested+and+INSTEAD+OF+triggers

TL;DR バージョンでは、最後にINSTEAD OF INSERTa を実行し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

Entity Model の Id 列はStoreGeneratedPatternbeing に設定されていIdentityます。これは、Entity Framework が を読み取ろうとすると、それ自体が提供した値ではなく、トリガー内SCOPE_IDENTITY()のステートメントが提供した値を取得することを意味します。これは、EF が予期していなかった次の結果セットに含まれ、無視されます。SELECTSELECT ... SCOPE_IDENTITY()

これにはいくつかの明らかな問題があります。

トリガーはトリガーから返されるデータを選択するようになったため、データを挿入して独自の選択を実行するストアド プロシージャなどの他のコードは、独自に選択したデータを押し出すことになります。そのため、データベース操作から 1 つの結果セットのみを期待するコードがある場合、追加の結果セットが含まれるようになりました。

エンティティ フレームワークのみを使用する場合は、これで問題ない可能性があります。ただし、将来がどうなるかはわかりませんので、このソリューションに完全に満足しているわけではありません。

于 2012-12-29T21:39:23.453 に答える