Это старый вопрос, но он все еще актуален для Entity Framework 6.2.0.Мое решение состоит из трех частей:
- НЕ установите для столбца
MainChildId
значение HasDatabaseGeneratedOption(Computed)
(это блокирует его последующее обновление) - ИспользованиеТриггер для обновления родительского элемента, когда я вставляю обе записи одновременно (это не проблема, если родительский элемент уже существует, а я просто добавляю нового дочернего элемента, поэтому убедитесь, что триггер объясняет это как-то - было легко в моем случаеcase)
- После вызова
ctx.SaveChanges()
также обязательно вызовите ctx.Entry(myParentEntity).Reload()
, чтобы получить какие-либо обновления для столбца MainChildId
из Trigger (EF не получит их автоматически).
В моем коде ниже Thing
является родителем, а ThingInstance
является дочерним и имеет следующие требования:
- Всякий раз, когда вставляется
Thing
(родительский), a ThingInstance
(дочерний элемент) также должен быть вставлен и установлен как Thing
'CurrentInstance
(основной дочерний элемент). - Другой
ThingInstances
(дочерний элемент) может быть добавлен к Thing
(родительский)с или без CurrentInstance
(основной ребенок)
Это привело к следующему дизайну: * EF Потребитель должен вОбслужите обе записи, но оставьте CurrentInstanceId
как ноль, но обязательно установите ThingInstance.Thing
для родителя.* Триггер обнаружит, если ThingInstance.Thing.CurrentInstanceId
равно нулю.Если это так, то он обновит его до ThingInstance.Id
.* EF Consumer должен перезагрузить / повторно загрузить данные, чтобы просмотреть любые обновления по триггеру* Два обхода все еще необходимы, но необходим только один атомарный вызов 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 # New Child:
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(); // Reload not necessary here
return dbThing;
}
}