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..89b058a 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 isOperationMandatory)
{
var operationContextState = operationContext.State.Get();
if(!operationContextState.IsOperationInputSet)
{
- throw new FrameworkException("DbContext cannot be activated outside of mediator execution context.");
+ if (isOperationMandatory)
+ {
+ 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 (isOperationMandatory)
+ {
+ 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)
+ {
+ }
+ }
+ }
+}