2017-05-24 88 views
2

我有一個場景,其中創建了一個可更新視圖,該視圖由多個使用INSTEAD OF觸發器的表組成。我已驗證我可以使用SSMS手動插入此視圖,而不會出現問題。但是,由於我的應用程序使用EF Code-First來表示視圖,因此在嘗試插入記錄時,由於EF在插入語句之後的後續SELECT中使用了SCOPE_IDENTITY(),所以最終出現問題。我曾嘗試在我的觸發器末尾添加一個聲明,以「強制」scope_identity()的包含,但沒有成功。我也嘗試在插入語句中包括OUTPUT選項,但沒有運氣。我開始懷疑我的方案對於EF來說太複雜了嗎?如何使用EF代碼首先插入/更新可更新視圖

基於反饋,我大大簡化這種情況下到的東西,很容易複製給大家:

相關的表和視圖定義:

IF NOT EXISTS(SELECT * FROM [sysobjects] WHERE [type] = 'U' AND [name] = 'Parent') 
BEGIN 
    CREATE TABLE [dbo].[Parent] 
    (
     [ent_pk] INT IDENTITY(1,1) NOT NULL, 
     [ent_id] VARCHAR(25) DEFAULT('') NOT NULL, 
     CONSTRAINT [PK_Parent] PRIMARY KEY CLUSTERED ([ent_pk]) 
    ) 
END 
GO 
IF NOT EXISTS(SELECT * FROM [sysobjects] WHERE [type] = 'U' AND [name] = 'Child1') 
BEGIN 
    CREATE TABLE [dbo].[Child1] 
    (
     [c1_entfk] INT NOT NULL, 
     [c1_field1] VARCHAR(30) DEFAULT('') NOT NULL, 
     [c1_fees] DECIMAL(7,2) DEFAULT(0) NOT NULL, 
     CONSTRAINT [PK_Child1] PRIMARY KEY CLUSTERED ([c1_entfk]) 
    ) 
END 
GO 
IF EXISTS(SELECT * FROM sys.views WHERE [object_id] = OBJECT_ID(N'[dbo].[vwView]')) 
    DROP VIEW [dbo].[vwView] 
GO 
CREATE VIEW [dbo].[vwView] AS 
SELECT [ent_pk], 
    [ent_id], 
    [c1_field1], 
    [c1_fees] 
    FROM [Parent] 
     LEFT JOIN [Child1] ON [c1_entfk] = [ent_pk] 
GO 

觸發代碼如下:

IF EXISTS(SELECT * FROM sysobjects WHERE [type] = 'TR' AND [name] = 'trg_vwView_i') 
    DROP TRIGGER [trg_vwView_i] 
GO 
CREATE TRIGGER [trg_vwView_i] ON [dbo].[vwView] INSTEAD OF INSERT 
AS 
BEGIN 
    INSERT INTO [Parent] ([ent_id]) 
     SELECT [ent_id] 
     FROM [Inserted] [I] 

    INSERT INTO [Child1] ([c1_entfk], [c1_field1], [c1_fees]) 
     SELECT [E].[ent_pk], [c1_field1], [c1_fees] 
     FROM [Inserted] [I] 
      INNER JOIN [Parent] [E] ON [E].[ent_id] = [I].[ent_id] 
END 
GO 

由於上述表格和視圖定義中沒有包含外鍵約束,所以父記錄必須在我之前插入插入其他表中的記錄以保持參照完整性。

而POCO類定義是:

[Table("vwView")] 
public class SimpleViewTest 
{ 

    [Key] 
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public int ent_pk { get; set; } 

    [MaxLength(25)] 
    public string ent_id { get; set; } 

    [MaxLength(30)] 
    public string c1_field1 { get; set; } 

    public decimal c1_fees { get; set; } 

} 

由EF用於插入生成的SQL的樣品:

exec sp_executesql N'INSERT [dbo].[vwView]([ent_id], [c1_field1], [c1_fees]) 
VALUES (@0, @1, @2) 
SELECT [ent_pk] 
FROM [dbo].[vwView] 
WHERE @@ROWCOUNT > 0 AND [ent_pk] = scope_identity()',N'@0 nvarchar(25),@1 nvarchar(30),@2 decimal(7,2) ',@0=N'AK FED CU',@1=N'Alaska Federal Credit Union',@2=10.00 

現在,執行在SSMS上述SQL語句正確地插入記錄進入各種表格。但是,它無法返回select中的任何內容,因爲SCOPE_IDENTITY()返回NULL。

所以,我不知道如何讓SCOPE_IDENTITY()返回插入到[Parent]表中的值,或者甚至是可能的。有誰知道這是否可以完成,或者如果我需要探索定義存儲過程來處理這種情況?或者還有其他建議嗎?

注:我要代表我的應用程序中的實體作爲「扁平化」的對象,而不是一個多對象組成......

+0

您的代碼太長,無法讀取,尤其是您應該嘗試簡化它的SQL,因此它更容易理解和閱讀 –

+0

感謝您的建議。我編輯了我原來的文章,以簡化應該更易於遵循的情景 –

回答

0

確定。在深入瞭解EF文檔後,我碰到了這個:https://msdn.microsoft.com/en-us/library/dn469464(v=vs.113).aspx。此外,我發現以下關於如何實現IDbCommandInterceptor的BLOG文章,它引用了可以修改EF執行前生成的SQL:https://www.skylinetechnologies.com/Blog/Skyline-Blog/December-2013/Entity-Framework-6-Intercepting-SQL-produced

所以,我的解決辦法是實現自己的IDbCommandInterceptor並重寫ReaderExecuiting方法如下:

public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) 
{ 
    string sqlToIntercept = "INSERT [dbo].[vwView]"; 

    if (command.CommandText.Contains(sqlToIntercept)) 
    { 
     command.CommandText = command.CommandText.Replace("WHERE @@ROWCOUNT > 0 AND [ent_pk] = scope_identity()", "WHERE @@ROWCOUNT > 0 AND [ent_id] = @1"); 
    } 
} 

現在,雖然不完全適合於這個問題的每一個可能的變化,在我的情況下,它確實非常努力很好,因爲業務規則,父表中的ent_id字段必須是唯一的。

希望即使這種情況與您可能遇到的情況不完全相同,該解決方案可能會提供額外的指導,讓您解決特定的情況。

感謝大家花時間閱讀和評論。祝一切順利。