From 88db3dbd1ea872738b2136d121f91389c6bfec11 Mon Sep 17 00:00:00 2001 From: Viktor Kulkov Date: Mon, 4 May 2026 17:20:38 +0300 Subject: [PATCH 1/2] dbcontext activation strategy --- AppForeach.Framework.sln | 233 ----------------- AppForeach.Framework.slnx | 39 +++ .../Audit/AuditMiddleware.cs | 10 +- .../DbContextActivator.cs | 38 ++- .../DbContextOperationEnlistmentStrategy.cs | 12 + .../IDbContextActivator.cs | 2 +- ...Framework.EntityFrameworkCore.Tests.csproj | 38 +++ .../DbContextActivatorTests.cs | 235 ++++++++++++++++++ 8 files changed, 358 insertions(+), 249 deletions(-) delete mode 100644 AppForeach.Framework.sln create mode 100644 AppForeach.Framework.slnx create mode 100644 src/AppForeach.Framework.EntityFrameworkCore/DbContextOperationEnlistmentStrategy.cs create mode 100644 tests/AppForeach.Framework.EntityFrameworkCore.Tests/AppForeach.Framework.EntityFrameworkCore.Tests.csproj create mode 100644 tests/AppForeach.Framework.EntityFrameworkCore.Tests/DbContextActivatorTests.cs diff --git a/AppForeach.Framework.sln b/AppForeach.Framework.sln deleted file mode 100644 index 7313054..0000000 --- a/AppForeach.Framework.sln +++ /dev/null @@ -1,233 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32126.317 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework", "src\AppForeach.Framework\AppForeach.Framework.csproj", "{2A6ECF74-2FC4-49AC-B6F5-7C24E1DD2E12}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{05C7432E-1129-4FDA-A1C1-4E6B02F29A68}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.Castle.Windsor", "src\AppForeach.Framework.Castle.Windsor\AppForeach.Framework.Castle.Windsor.csproj", "{1D6F3396-B454-46F7-AC3D-1C73FCC32CB0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.EntityFrameworkCore", "src\AppForeach.Framework.EntityFrameworkCore\AppForeach.Framework.EntityFrameworkCore.csproj", "{4BAE543F-9636-45C8-B879-3A3235B3CF83}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.EntityFrameworkCore.Design", "src\AppForeach.Framework.EntityFrameworkCore.Design\AppForeach.Framework.EntityFrameworkCore.Design.csproj", "{9BB52869-7DA1-4532-8EA0-78D30E9F8F26}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.Autofac", "src\AppForeach.Framework.Autofac\AppForeach.Framework.Autofac.csproj", "{81F5ABA4-3E77-4A01-B79A-E3654E95C7F3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.Microsoft.Extensions", "src\AppForeach.Framework.Microsoft.Extensions\AppForeach.Framework.Microsoft.Extensions.csproj", "{FD4CBA8E-F617-4A61-90FF-854E40543E1C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.Hosting", "src\AppForeach.Framework.Hosting\AppForeach.Framework.Hosting.csproj", "{D3B1B5F6-3155-4F3B-A4F5-1EFB153A2C73}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.Hosting.Web", "src\AppForeach.Framework.Hosting.Web\AppForeach.Framework.Hosting.Web.csproj", "{BD0F5FFD-FFEB-4A18-A15F-97AB7D6804B9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.MassTransit", "src\AppForeach.Framework.MassTransit\AppForeach.Framework.MassTransit.csproj", "{00A7B6CD-F613-466F-913A-5E62D2181DEE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.EntityFrameworkCore.SqlServer", "src\AppForeach.Framework.EntityFrameworkCore.SqlServer\AppForeach.Framework.EntityFrameworkCore.SqlServer.csproj", "{715B32C2-7E5D-4E2B-8AF0-43891884A846}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppForeach.Framework.Hosting.Features.SqlServer", "src\AppForeach.Framework.Hosting.Features.SqlServer\AppForeach.Framework.Hosting.Features.SqlServer.csproj", "{AE8CB98C-EF0A-40BA-94DD-1FA329D048F9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.Tests", "tests\AppForeach.Framework.Tests\AppForeach.Framework.Tests.csproj", "{C9D13CE3-A6A1-4C68-A56B-16B095EB9DB0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.FluentValidation", "src\AppForeach.Framework.FluentValidation\AppForeach.Framework.FluentValidation.csproj", "{3C775D76-45F2-437E-BE87-7D2AF5D672E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.AutoMapper", "src\AppForeach.Framework.AutoMapper\AppForeach.Framework.AutoMapper.csproj", "{517BBEC2-6740-49B4-8395-CE7FDEDD9062}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.AutoMapper.Tests", "tests\AppForeach.Framework.AutoMapper.Tests\AppForeach.Framework.AutoMapper.Tests.csproj", "{FED6689C-A438-4543-9878-DB5560CDF79C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.FluentValidation.Tests", "tests\AppForeach.Framework.FluentValidation.Tests\AppForeach.Framework.FluentValidation.Tests.csproj", "{76CCEA85-4990-4AD6-AF7C-B708B54BC2AF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.Hosting.Features.Serilog.Ecs", "src\AppForeach.Framework.Hosting.Features.Serilog.Ecs\AppForeach.Framework.Hosting.Features.Serilog.Ecs.csproj", "{4A31C38A-6D80-FB65-7FDE-AA5193751C0F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.Serilog", "src\AppForeach.Framework.Serilog\AppForeach.Framework.Serilog.csproj", "{2B4DF4D3-EB50-3F67-79F6-91AB9D8D912E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.Hosting.Features.PostgreSql", "src\AppForeach.Framework.Hosting.Features.PostgreSql\AppForeach.Framework.Hosting.Features.PostgreSql.csproj", "{AAD9C406-038A-CA25-FCBA-E4FB769AE34A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.EntityFrameworkCore.PostgreSql", "src\AppForeach.Framework.EntityFrameworkCore.PostgreSql\AppForeach.Framework.EntityFrameworkCore.PostgreSql.csproj", "{6EFBA666-98FD-5FE0-9BD5-9D9CEAC68835}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppForeach.Framework.EntityFrameworkCore.PostgreSql.Design", "src\AppForeach.Framework.EntityFrameworkCore.PostgreSql.Design\AppForeach.Framework.EntityFrameworkCore.PostgreSql.Design.csproj", "{E3388F03-F5D2-9A8B-1A41-7A84E90301F0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B2E7681C-7927-4FBE-A7D7-3B675E480D41}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EscapeHit", "EscapeHit", "{87C21479-CDC0-4E42-ADDD-2A1F6B22190A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit", "samples\EscapeHit\EscapeHit\EscapeHit.csproj", "{AA0D229C-82BC-044D-DBA7-BC80A1BB2B36}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.Service", "samples\EscapeHit\EscapeHit.Service\EscapeHit.Service.csproj", "{7B2DAA3C-38CF-FBD1-6ABB-EAFAE147CB51}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.WebApi", "samples\EscapeHit\EscapeHit.WebApi\EscapeHit.WebApi.csproj", "{D5FE8AEB-86A9-F5D8-B597-B09E6E99BAAD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.Invoice", "samples\EscapeHit\EscapeHit.Invoice\EscapeHit.Invoice.csproj", "{6E52E381-FA0A-E1A1-8499-42786F76DA1A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.Invoice.Database", "samples\EscapeHit\EscapeHit.Invoice.Database\EscapeHit.Invoice.Database.csproj", "{20DCD3F2-5EEA-52B6-E312-F9E4EA5E4C54}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.Invoice.Database.Design", "samples\EscapeHit\EscapeHit.Invoice.Database.Design\EscapeHit.Invoice.Database.Design.csproj", "{05BAA965-9B53-98D2-6ACE-30FF076AB0C3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.Invoice.Service", "samples\EscapeHit\EscapeHit.Invoice.Service\EscapeHit.Invoice.Service.csproj", "{5533EB29-3EBF-6E1F-92FB-DB7BE18B8CEA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EscapeHit.Invoice.WebApi", "samples\EscapeHit\EscapeHit.Invoice.WebApi\EscapeHit.Invoice.WebApi.csproj", "{6C088FDE-CC76-FD93-817F-473D625C28CB}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2A6ECF74-2FC4-49AC-B6F5-7C24E1DD2E12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A6ECF74-2FC4-49AC-B6F5-7C24E1DD2E12}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A6ECF74-2FC4-49AC-B6F5-7C24E1DD2E12}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A6ECF74-2FC4-49AC-B6F5-7C24E1DD2E12}.Release|Any CPU.Build.0 = Release|Any CPU - {1D6F3396-B454-46F7-AC3D-1C73FCC32CB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D6F3396-B454-46F7-AC3D-1C73FCC32CB0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D6F3396-B454-46F7-AC3D-1C73FCC32CB0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D6F3396-B454-46F7-AC3D-1C73FCC32CB0}.Release|Any CPU.Build.0 = Release|Any CPU - {4BAE543F-9636-45C8-B879-3A3235B3CF83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BAE543F-9636-45C8-B879-3A3235B3CF83}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BAE543F-9636-45C8-B879-3A3235B3CF83}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BAE543F-9636-45C8-B879-3A3235B3CF83}.Release|Any CPU.Build.0 = Release|Any CPU - {9BB52869-7DA1-4532-8EA0-78D30E9F8F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BB52869-7DA1-4532-8EA0-78D30E9F8F26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BB52869-7DA1-4532-8EA0-78D30E9F8F26}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BB52869-7DA1-4532-8EA0-78D30E9F8F26}.Release|Any CPU.Build.0 = Release|Any CPU - {81F5ABA4-3E77-4A01-B79A-E3654E95C7F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {81F5ABA4-3E77-4A01-B79A-E3654E95C7F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {81F5ABA4-3E77-4A01-B79A-E3654E95C7F3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {81F5ABA4-3E77-4A01-B79A-E3654E95C7F3}.Release|Any CPU.Build.0 = Release|Any CPU - {FD4CBA8E-F617-4A61-90FF-854E40543E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD4CBA8E-F617-4A61-90FF-854E40543E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD4CBA8E-F617-4A61-90FF-854E40543E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD4CBA8E-F617-4A61-90FF-854E40543E1C}.Release|Any CPU.Build.0 = Release|Any CPU - {D3B1B5F6-3155-4F3B-A4F5-1EFB153A2C73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3B1B5F6-3155-4F3B-A4F5-1EFB153A2C73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3B1B5F6-3155-4F3B-A4F5-1EFB153A2C73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3B1B5F6-3155-4F3B-A4F5-1EFB153A2C73}.Release|Any CPU.Build.0 = Release|Any CPU - {BD0F5FFD-FFEB-4A18-A15F-97AB7D6804B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD0F5FFD-FFEB-4A18-A15F-97AB7D6804B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD0F5FFD-FFEB-4A18-A15F-97AB7D6804B9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD0F5FFD-FFEB-4A18-A15F-97AB7D6804B9}.Release|Any CPU.Build.0 = Release|Any CPU - {00A7B6CD-F613-466F-913A-5E62D2181DEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00A7B6CD-F613-466F-913A-5E62D2181DEE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00A7B6CD-F613-466F-913A-5E62D2181DEE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00A7B6CD-F613-466F-913A-5E62D2181DEE}.Release|Any CPU.Build.0 = Release|Any CPU - {715B32C2-7E5D-4E2B-8AF0-43891884A846}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715B32C2-7E5D-4E2B-8AF0-43891884A846}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715B32C2-7E5D-4E2B-8AF0-43891884A846}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715B32C2-7E5D-4E2B-8AF0-43891884A846}.Release|Any CPU.Build.0 = Release|Any CPU - {AE8CB98C-EF0A-40BA-94DD-1FA329D048F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE8CB98C-EF0A-40BA-94DD-1FA329D048F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE8CB98C-EF0A-40BA-94DD-1FA329D048F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE8CB98C-EF0A-40BA-94DD-1FA329D048F9}.Release|Any CPU.Build.0 = Release|Any CPU - {C9D13CE3-A6A1-4C68-A56B-16B095EB9DB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9D13CE3-A6A1-4C68-A56B-16B095EB9DB0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9D13CE3-A6A1-4C68-A56B-16B095EB9DB0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9D13CE3-A6A1-4C68-A56B-16B095EB9DB0}.Release|Any CPU.Build.0 = Release|Any CPU - {3C775D76-45F2-437E-BE87-7D2AF5D672E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C775D76-45F2-437E-BE87-7D2AF5D672E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C775D76-45F2-437E-BE87-7D2AF5D672E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C775D76-45F2-437E-BE87-7D2AF5D672E8}.Release|Any CPU.Build.0 = Release|Any CPU - {517BBEC2-6740-49B4-8395-CE7FDEDD9062}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {517BBEC2-6740-49B4-8395-CE7FDEDD9062}.Debug|Any CPU.Build.0 = Debug|Any CPU - {517BBEC2-6740-49B4-8395-CE7FDEDD9062}.Release|Any CPU.ActiveCfg = Release|Any CPU - {517BBEC2-6740-49B4-8395-CE7FDEDD9062}.Release|Any CPU.Build.0 = Release|Any CPU - {FED6689C-A438-4543-9878-DB5560CDF79C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FED6689C-A438-4543-9878-DB5560CDF79C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FED6689C-A438-4543-9878-DB5560CDF79C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FED6689C-A438-4543-9878-DB5560CDF79C}.Release|Any CPU.Build.0 = Release|Any CPU - {76CCEA85-4990-4AD6-AF7C-B708B54BC2AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76CCEA85-4990-4AD6-AF7C-B708B54BC2AF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76CCEA85-4990-4AD6-AF7C-B708B54BC2AF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76CCEA85-4990-4AD6-AF7C-B708B54BC2AF}.Release|Any CPU.Build.0 = Release|Any CPU - {4A31C38A-6D80-FB65-7FDE-AA5193751C0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A31C38A-6D80-FB65-7FDE-AA5193751C0F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A31C38A-6D80-FB65-7FDE-AA5193751C0F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A31C38A-6D80-FB65-7FDE-AA5193751C0F}.Release|Any CPU.Build.0 = Release|Any CPU - {2B4DF4D3-EB50-3F67-79F6-91AB9D8D912E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B4DF4D3-EB50-3F67-79F6-91AB9D8D912E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B4DF4D3-EB50-3F67-79F6-91AB9D8D912E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B4DF4D3-EB50-3F67-79F6-91AB9D8D912E}.Release|Any CPU.Build.0 = Release|Any CPU - {AAD9C406-038A-CA25-FCBA-E4FB769AE34A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAD9C406-038A-CA25-FCBA-E4FB769AE34A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAD9C406-038A-CA25-FCBA-E4FB769AE34A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAD9C406-038A-CA25-FCBA-E4FB769AE34A}.Release|Any CPU.Build.0 = Release|Any CPU - {6EFBA666-98FD-5FE0-9BD5-9D9CEAC68835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EFBA666-98FD-5FE0-9BD5-9D9CEAC68835}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EFBA666-98FD-5FE0-9BD5-9D9CEAC68835}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EFBA666-98FD-5FE0-9BD5-9D9CEAC68835}.Release|Any CPU.Build.0 = Release|Any CPU - {E3388F03-F5D2-9A8B-1A41-7A84E90301F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E3388F03-F5D2-9A8B-1A41-7A84E90301F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E3388F03-F5D2-9A8B-1A41-7A84E90301F0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E3388F03-F5D2-9A8B-1A41-7A84E90301F0}.Release|Any CPU.Build.0 = Release|Any CPU - {AA0D229C-82BC-044D-DBA7-BC80A1BB2B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AA0D229C-82BC-044D-DBA7-BC80A1BB2B36}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AA0D229C-82BC-044D-DBA7-BC80A1BB2B36}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AA0D229C-82BC-044D-DBA7-BC80A1BB2B36}.Release|Any CPU.Build.0 = Release|Any CPU - {7B2DAA3C-38CF-FBD1-6ABB-EAFAE147CB51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B2DAA3C-38CF-FBD1-6ABB-EAFAE147CB51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B2DAA3C-38CF-FBD1-6ABB-EAFAE147CB51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B2DAA3C-38CF-FBD1-6ABB-EAFAE147CB51}.Release|Any CPU.Build.0 = Release|Any CPU - {D5FE8AEB-86A9-F5D8-B597-B09E6E99BAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5FE8AEB-86A9-F5D8-B597-B09E6E99BAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5FE8AEB-86A9-F5D8-B597-B09E6E99BAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5FE8AEB-86A9-F5D8-B597-B09E6E99BAAD}.Release|Any CPU.Build.0 = Release|Any CPU - {6E52E381-FA0A-E1A1-8499-42786F76DA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E52E381-FA0A-E1A1-8499-42786F76DA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E52E381-FA0A-E1A1-8499-42786F76DA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E52E381-FA0A-E1A1-8499-42786F76DA1A}.Release|Any CPU.Build.0 = Release|Any CPU - {20DCD3F2-5EEA-52B6-E312-F9E4EA5E4C54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {20DCD3F2-5EEA-52B6-E312-F9E4EA5E4C54}.Debug|Any CPU.Build.0 = Debug|Any CPU - {20DCD3F2-5EEA-52B6-E312-F9E4EA5E4C54}.Release|Any CPU.ActiveCfg = Release|Any CPU - {20DCD3F2-5EEA-52B6-E312-F9E4EA5E4C54}.Release|Any CPU.Build.0 = Release|Any CPU - {05BAA965-9B53-98D2-6ACE-30FF076AB0C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {05BAA965-9B53-98D2-6ACE-30FF076AB0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {05BAA965-9B53-98D2-6ACE-30FF076AB0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {05BAA965-9B53-98D2-6ACE-30FF076AB0C3}.Release|Any CPU.Build.0 = Release|Any CPU - {5533EB29-3EBF-6E1F-92FB-DB7BE18B8CEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5533EB29-3EBF-6E1F-92FB-DB7BE18B8CEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5533EB29-3EBF-6E1F-92FB-DB7BE18B8CEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5533EB29-3EBF-6E1F-92FB-DB7BE18B8CEA}.Release|Any CPU.Build.0 = Release|Any CPU - {6C088FDE-CC76-FD93-817F-473D625C28CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6C088FDE-CC76-FD93-817F-473D625C28CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6C088FDE-CC76-FD93-817F-473D625C28CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6C088FDE-CC76-FD93-817F-473D625C28CB}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {2A6ECF74-2FC4-49AC-B6F5-7C24E1DD2E12} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {1D6F3396-B454-46F7-AC3D-1C73FCC32CB0} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {4BAE543F-9636-45C8-B879-3A3235B3CF83} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {9BB52869-7DA1-4532-8EA0-78D30E9F8F26} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {81F5ABA4-3E77-4A01-B79A-E3654E95C7F3} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {FD4CBA8E-F617-4A61-90FF-854E40543E1C} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {D3B1B5F6-3155-4F3B-A4F5-1EFB153A2C73} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {BD0F5FFD-FFEB-4A18-A15F-97AB7D6804B9} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {00A7B6CD-F613-466F-913A-5E62D2181DEE} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {715B32C2-7E5D-4E2B-8AF0-43891884A846} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {AE8CB98C-EF0A-40BA-94DD-1FA329D048F9} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {C9D13CE3-A6A1-4C68-A56B-16B095EB9DB0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3C775D76-45F2-437E-BE87-7D2AF5D672E8} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {517BBEC2-6740-49B4-8395-CE7FDEDD9062} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {FED6689C-A438-4543-9878-DB5560CDF79C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {76CCEA85-4990-4AD6-AF7C-B708B54BC2AF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {4A31C38A-6D80-FB65-7FDE-AA5193751C0F} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {2B4DF4D3-EB50-3F67-79F6-91AB9D8D912E} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {AAD9C406-038A-CA25-FCBA-E4FB769AE34A} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {6EFBA666-98FD-5FE0-9BD5-9D9CEAC68835} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {E3388F03-F5D2-9A8B-1A41-7A84E90301F0} = {05C7432E-1129-4FDA-A1C1-4E6B02F29A68} - {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} = {B2E7681C-7927-4FBE-A7D7-3B675E480D41} - {AA0D229C-82BC-044D-DBA7-BC80A1BB2B36} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {7B2DAA3C-38CF-FBD1-6ABB-EAFAE147CB51} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {D5FE8AEB-86A9-F5D8-B597-B09E6E99BAAD} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {6E52E381-FA0A-E1A1-8499-42786F76DA1A} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {20DCD3F2-5EEA-52B6-E312-F9E4EA5E4C54} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {05BAA965-9B53-98D2-6ACE-30FF076AB0C3} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {5533EB29-3EBF-6E1F-92FB-DB7BE18B8CEA} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - {6C088FDE-CC76-FD93-817F-473D625C28CB} = {87C21479-CDC0-4E42-ADDD-2A1F6B22190A} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B1D619CF-2C52-4F0A-9034-C10A04583982} - EndGlobalSection -EndGlobal diff --git a/AppForeach.Framework.slnx b/AppForeach.Framework.slnx new file mode 100644 index 0000000..6fa3a5f --- /dev/null +++ b/AppForeach.Framework.slnx @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AppForeach.Framework.EntityFrameworkCore/Audit/AuditMiddleware.cs b/src/AppForeach.Framework.EntityFrameworkCore/Audit/AuditMiddleware.cs index 154332a..be50fee 100644 --- a/src/AppForeach.Framework.EntityFrameworkCore/Audit/AuditMiddleware.cs +++ b/src/AppForeach.Framework.EntityFrameworkCore/Audit/AuditMiddleware.cs @@ -13,17 +13,17 @@ public class AuditMiddleware : IOperationMiddleware { private readonly IOperationContext context; private readonly ILoggingCorrelationProvider loggingCorrelationProvider; - private readonly IDbOptionsConfigurator dbOptionsConfigurator; + private readonly IDbContextActivator dbContextActivator; private readonly IConnectionStringProvider connectionStringProvider; private readonly IServiceProvider serviceProvider; public AuditMiddleware(IOperationContext context, ILoggingCorrelationProvider loggingCorrelationProvider, - IDbOptionsConfigurator dbOptionsConfigurator, IConnectionStringProvider connectionStringProvider, + IDbContextActivator dbContextActivator, IConnectionStringProvider connectionStringProvider, IServiceProvider serviceProvider) { this.context = context; this.loggingCorrelationProvider = loggingCorrelationProvider; - this.dbOptionsConfigurator = dbOptionsConfigurator; + this.dbContextActivator = dbContextActivator; this.connectionStringProvider = connectionStringProvider; this.serviceProvider = serviceProvider; } @@ -35,9 +35,7 @@ public async Task ExecuteAsync(NextOperationDelegate next, CancellationToken can if (auditEnabled) { - var optionsBuilder = new DbContextOptionsBuilder(); - dbOptionsConfigurator.SetConnectionString(optionsBuilder, connectionStringProvider.ConnectionString); - using var db = (FrameworkDbContext)ActivatorUtilities.CreateInstance(serviceProvider, typeof(FrameworkDbContext), optionsBuilder.Options); + using var db = dbContextActivator.Activate(DbContextOperationEnlistmentStrategy.Suppress); var inputAudit = await AuditInput(db, cancellationToken); diff --git a/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs b/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs index 693e46f..e8834f8 100644 --- a/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs +++ b/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs @@ -22,13 +22,13 @@ public DbContextActivator(IOperationContext operationContext, IConnectionStringP this.serviceProvider = serviceProvider; } - public TDbContext Activate() where TDbContext : DbContext + public TDbContext Activate(DbContextOperationEnlistmentStrategy operationEnlistmentStrategy = DbContextOperationEnlistmentStrategy.Required) where TDbContext : DbContext { - EnsureDbContextCanBeActivated(); + bool? isCommand = GetIsCurrentOperationCommand(operationEnlistmentStrategy == DbContextOperationEnlistmentStrategy.Required); TDbContext db; - if (operationContext.IsCommand) + if (isCommand == true && operationEnlistmentStrategy != DbContextOperationEnlistmentStrategy.Suppress) { var transationState = operationContext.State.Get(); @@ -46,26 +46,46 @@ public TDbContext Activate() where TDbContext : DbContext db = (TDbContext)ActivatorUtilities.CreateInstance(serviceProvider, typeof(TDbContext), optionsBuilder.Options); - db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; - db.SavingChanges += Db_SavingChanges; + if (isCommand == false) + { + db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + db.SavingChanges += Db_SavingChanges; + } } return db; } - private void EnsureDbContextCanBeActivated() + private bool? GetIsCurrentOperationCommand(bool operationRequired) { var operationContextState = operationContext.State.Get(); if(!operationContextState.IsOperationInputSet) { - throw new FrameworkException("DbContext cannot be activated outside of mediator execution context."); + if (operationRequired) + { + throw new FrameworkException("DbContext cannot be activated outside of mediator execution context."); + } + else + { + return null; + } } + var transationState = operationContext.State.Get(); - if(!transationState.IsTransactionInitialized) + if (!transationState.IsTransactionInitialized) { - throw new FrameworkException("DbContext cannot be activated outside of transaction middleware."); + if (operationRequired) + { + throw new FrameworkException("DbContext cannot be activated outside of transaction middleware."); + } + else + { + return null; + } } + + return operationContext.IsCommand; } private void Db_SavingChanges(object sender, SavingChangesEventArgs e) diff --git a/src/AppForeach.Framework.EntityFrameworkCore/DbContextOperationEnlistmentStrategy.cs b/src/AppForeach.Framework.EntityFrameworkCore/DbContextOperationEnlistmentStrategy.cs new file mode 100644 index 0000000..7085880 --- /dev/null +++ b/src/AppForeach.Framework.EntityFrameworkCore/DbContextOperationEnlistmentStrategy.cs @@ -0,0 +1,12 @@ + +namespace AppForeach.Framework.EntityFrameworkCore +{ + public enum DbContextOperationEnlistmentStrategy + { + Required, + + Optional, + + Suppress, + } +} diff --git a/src/AppForeach.Framework.EntityFrameworkCore/IDbContextActivator.cs b/src/AppForeach.Framework.EntityFrameworkCore/IDbContextActivator.cs index 5f2304c..a4066cc 100644 --- a/src/AppForeach.Framework.EntityFrameworkCore/IDbContextActivator.cs +++ b/src/AppForeach.Framework.EntityFrameworkCore/IDbContextActivator.cs @@ -5,6 +5,6 @@ namespace AppForeach.Framework.EntityFrameworkCore { public interface IDbContextActivator { - TDbContext Activate() where TDbContext : DbContext; + TDbContext Activate(DbContextOperationEnlistmentStrategy operationEnlistmentStrategy = DbContextOperationEnlistmentStrategy.Required) where TDbContext : DbContext; } } diff --git a/tests/AppForeach.Framework.EntityFrameworkCore.Tests/AppForeach.Framework.EntityFrameworkCore.Tests.csproj b/tests/AppForeach.Framework.EntityFrameworkCore.Tests/AppForeach.Framework.EntityFrameworkCore.Tests.csproj new file mode 100644 index 0000000..388fe8e --- /dev/null +++ b/tests/AppForeach.Framework.EntityFrameworkCore.Tests/AppForeach.Framework.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,38 @@ + + + + net10.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/AppForeach.Framework.EntityFrameworkCore.Tests/DbContextActivatorTests.cs b/tests/AppForeach.Framework.EntityFrameworkCore.Tests/DbContextActivatorTests.cs new file mode 100644 index 0000000..467dbd9 --- /dev/null +++ b/tests/AppForeach.Framework.EntityFrameworkCore.Tests/DbContextActivatorTests.cs @@ -0,0 +1,235 @@ +using Microsoft.EntityFrameworkCore; +using Moq; +using Shouldly; +using System.Data.Common; + +namespace AppForeach.Framework.EntityFrameworkCore.Tests +{ + public class DbContextActivatorTests : IDisposable + { + private readonly Mock operationContextMock; + private readonly Mock connectionStringProviderMock; + private readonly Mock dbOptionsConfiguratorMock; + private readonly Mock serviceProviderMock; + private readonly DbContextActivator activator; + + private OperationContextState? operationContextState; + private TransactionScopeState? transactionScopeState; + + [Fact] + public async Task Activate_Should_CreateEnlistedDbContext_WhenDefaultStrategyAndTransactionOpened() + { + await OpenTransaction(); + + using var db = activator.Activate(); + + db.Database.CurrentTransaction.ShouldNotBeNull(); + } + + [Fact] + public async Task Activate_Should_CreateIndependentDbContext_WhenDefaultStrategyAndQuery() + { + SetQuery(); + + using var db = activator.Activate(); + + db.Database.CurrentTransaction.ShouldBeNull(); + } + + [Fact] + public async Task Activate_Should_CreateEnlistedDbContext_WhenRequiredStrategyAndTransactionOpened() + { + await OpenTransaction(); + + using var db = activator.Activate(DbContextOperationEnlistmentStrategy.Required); + + db.Database.CurrentTransaction.ShouldNotBeNull(); + } + + [Fact] + public async Task Activate_Should_CreateIndependentDbContext_WhenRequiredStrategyAndQuery() + { + SetQuery(); + + using var db = activator.Activate(DbContextOperationEnlistmentStrategy.Required); + + db.Database.CurrentTransaction.ShouldBeNull(); + } + + [Fact] + public async Task Activate_Should_Throw_WhenRequiredStrategyAndNoOperationContext() + { + operationContextState = null; + + var act = () => activator.Activate(DbContextOperationEnlistmentStrategy.Required); + + act.ShouldThrow(); + } + + [Fact] + public async Task Activate_Should_Throw_WhenRequiredStrategyAndInputNotSet() + { + operationContextState = new OperationContextState + { + IsOperationInputSet = false + }; + + var act = () => activator.Activate(DbContextOperationEnlistmentStrategy.Required); + + act.ShouldThrow(); + } + + [Fact] + public async Task Activate_Should_Throw_WhenRequiredStrategyAndNoTransactionState() + { + transactionScopeState = null; + + var act = () => activator.Activate(DbContextOperationEnlistmentStrategy.Required); + + act.ShouldThrow(); + } + + [Fact] + public async Task Activate_Should_Throw_WhenRequiredStrategyAndTransactionNotSet() + { + transactionScopeState = new TransactionScopeState + { + IsTransactionInitialized = false + }; + + var act = () => activator.Activate(DbContextOperationEnlistmentStrategy.Required); + + act.ShouldThrow(); + } + + [Fact] + public async Task Activate_Should_CreateEnlistedDbContext_WhenOptionalStrategyAndTransactionOpened() + { + await OpenTransaction(); + + using var db = activator.Activate(DbContextOperationEnlistmentStrategy.Optional); + + db.Database.CurrentTransaction.ShouldNotBeNull(); + } + + [Fact] + public async Task Activate_Should_CreateIndependentReadOnlyDbContext_WhenOptionalStrategyAndQuery() + { + SetQuery(); + + using var db = activator.Activate(DbContextOperationEnlistmentStrategy.Optional); + + db.Database.CurrentTransaction.ShouldBeNull(); + + var act = () => db.SaveChangesAsync(); + + await act.ShouldThrowAsync(); + } + + [Fact] + public async Task Activate_Should_CreateIndependentModifiableDbContext_WhenOptionalStrategyAndNoOperation() + { + operationContextState = null; + + using var db = activator.Activate(DbContextOperationEnlistmentStrategy.Optional); + + db.Database.CurrentTransaction.ShouldBeNull(); + + await db.SaveChangesAsync(); + } + + [Fact] + public async Task Activate_Should_CreateIndependentDbContext_WhenSuppressStrategyAndTransaction() + { + await OpenTransaction(); + + using var db = activator.Activate(DbContextOperationEnlistmentStrategy.Suppress); + + db.Database.CurrentTransaction.ShouldBeNull(); + } + + public DbContextActivatorTests() + { + + operationContextState = new OperationContextState + { + IsOperationInputSet = true, + IsCommand = true + }; + + transactionScopeState = new TransactionScopeState + { + IsTransactionInitialized = true + }; + + operationContextMock = new Mock(); + operationContextMock.SetupGet(m => m.State).Returns(() => + { + var state = new Bag(); + + if (operationContextState != null) + state.Set(operationContextState); + if (transactionScopeState != null) + state.Set(transactionScopeState); + + return state; + }); + + operationContextMock.SetupGet(m => m.IsCommand).Returns(() => + { + return operationContextState?.IsCommand ?? false; + }); + + connectionStringProviderMock = new Mock(); + + dbOptionsConfiguratorMock = new Mock(); + dbOptionsConfiguratorMock.Setup(m => m.SetConnectionString(It.IsAny>(), It.IsAny(), + It.IsAny())) + .Callback((DbContextOptionsBuilder dbBuilder, string connection, TransactionRetrySettings settings) => + { + dbBuilder.UseSqlite("DataSource=:memory:"); + }); + + dbOptionsConfiguratorMock.Setup(m => m.SetConnection(It.IsAny>(), It.IsAny())) + .Callback((DbContextOptionsBuilder dbBuilder, DbConnection connection) => + { + dbBuilder.UseSqlite(connection); + }); + + serviceProviderMock = new Mock(); + + activator = new DbContextActivator(operationContextMock.Object, connectionStringProviderMock.Object, dbOptionsConfiguratorMock.Object, serviceProviderMock.Object); + } + + private async Task OpenTransaction() + { + DbContextOptionsBuilder optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite("DataSource=:memory:"); + var db = new FrameworkDbContext(optionsBuilder.Options); + + transactionScopeState!.DbContext = db; + transactionScopeState!.DbContextTransaction = await db.Database.BeginTransactionAsync(); + } + + private void SetQuery() + { + operationContextState!.IsCommand = false; + } + + public void Dispose() + { + if(transactionScopeState != null && transactionScopeState.DbContextTransaction != null) + { + transactionScopeState.DbContextTransaction.Rollback(); + transactionScopeState.DbContextTransaction.Dispose(); + } + } + + private class TestDbContext : DbContext + { + public TestDbContext(DbContextOptions options) : base(options) + { + } + } + } +} From 3ccd45bf4ca2c7daa1f94d101e5071b045fcbc0d Mon Sep 17 00:00:00 2001 From: Viktor Kulkov Date: Mon, 4 May 2026 17:22:38 +0300 Subject: [PATCH 2/2] naming change --- .../DbContextActivator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs b/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs index e8834f8..89b058a 100644 --- a/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs +++ b/src/AppForeach.Framework.EntityFrameworkCore/DbContextActivator.cs @@ -56,12 +56,12 @@ public TDbContext Activate(DbContextOperationEnlistmentStrategy oper return db; } - private bool? GetIsCurrentOperationCommand(bool operationRequired) + private bool? GetIsCurrentOperationCommand(bool isOperationMandatory) { var operationContextState = operationContext.State.Get(); if(!operationContextState.IsOperationInputSet) { - if (operationRequired) + if (isOperationMandatory) { throw new FrameworkException("DbContext cannot be activated outside of mediator execution context."); } @@ -75,7 +75,7 @@ public TDbContext Activate(DbContextOperationEnlistmentStrategy oper var transationState = operationContext.State.Get(); if (!transationState.IsTransactionInitialized) { - if (operationRequired) + if (isOperationMandatory) { throw new FrameworkException("DbContext cannot be activated outside of transaction middleware."); }