How to insert/update an updatable view using EF Code-First

风格不统一 提交于 2019-12-13 15:38:10

问题


I have a scenario in which I have created an updatable view composed of several tables that uses INSTEAD OF triggers. I have verified that I can manually insert into this view using SSMS without an issue. However, as my application is using EF Code-First to represent the view, when attempting to insert a record I end up with a problem because of the EF's use of SCOPE_IDENTITY() in the subsequent SELECT after the insert statement. I have tried including a statement at the end of my trigger to "force" the scope_identity()'s inclusion, but without success. I have also tried including the OUTPUT option on my insert statement with no luck either. I'm beginning to wonder if my scenario is too complex for EF to handle?

Based upon feedback, I am greatly simplifying this scenario into something that's easy to reproduce for everyone:

The relevant table and view definitions:

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

The trigger code is as follows:

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

Because of Foreign Key constraints not included in the above table and view definitions, the Parent record must be inserted first before inserting the records in the other tables in order to maintain referential integrity.

And the POCO class definition is:

[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; }

}

A sample of the SQL generated by EF for the insert:

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

Now, executing the above SQL statement in SSMS correctly inserts the records into the various tables. But, it fails to return anything in the select because SCOPE_IDENTITY() returns NULL.

So, I'm not sure how to get the SCOPE_IDENTITY() to return the value for the insert into the [Parent] table or if it's even possible. Does anyone know if this is something that can be done, or if I need to explore defining stored procedures to handle this sort of scenario? Or are there any other suggestions people have?

NOTE: I want to represent the entity in my application as a "flat" object, not one composed of multiple objects...


回答1:


Ok. After digging around a lot more into the EF documentation, I ran across this: https://msdn.microsoft.com/en-us/library/dn469464(v=vs.113).aspx. Furthermore, I found the following BLOG post regarding implementing the IDbCommandInterceptor where it makes reference to being able to modify the SQL generated by EF before it gets executed: https://www.skylinetechnologies.com/Blog/Skyline-Blog/December-2013/Entity-Framework-6-Intercepting-SQL-produced.

So, my solution was to implement my own IDbCommandInterceptor and override the ReaderExecuiting method as follows:

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");
    }
}

Now, while not perfectly suited to every possible variation of this problem, in my case it does work quite nicely as the ent_id field in the Parent table must be unique thanks to business rules.

Hopefully, even if this scenario isn't exactly the same as a similar one you may encounter, this solution may provide additional direction allowing you to solve your specific scenario.

Thanks to everyone who took the time to read this and comment. All the best.



来源:https://stackoverflow.com/questions/44164403/how-to-insert-update-an-updatable-view-using-ef-code-first

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!