[英]How to insert/update an updatable view using EF Code-First
我有一个场景,其中我创建了一个由几个使用INSTEAD OF触发器的表组成的可更新视图。 我已经验证可以使用SSMS手动插入此视图,而不会出现问题。 但是,由于我的应用程序使用EF Code-First表示视图,因此当尝试插入记录时,由于EF在insert语句之后的后续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]表中,或者甚至不可能。 有谁知道这是可以做的事情,还是我需要探索定义存储过程来处理这种情况? 还是人们还有其他建议?
注意:我想在我的应用程序中将实体表示为“平面”对象,而不是由多个对象组成的对象...
好。 在深入研究了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字段必须唯一,因此在我看来,它的效果很好。
希望即使这种情况与您可能遇到的相似情况都不完全相同,该解决方案也可以提供其他指导,使您能够解决自己的特定情况。
感谢所有花时间阅读和评论的人。 祝一切顺利。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.