这是一个旧问题,但在Entity Framework 6.2.0中仍然相关。 我的解决方案有三个步骤:
- 不要将
MainChildId
列设置为HasDatabaseGeneratedOption(Computed)
(否则您将无法随后更新它)
- 使用触发器在同时插入两条记录时更新父记录(如果父记录已存在且您只是添加新的子记录,则这不是问题,请确保触发器以某种方式考虑到这一点 - 在我的情况下很容易)
- 在调用
ctx.SaveChanges()
之后,还要确保调用ctx.Entry(myParentEntity).Reload()
从触发器获取MainChildId
列的任何更新(EF不会自动获取这些更新)。
在我的代码中,
Thing
是父记录,
ThingInstance
是子记录,并具有以下要求:
- 每当插入一个
Thing
(父级)时,也应该插入一个ThingInstance
(子级),并将其设置为Thing
的CurrentInstance
(主要子级)。
- 其他
ThingInstances
(子级)可以添加到Thing
(父级)中,可以成为CurrentInstance
(主要子级),也可以不成为。
这导致了以下设计:
* EF消费者必须插入两个记录,但将CurrentInstanceId
保留为空,但一定要确保将ThingInstance.Thing
设置为父级。
* 触发器将检测是否ThingInstance.Thing.CurrentInstanceId
为空。如果是,则将其更新为ThingInstance.Id
。
* EF消费者必须重新加载/获取数据以查看触发器的任何更新。
* 仍然需要两次往返,但只需要一次原子调用ctx.SaveChanges
,而且我不必处理手动回滚。
* 我有一个额外的触发器要管理,并且可能有比我在这里使用游标更有效的方法,但我永远不会在需要性能的情况下执行此操作。
数据库:
(抱歉,我没有测试这个脚本 - 只是从我的数据库中生成它并将其放在这里,因为我很匆忙。你应该能够从这里获取重要的部分。)
CREATE TABLE [dbo].[Thing](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Something] [nvarchar](255) NOT NULL,
[CurrentInstanceId] [bigint] NULL,
CONSTRAINT [PK_Thing] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[ThingInstance](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[ThingId] [bigint] NOT NULL,
[SomethingElse] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_ThingInstance] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Thing] WITH CHECK ADD CONSTRAINT [FK_Thing_ThingInstance] FOREIGN KEY([CurrentInstanceId])
REFERENCES [dbo].[ThingInstance] ([Id])
GO
ALTER TABLE [dbo].[Thing] CHECK CONSTRAINT [FK_Thing_ThingInstance]
GO
ALTER TABLE [dbo].[ThingInstance] WITH CHECK ADD CONSTRAINT [FK_ThingInstance_Thing] FOREIGN KEY([ThingId])
REFERENCES [dbo].[Thing] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[ThingInstance] CHECK CONSTRAINT [FK_ThingInstance_Thing]
GO
CREATE TRIGGER [dbo].[TR_ThingInstance_Insert]
ON [dbo].[ThingInstance]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @thingId bigint;
DECLARE @instanceId bigint;
declare cur CURSOR LOCAL for
select Id, ThingId from INSERTED
open cur
fetch next from cur into @instanceId, @thingId
while @@FETCH_STATUS = 0 BEGIN
DECLARE @CurrentInstanceId bigint = NULL;
SELECT @CurrentInstanceId=CurrentInstanceId FROM Thing WHERE Id=@thingId
IF @CurrentInstanceId IS NULL
BEGIN
UPDATE Thing SET CurrentInstanceId=@instanceId WHERE Id=@thingId
END
fetch next from cur into @instanceId, @thingId
END
close cur
deallocate cur
END
GO
ALTER TABLE [dbo].[ThingInstance] ENABLE TRIGGER [TR_ThingInstance_Insert]
GO
C# 插入:
public Thing Inserts(long currentId, string something)
{
using (var ctx = new MyContext())
{
Thing dbThing;
ThingInstance instance;
if (currentId > 0)
{
dbThing = ctx.Things
.Include(t => t.CurrentInstance)
.Single(t => t.Id == currentId);
instance = dbThing.CurrentInstance;
}
else
{
dbThing = new Thing();
instance = new ThingInstance
{
Thing = dbThing,
SomethingElse = "asdf"
};
ctx.ThingInstances.Add(instance);
}
dbThing.Something = something;
ctx.SaveChanges();
ctx.Entry(dbThing).Reload();
return dbThing;
}
}
C# 新建子项:
public Thing AddInstance(long thingId)
{
using (var ctx = new MyContext())
{
var dbThing = ctx.Things
.Include(t => t.CurrentInstance)
.Single(t => t.Id == thingId);
dbThing.CurrentInstance = new ThingInstance { SomethingElse = "qwerty", ThingId = dbThing.Id };
ctx.SaveChanges();
return dbThing;
}
}
SaveChanges()
来绕过它。 - theyetiman