如何使用 EF Code-First 插入/更新可更新视图

Posted

技术标签:

【中文标题】如何使用 EF Code-First 插入/更新可更新视图【英文标题】:How to insert/update an updatable view using EF Code-First 【发布时间】:2017-05-24 16:53:07 【问题描述】:

我有一个场景,我创建了一个可更新视图,该视图由几个使用 INSTEAD OF 触发器的表组成。我已经验证我可以使用 SSMS 手动插入此视图而不会出现问题。但是,由于我的应用程序使用 EF Code-First 来表示视图,当尝试插入记录时,由于 EF 在插入语句之后的后续 SELECT 中使用 SCOPE_IDENTITY() ,我最终遇到了问题。我曾尝试在触发器末尾包含一条语句以“强制”包含 scope_identity(),但没有成功。我也尝试在我的插入语句中包含 OUTPUT 选项,但也没有运气。我开始怀疑我的场景是否过于复杂以至于 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 语句可以正确地将记录插入到各个表中。但是,它无法在选择中返回任何内容,因为 SCOPE_IDENTITY() 返回 NULL。

所以,我不确定如何让 SCOPE_IDENTITY() 将插入的值返回到 [Parent] 表中,或者是否有可能。有谁知道这是否可以做到,或者我是否需要探索定义存储过程来处理这种情况?或者大家有什么其他的建议吗?

注意:我想将应用程序中的实体表示为“平面”对象,而不是由多个对象组成的对象...

【问题讨论】:

您的代码太长,无法阅读,尤其是 sql,您应该尝试简化它,以便更容易理解和阅读 感谢您的建议。我已经编辑了我的原始帖子以简化场景,这应该更容易理解 【参考方案1】:

好的。在深入研究了 EF 文档之后,我遇到了这个问题:https://msdn.microsoft.com/en-us/library/dn469464(v=vs.113).aspx。此外,我发现了以下关于实现 IDbCommandInterceptor 的博客文章,其中提到了能够在 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 字段必须是唯一的,这要归功于业务规则。

希望,即使这种情况与您可能遇到的类似情况不完全相同,此解决方案也可以提供额外的指导,让您解决您的特定情况。

感谢所有花时间阅读本文并发表评论的人。一切顺利。

【讨论】:

以上是关于如何使用 EF Code-First 插入/更新可更新视图的主要内容,如果未能解决你的问题,请参考以下文章

EF Code-First(Oracle)通过Migration来更新数据库的表的字段

如何使用 EF Core Code-First 创建具有子类别的类别表? [复制]

如何使用EF Core Code-First桥接自己的表

EF Code-First如何使用复合键从表中读取A.

如何使用 Code-First EF 4.1 从数据库中删除多个项目

如何将时态表添加到 EF Code-First DBContext?