我正在尝试更新子实体,但在调用保存更改时收到 DbUpdateConcurrencyException。我的组织(父级)有一个设备列表(子级)。计划是建立一个组织,向其中添加新设备,然后更新数据库。
提前致谢。
我正在使用实体框架 8.0.10。
这是我的处理函数:
public async Task<Result> Handle(CreateDeviceCommand command, CancellationToken cancellationToken)
{
var organisation = await _organisationRepository.GetAsync(command.OrganisationId);
if (organisation == null) return Result.Failure(CreateDeviceErrors.NullOrganisation);
var deviceResult = Device.Create(
organisation,
command.DeviceId,
command.DeviceName);
if (deviceResult.IsFailure) return Result.Failure(deviceResult.Error);
var addDeviceResult = organisation.AddDevice(deviceResult.Value);
if (addDeviceResult.IsFailure) return Result.Failure(addDeviceResult.Error);
_organisationRepository.Update(organisation);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return Result.Success();
}
我的组织类型:
public sealed class Organisation : Entity
{
public string Name { get; private set; }
public DateTimeOffset Created { get; private set; }
public DateTimeOffset Updated { get; private set; }
public bool Deleted { get; private set; }
public List<Device> Devices { get; private set; }
public Result AddDevice(Device device)
{
if (Devices.Any(d => d.ExternalDeviceId == device.ExternalDeviceId)) return Result.Failure(OrganisationErrors.DuplicateDevice);
Devices.Add(device);
return Result.Success();
}
}
我的设备类型:
public sealed class Device : Entity
{
public string ExternalDeviceId { get; private set; }
public string Name { get; private set; }
public Guid OrganisationId { get; private set; }
public DateTimeOffset Created { get; private set; }
public DateTimeOffset Updated { get; private set; }
public bool Deleted { get; private set; }
}
我的组织存储库中的更新调用:
public void Update(Organisation organisation)
{
_context.Organisations.Update(organisation);
}
完整错误:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Npgsql.EntityFrameworkCore.PostgreSQL.Update.Internal.NpgsqlModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Npgsql.EntityFrameworkCore.PostgreSQL.Update.Internal.NpgsqlModificationCommandBatch.Consume(RelationalDataReader reader, Boolean async, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Rubicon.Licensing.Infrastructure.LicensingContext.SaveChangesAsync(CancellationToken cancellationToken) in C:\_repos\Rubicon.Platform.Payment\src\Licensing\Rubicon.Licensing.Infrastructure\LicensingContext.cs:line 49
at Rubicon.Licensing.Application.Devices.Create.CreateDeviceHandler.Handle(CreateDeviceCommand command, CancellationToken cancellationToken) in C:\_repos\Rubicon.Platform.Payment\src\Licensing\Rubicon.Licensing.Application\Devices\Create\CreateDeviceHandler.cs:line 39
at Rubicon.Licensing.Database.Tests.IntegrationTests.CreateDevice() in C:\_repos\Rubicon.Platform.Payment\Rubicon.Licensing.Database.Tests\IntegrationTests.cs:line 101
at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter`1.BlockUntilCompleted()
at NUnit.Framework.Internal.MessagePumpStrategy.NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter)
at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(Func`1 invoke)
at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
at NUnit.Framework.Internal.Commands.DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (466ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE DATABASE "Licensing";
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (27ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Organisations" (
"Id" uuid NOT NULL,
"Name" text NOT NULL,
"Created" timestamp with time zone NOT NULL,
"Updated" timestamp with time zone NOT NULL,
"Deleted" boolean NOT NULL,
CONSTRAINT "PK_Organisations" PRIMARY KEY ("Id")
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (9ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Users" (
"Id" uuid NOT NULL,
"AuthenticationId" text NOT NULL,
"Email" text NOT NULL,
"Name" text NOT NULL,
"Created" timestamp with time zone NOT NULL,
"Updated" timestamp with time zone NOT NULL,
"Deleted" boolean NOT NULL,
CONSTRAINT "PK_Users" PRIMARY KEY ("Id")
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (13ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Subscriptions" (
"Id" uuid NOT NULL,
"UserId" uuid NOT NULL,
"Source" integer NOT NULL,
"Status" integer NOT NULL,
"Created" timestamp with time zone NOT NULL,
"Updated" timestamp with time zone NOT NULL,
CONSTRAINT "PK_Subscriptions" PRIMARY KEY ("Id"),
CONSTRAINT "FK_Subscriptions_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Licenses" (
"Id" uuid NOT NULL,
"SubscriptionId" uuid NOT NULL,
"ProductType" integer NOT NULL,
"Quantity" integer NOT NULL,
CONSTRAINT "PK_Licenses" PRIMARY KEY ("Id"),
CONSTRAINT "FK_Licenses_Subscriptions_SubscriptionId" FOREIGN KEY ("SubscriptionId") REFERENCES "Subscriptions" ("Id") ON DELETE CASCADE
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "DeviceLicenses" (
"Id" uuid NOT NULL,
"OrganisationId" uuid NOT NULL,
"LicenseId" uuid NOT NULL,
"Quantity" integer NOT NULL,
"Created" timestamp with time zone NOT NULL,
"Updated" timestamp with time zone NOT NULL,
CONSTRAINT "PK_DeviceLicenses" PRIMARY KEY ("Id"),
CONSTRAINT "FK_DeviceLicenses_Licenses_LicenseId" FOREIGN KEY ("LicenseId") REFERENCES "Licenses" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_DeviceLicenses_Organisations_OrganisationId" FOREIGN KEY ("OrganisationId") REFERENCES "Organisations" ("Id") ON DELETE CASCADE
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (6ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "PlatformLicenses" (
"Id" uuid NOT NULL,
"OrganisationId" uuid NOT NULL,
"LicenseId" uuid NOT NULL,
"Created" timestamp with time zone NOT NULL,
"Updated" timestamp with time zone NOT NULL,
CONSTRAINT "PK_PlatformLicenses" PRIMARY KEY ("Id"),
CONSTRAINT "FK_PlatformLicenses_Licenses_LicenseId" FOREIGN KEY ("LicenseId") REFERENCES "Licenses" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_PlatformLicenses_Organisations_OrganisationId" FOREIGN KEY ("OrganisationId") REFERENCES "Organisations" ("Id") ON DELETE CASCADE
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Devices" (
"Id" uuid NOT NULL,
"ExternalDeviceId" text NOT NULL,
"Name" text NOT NULL,
"OrganisationId" uuid NOT NULL,
"Created" timestamp with time zone NOT NULL,
"Updated" timestamp with time zone NOT NULL,
"Deleted" boolean NOT NULL,
"DeviceLicenseId" uuid,
CONSTRAINT "PK_Devices" PRIMARY KEY ("Id"),
CONSTRAINT "FK_Devices_DeviceLicenses_DeviceLicenseId" FOREIGN KEY ("DeviceLicenseId") REFERENCES "DeviceLicenses" ("Id"),
CONSTRAINT "FK_Devices_Organisations_OrganisationId" FOREIGN KEY ("OrganisationId") REFERENCES "Organisations" ("Id") ON DELETE CASCADE
);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_DeviceLicenses_LicenseId" ON "DeviceLicenses" ("LicenseId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (6ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_DeviceLicenses_OrganisationId" ON "DeviceLicenses" ("OrganisationId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_Devices_DeviceLicenseId" ON "Devices" ("DeviceLicenseId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_Devices_OrganisationId" ON "Devices" ("OrganisationId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_Licenses_SubscriptionId" ON "Licenses" ("SubscriptionId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_PlatformLicenses_LicenseId" ON "PlatformLicenses" ("LicenseId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE UNIQUE INDEX "IX_PlatformLicenses_OrganisationId" ON "PlatformLicenses" ("OrganisationId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE INDEX "IX_Subscriptions_UserId" ON "Subscriptions" ("UserId");
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (26ms) [Parameters=[@__get_Item_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30']
SELECT o."Id", o."Created", o."Deleted", o."Name", o."Updated"
FROM "Organisations" AS o
WHERE o."Id" = @__get_Item_0
LIMIT 1
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (7ms) [Parameters=[@p0='?' (DbType = Guid), @p1='?' (DbType = DateTime), @p2='?' (DbType = Boolean), @p3='?', @p4='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Organisations" ("Id", "Created", "Deleted", "Name", "Updated")
VALUES (@p0, @p1, @p2, @p3, @p4);
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[@p6='?' (DbType = Guid), @p0='?' (DbType = DateTime), @p1='?' (DbType = Boolean), @p2='?', @p3='?', @p4='?' (DbType = Guid), @p5='?' (DbType = DateTime), @p11='?' (DbType = Guid), @p7='?' (DbType = DateTime), @p8='?' (DbType = Boolean), @p9='?', @p10='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30']
UPDATE "Devices" SET "Created" = @p0, "Deleted" = @p1, "ExternalDeviceId" = @p2, "Name" = @p3, "OrganisationId" = @p4, "Updated" = @p5
WHERE "Id" = @p6;
UPDATE "Organisations" SET "Created" = @p7, "Deleted" = @p8, "Name" = @p9, "Updated" = @p10
WHERE "Id" = @p11;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (18ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
DROP DATABASE "Licensing" WITH (FORCE);
我不确定这里需要改变什么,我有一个很好的谷歌,但有点卡住了。我可以重构处理程序以使用 DeviceRepository 创建设备,我已经测试过该设备并且工作正常,但我在其他地方使用此模式并希望确保我没有做一些愚蠢的事情。有人有什么想法吗?
https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.infrastruct.dbupdateconcurrencyException?view=entity-framework-6.2.0 上述文档指出: “当预期实体的 SaveChanges 会导致数据库更新但实际上数据库中没有行受到影响时,DbContext 抛出异常。这通常表明数据库已被并发更新,从而导致预期的并发令牌匹配实际上并不匹配。”
实体框架上下文不是线程安全的。鉴于上述内容在任务中运行,我将确保实际的 EF Core 操作以线程安全的方式发生。