From 2230297a238a099bd029b7b8cc8b71154d4f1fa9 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Thu, 10 Mar 2022 21:25:23 +0000 Subject: [PATCH 01/16] Add first version of the web app that we can use for Dapper instead of EF Core --- Brighter.sln | 93 ++++++++ .../GreetingsEntities/Greeting.cs | 28 +++ .../GreetingsEntities.csproj | 7 + .../WebAPI_Dapper/GreetingsEntities/Person.cs | 32 +++ .../EntityGateway/GreetingsEntityGateway.cs | 9 + .../GreetingsPorts/GreetingsPorts.csproj | 16 ++ .../Handlers/AddGreetingHandlerAsync.cs | 62 ++++++ .../Handlers/AddPersonHandlerAsync.cs | 30 +++ .../Handlers/DeletePersonHandlerAsync.cs | 31 +++ .../FIndGreetingsForPersonHandlerAsync.cs | 37 ++++ .../Handlers/FindPersonByNameHandlerAsync.cs | 28 +++ .../GreetingsPorts/Requests/AddGreeting.cs | 18 ++ .../GreetingsPorts/Requests/AddPerson.cs | 16 ++ .../GreetingsPorts/Requests/DeletePerson.cs | 16 ++ .../Requests/FindGreetingsForPerson.cs | 15 ++ .../Requests/FindPersonByName.cs | 15 ++ .../GreetingsPorts/Requests/GreetingMade.cs | 15 ++ .../Responses/FindPersonResult.cs | 14 ++ .../Responses/FindPersonsGreetings.cs | 22 ++ .../Controllers/GreetingsController.cs | 48 +++++ .../Controllers/PeopleController.cs | 60 ++++++ .../GreetingsWeb/Database/SchemaCreation.cs | 140 ++++++++++++ samples/WebAPI_Dapper/GreetingsWeb/Dockerfile | 10 + .../GreetingsWeb/GreetingsWeb.csproj | 47 ++++ .../Mappers/GreetingMadeMessageMapper.cs | 23 ++ .../GreetingsWeb/Models/NewGreeting.cs | 7 + .../GreetingsWeb/Models/NewPerson.cs | 7 + samples/WebAPI_Dapper/GreetingsWeb/Program.cs | 51 +++++ .../Properties/launchSettings.json | 33 +++ samples/WebAPI_Dapper/GreetingsWeb/Startup.cs | 204 ++++++++++++++++++ .../GreetingsWeb/appsettings.Development.json | 9 + .../GreetingsWeb/appsettings.Production.json | 13 ++ .../GreetingsWeb/appsettings.json | 10 + .../Database/SchemaCreation.cs | 202 +++++++++++++++++ .../SalutationAnalytics/Dockerfile | 7 + .../Mappers/GreetingMadeMessageMapper.cs | 19 ++ .../SalutationReceivedMessageMapper.cs | 22 ++ .../SalutationAnalytics/Program.cs | 137 ++++++++++++ .../Properties/launchSettings.json | 17 ++ .../SalutationAnalytics.csproj | 38 ++++ .../appsettings.Development.json | 9 + .../appsettings.Production.json | 13 ++ .../SalutationAnalytics/appsettings.json | 9 + .../SalutationEntities/Salutation.cs | 21 ++ .../SalutationEntities.csproj | 7 + .../EntityGateway/SalutationsEntityGateway.cs | 9 + .../Handlers/GreetingMadeHandler.cs | 62 ++++++ .../SalutationPorts/Policies/Retry.cs | 27 +++ .../Policies/SalutationPolicy.cs | 18 ++ .../SalutationPorts/Requests/GreetingMade.cs | 15 ++ .../Requests/SalutationReceived.cs | 15 ++ .../SalutationPorts/SalutationPorts.csproj | 16 ++ 52 files changed, 1829 insertions(+) create mode 100644 samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs create mode 100644 samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj create mode 100644 samples/WebAPI_Dapper/GreetingsEntities/Person.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Requests/AddGreeting.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Requests/AddPerson.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Requests/DeletePerson.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Requests/FindGreetingsForPerson.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Requests/FindPersonByName.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Requests/GreetingMade.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonsGreetings.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Controllers/GreetingsController.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Dockerfile create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Mappers/GreetingMadeMessageMapper.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Models/NewGreeting.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Models/NewPerson.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Program.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/Startup.cs create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/appsettings.Development.json create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json create mode 100644 samples/WebAPI_Dapper/GreetingsWeb/appsettings.json create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/Dockerfile create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/Mappers/GreetingMadeMessageMapper.cs create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/Mappers/SalutationReceivedMessageMapper.cs create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/Program.cs create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Development.json create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json create mode 100644 samples/WebAPI_Dapper/SalutationAnalytics/appsettings.json create mode 100644 samples/WebAPI_Dapper/SalutationEntities/Salutation.cs create mode 100644 samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj create mode 100644 samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/Requests/GreetingMade.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/Requests/SalutationReceived.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj diff --git a/Brighter.sln b/Brighter.sln index 75beff5339..c251ae009f 100644 --- a/Brighter.sln +++ b/Brighter.sln @@ -235,6 +235,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutations_MySqlMigrations EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutations_SqliteMigrations", "samples\WebAPI_EFCore\Salutations_SqliteMigrations\Salutations_SqliteMigrations.csproj", "{05647D1B-87A3-4440-B468-82866B206E49}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI_Dapper", "WebAPI_Dapper", "{4D3A09F1-92BB-480D-88F8-D43AC16B03EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GreetingsEntities", "samples\WebAPI_Dapper\GreetingsEntities\GreetingsEntities.csproj", "{C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GreetingsPorts", "samples\WebAPI_Dapper\GreetingsPorts\GreetingsPorts.csproj", "{17C22BAB-65F2-43BF-9438-B759C75947CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GreetingsWeb", "samples\WebAPI_Dapper\GreetingsWeb\GreetingsWeb.csproj", "{CDD81D42-229B-455B-8760-92C76CD3AC96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationEntities", "samples\WebAPI_Dapper\SalutationEntities\SalutationEntities.csproj", "{019F47CB-FA30-4912-9CF0-86C19CA742AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationPorts", "samples\WebAPI_Dapper\SalutationPorts\SalutationPorts.csproj", "{5D1BB2DE-23D6-450B-8E70-56FABABC87D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationAnalytics", "samples\WebAPI_Dapper\SalutationAnalytics\SalutationAnalytics.csproj", "{1A714140-A61A-4702-A358-2E906C9FEE8E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1313,6 +1327,78 @@ Global {05647D1B-87A3-4440-B468-82866B206E49}.Release|Mixed Platforms.Build.0 = Release|Any CPU {05647D1B-87A3-4440-B468-82866B206E49}.Release|x86.ActiveCfg = Release|Any CPU {05647D1B-87A3-4440-B468-82866B206E49}.Release|x86.Build.0 = Release|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Debug|x86.ActiveCfg = Debug|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Debug|x86.Build.0 = Debug|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Release|Any CPU.Build.0 = Release|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Release|x86.ActiveCfg = Release|Any CPU + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9}.Release|x86.Build.0 = Release|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Debug|x86.Build.0 = Debug|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Release|Any CPU.Build.0 = Release|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Release|x86.ActiveCfg = Release|Any CPU + {17C22BAB-65F2-43BF-9438-B759C75947CE}.Release|x86.Build.0 = Release|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Debug|x86.ActiveCfg = Debug|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Debug|x86.Build.0 = Debug|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Release|Any CPU.Build.0 = Release|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Release|x86.ActiveCfg = Release|Any CPU + {CDD81D42-229B-455B-8760-92C76CD3AC96}.Release|x86.Build.0 = Release|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Debug|x86.ActiveCfg = Debug|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Debug|x86.Build.0 = Debug|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Release|Any CPU.Build.0 = Release|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Release|x86.ActiveCfg = Release|Any CPU + {019F47CB-FA30-4912-9CF0-86C19CA742AB}.Release|x86.Build.0 = Release|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Debug|x86.ActiveCfg = Debug|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Debug|x86.Build.0 = Debug|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Release|Any CPU.Build.0 = Release|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Release|x86.ActiveCfg = Release|Any CPU + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2}.Release|x86.Build.0 = Release|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Debug|x86.Build.0 = Debug|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|Any CPU.Build.0 = Release|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|x86.ActiveCfg = Release|Any CPU + {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1385,6 +1471,13 @@ Global {F701369D-EDA3-4407-8655-6B81DD6EBCBA} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994} {82E64F30-8D74-4E01-A974-5A78EBAD916C} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994} {05647D1B-87A3-4440-B468-82866B206E49} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994} + {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} = {235DE1F1-E71B-4817-8E27-3B34FF006E4C} + {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} + {17C22BAB-65F2-43BF-9438-B759C75947CE} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} + {CDD81D42-229B-455B-8760-92C76CD3AC96} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} + {019F47CB-FA30-4912-9CF0-86C19CA742AB} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} + {5D1BB2DE-23D6-450B-8E70-56FABABC87D2} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} + {1A714140-A61A-4702-A358-2E906C9FEE8E} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B7C7E31-2E32-4E0D-9426-BC9AF22E9F4C} diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs new file mode 100644 index 0000000000..44100d5920 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs @@ -0,0 +1,28 @@ +using System; + +namespace GreetingsEntities +{ + public class Greeting + { + private int _id; + public string Message { get; set; } + public Person Recipient { get; set; } + + public Greeting(string message) + { + Message = message; + } + + public Greeting(int id, string message, Person recipient) + { + _id = id; + Message = message; + Recipient = recipient; + } + + public string Greet() + { + return $"{Message} {Recipient.Name}!"; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj b/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj new file mode 100644 index 0000000000..cbfa58153a --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs new file mode 100644 index 0000000000..53c8ce6e64 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; + +namespace GreetingsEntities +{ + public class Person + { + private int _id; + private readonly List _greetings = new List(); + public byte[] TimeStamp { get; set; } + public string Name { get; } + public IReadOnlyList Greetings => _greetings; + + public Person(string name) + { + Name = name; + } + + public Person(int id, string name) + { + _id = id; + Name = name; + } + + public void AddGreeting(Greeting greeting) + { + greeting.Recipient = this; + _greetings.Add(greeting); + } + + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs new file mode 100644 index 0000000000..a7063b3c22 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs @@ -0,0 +1,9 @@ +using GreetingsEntities; + +namespace GreetingsPorts.EntityGateway +{ + public class GreetingsEntityGateway + { + + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj new file mode 100644 index 0000000000..bbdad72230 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + + + + + + + + + + + + diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs new file mode 100644 index 0000000000..26d8264180 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using GreetingsEntities; +using GreetingsPorts.EntityGateway; +using GreetingsPorts.Requests; +using Paramore.Brighter; + +namespace GreetingsPorts.Handlers +{ + public class AddGreetingHandlerAsync: RequestHandlerAsync + { + private readonly GreetingsEntityGateway _uow; + private readonly IAmACommandProcessor _postBox; + + public AddGreetingHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor postBox) + { + _uow = uow; + _postBox = postBox; + + } + + public override async Task HandleAsync(AddGreeting addGreeting, CancellationToken cancellationToken = default(CancellationToken)) + { + var posts = new List(); + + //We span a Db outside of EF's control, so start an explicit transactional scope + //var tx = await _uow.Database.BeginTransactionAsync(cancellationToken); + try + { + //var person = await _uow.People + // .Where(p => p.Name == addGreeting.Name) + // .SingleAsync(cancellationToken); + + var greeting = new Greeting(addGreeting.Greeting); + + //person.AddGreeting(greeting); + + //Now write the message we want to send to the Db in the same transaction. + posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken)); + + //write the changed entity to the Db + //await _uow.SaveChangesAsync(cancellationToken); + + //write new person and the associated message to the Db + //await tx.CommitAsync(cancellationToken); + } + catch (Exception) + { + //it went wrong, rollback the entity change and the downstream message + //await tx.RollbackAsync(cancellationToken); + } + + //Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones. + //Alternatively, you can let the Sweeper do this, but at the cost of increased latency + await _postBox.ClearOutboxAsync(posts, cancellationToken:cancellationToken); + + return await base.HandleAsync(addGreeting, cancellationToken); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs new file mode 100644 index 0000000000..9faf9ca0f8 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using GreetingsEntities; +using GreetingsPorts.EntityGateway; +using GreetingsPorts.Requests; +using Paramore.Brighter; + +namespace GreetingsPorts.Handlers +{ + public class AddPersonHandlerAsync : RequestHandlerAsync + { + private readonly GreetingsEntityGateway _uow; + private readonly IAmACommandProcessor _postBox; + + public AddPersonHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor postBox) + { + _uow = uow; + _postBox = postBox; + } + + public override async Task HandleAsync(AddPerson addPerson, CancellationToken cancellationToken = default(CancellationToken)) + { + //_uow.Add(new Person(addPerson.Name)); + + //await _uow.SaveChangesAsync(cancellationToken); + + return await base.HandleAsync(addPerson, cancellationToken); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs new file mode 100644 index 0000000000..b292fb9fc0 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs @@ -0,0 +1,31 @@ +using System.Threading; +using System.Threading.Tasks; +using GreetingsPorts.EntityGateway; +using GreetingsPorts.Requests; +using Paramore.Brighter; + +namespace GreetingsPorts.Handlers +{ + public class DeletePersonHandlerAsync : RequestHandlerAsync + { + private readonly GreetingsEntityGateway _uow; + + public DeletePersonHandlerAsync(GreetingsEntityGateway uow) + { + _uow = uow; + } + public async override Task HandleAsync(DeletePerson deletePerson, CancellationToken cancellationToken = default(CancellationToken)) + { + //var person = await _uow.People + // .Include(p => p.Greetings) + // .Where(p => p.Name == deletePerson.Name) + // .SingleAsync(cancellationToken); + + //_uow.Remove(person); + + // await _uow.SaveChangesAsync(cancellationToken); + + return await base.HandleAsync(deletePerson, cancellationToken); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs new file mode 100644 index 0000000000..b3dbf78ccf --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs @@ -0,0 +1,37 @@ +using System.Threading; +using System.Threading.Tasks; +using GreetingsPorts.EntityGateway; +using GreetingsPorts.Requests; +using GreetingsPorts.Responses; +using Paramore.Darker; + +namespace GreetingsPorts.Handlers +{ + public class FIndGreetingsForPersonHandlerAsync : QueryHandlerAsync + { + private readonly GreetingsEntityGateway _uow; + + public FIndGreetingsForPersonHandlerAsync(GreetingsEntityGateway uow) + { + _uow = uow; + } + + public override async Task ExecuteAsync(FindGreetingsForPerson query, CancellationToken cancellationToken = new CancellationToken()) + { + //var person = await _uow.People + // .Include(p => p.Greetings) + // .Where(p => p.Name == query.Name) + // .SingleAsync(cancellationToken); + + //if (person == null) return null; + + return new FindPersonsGreetings + { + //Name = person.Name, + //Greetings = person.Greetings.Select(g => new Salutation(g.Greet())) + }; + + } + + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs new file mode 100644 index 0000000000..9b020c0043 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs @@ -0,0 +1,28 @@ +using System.Threading; +using System.Threading.Tasks; +using GreetingsPorts.EntityGateway; +using GreetingsPorts.Requests; +using GreetingsPorts.Responses; +using Paramore.Darker; + +namespace GreetingsPorts.Handlers +{ + public class FindPersonByNameHandlerAsync : QueryHandlerAsync + { + private readonly GreetingsEntityGateway _uow; + + public FindPersonByNameHandlerAsync(GreetingsEntityGateway uow) + { + _uow = uow; + } + + public override async Task ExecuteAsync(FindPersonByName query, CancellationToken cancellationToken = new CancellationToken()) + { + //return await _uow.People + // .Where(p => p.Name == query.Name) + // .Select(p => new FindPersonResult(p)) + // .SingleAsync(cancellationToken); + return null; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Requests/AddGreeting.cs b/samples/WebAPI_Dapper/GreetingsPorts/Requests/AddGreeting.cs new file mode 100644 index 0000000000..0e10dc0697 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Requests/AddGreeting.cs @@ -0,0 +1,18 @@ +using System; +using Paramore.Brighter; + +namespace GreetingsPorts.Requests +{ + public class AddGreeting : Command + { + public string Name { get; } + public string Greeting { get; } + + public AddGreeting(string name, string greeting) + : base(Guid.NewGuid()) + { + Name = name; + Greeting = greeting; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Requests/AddPerson.cs b/samples/WebAPI_Dapper/GreetingsPorts/Requests/AddPerson.cs new file mode 100644 index 0000000000..2e86c6d7f3 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Requests/AddPerson.cs @@ -0,0 +1,16 @@ +using System; +using Paramore.Brighter; + +namespace GreetingsPorts.Requests +{ + public class AddPerson : Command + { + public string Name { get; set; } + + public AddPerson(string name) + : base(Guid.NewGuid()) + { + Name = name; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Requests/DeletePerson.cs b/samples/WebAPI_Dapper/GreetingsPorts/Requests/DeletePerson.cs new file mode 100644 index 0000000000..d09f213ba9 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Requests/DeletePerson.cs @@ -0,0 +1,16 @@ +using System; +using Paramore.Brighter; + +namespace GreetingsPorts.Requests +{ + public class DeletePerson : Command + { + public string Name { get; } + + public DeletePerson(string name) + : base(Guid.NewGuid()) + { + Name = name; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Requests/FindGreetingsForPerson.cs b/samples/WebAPI_Dapper/GreetingsPorts/Requests/FindGreetingsForPerson.cs new file mode 100644 index 0000000000..b7ef165f22 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Requests/FindGreetingsForPerson.cs @@ -0,0 +1,15 @@ +using GreetingsPorts.Responses; +using Paramore.Darker; + +namespace GreetingsPorts.Requests +{ + public class FindGreetingsForPerson : IQuery + { + public string Name { get; } + + public FindGreetingsForPerson(string name) + { + Name = name; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Requests/FindPersonByName.cs b/samples/WebAPI_Dapper/GreetingsPorts/Requests/FindPersonByName.cs new file mode 100644 index 0000000000..bc571978c0 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Requests/FindPersonByName.cs @@ -0,0 +1,15 @@ +using GreetingsPorts.Responses; +using Paramore.Darker; + +namespace GreetingsPorts.Requests +{ + public class FindPersonByName : IQuery + { + public string Name { get; } + + public FindPersonByName(string name) + { + Name = name; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Requests/GreetingMade.cs b/samples/WebAPI_Dapper/GreetingsPorts/Requests/GreetingMade.cs new file mode 100644 index 0000000000..4f8ea156a2 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Requests/GreetingMade.cs @@ -0,0 +1,15 @@ +using System; +using Paramore.Brighter; + +namespace GreetingsPorts.Requests +{ + public class GreetingMade : Event + { + public string Greeting { get; set; } + + public GreetingMade(string greeting) : base(Guid.NewGuid()) + { + Greeting = greeting; + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs b/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs new file mode 100644 index 0000000000..ea50242c8c --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs @@ -0,0 +1,14 @@ +using GreetingsEntities; + +namespace GreetingsPorts.Responses +{ + public class FindPersonResult + { + public string Name { get; private set; } + public FindPersonResult(Person person) + { + Name = person.Name; + } + + } +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonsGreetings.cs b/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonsGreetings.cs new file mode 100644 index 0000000000..6ab530b4ba --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonsGreetings.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace GreetingsPorts.Responses +{ + public class FindPersonsGreetings + { + public string Name { get; set; } + public IEnumerable Greetings { get;set; } + + } + + public class Salutation + { + public string Words { get; set; } + + public Salutation(string words) + { + Words = words; + } + + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Controllers/GreetingsController.cs b/samples/WebAPI_Dapper/GreetingsWeb/Controllers/GreetingsController.cs new file mode 100644 index 0000000000..47e2f6f9fb --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Controllers/GreetingsController.cs @@ -0,0 +1,48 @@ +using System.Threading.Tasks; +using GreetingsPorts.Requests; +using GreetingsPorts.Responses; +using Greetingsweb.Models; +using Microsoft.AspNetCore.Mvc; +using Paramore.Brighter; +using Paramore.Darker; + +namespace Greetingsweb.Controllers +{ + [ApiController] + [Route("[controller]")] + public class GreetingsController : Controller + { + private readonly IAmACommandProcessor _commandProcessor; + private readonly IQueryProcessor _queryProcessor; + + public GreetingsController(IAmACommandProcessor commandProcessor, IQueryProcessor queryProcessor) + { + _commandProcessor = commandProcessor; + _queryProcessor = queryProcessor; + } + + [Route("{name}")] + [HttpGet] + public async Task Get(string name) + { + var personsGreetings = await _queryProcessor.ExecuteAsync(new FindGreetingsForPerson(name)); + + if (personsGreetings == null) return new NotFoundResult(); + + return Ok(personsGreetings); + } + + [Route("{name}/new")] + [HttpPost] + public async Task> Post(string name, NewGreeting newGreeting) + { + await _commandProcessor.SendAsync(new AddGreeting(name, newGreeting.Greeting)); + + var personsGreetings = await _queryProcessor.ExecuteAsync(new FindGreetingsForPerson(name)); + + if (personsGreetings == null) return new NotFoundResult(); + + return Ok(personsGreetings); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs b/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs new file mode 100644 index 0000000000..67f82de26a --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using GreetingsPorts.Requests; +using GreetingsPorts.Responses; +using Greetingsweb.Models; +using Microsoft.AspNetCore.Mvc; +using Paramore.Brighter; +using Paramore.Darker; + +namespace Greetingsweb.Controllers +{ + [ApiController] + [Route("[controller]")] + public class PeopleController : Controller + { + private readonly IAmACommandProcessor _commandProcessor; + private readonly IQueryProcessor _queryProcessor; + + public PeopleController(IAmACommandProcessor commandProcessor, IQueryProcessor queryProcessor) + { + _commandProcessor = commandProcessor; + _queryProcessor = queryProcessor; + } + + + [Route("{name}")] + [HttpGet] + public async Task> Get(string name) + { + var foundPerson = await _queryProcessor.ExecuteAsync(new FindPersonByName(name)); + + if (foundPerson == null) return new NotFoundResult(); + + return Ok(foundPerson); + } + + [Route("{name}")] + [HttpDelete] + public async Task Delete(string name) + { + await _commandProcessor.SendAsync(new DeletePerson(name)); + + return Ok(); + } + + [Route("new")] + [HttpPost] + public async Task> Post(NewPerson newPerson) + { + await _commandProcessor.SendAsync(new AddPerson(newPerson.Name)); + + var addedPeson = await _queryProcessor.ExecuteAsync(new FindPersonByName(newPerson.Name)); + + if (addedPeson == null) return new NotFoundResult(); + + return Ok(addedPeson); + } + + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs new file mode 100644 index 0000000000..63ba8a8721 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs @@ -0,0 +1,140 @@ +using System; +using System.Data; +using GreetingsPorts.EntityGateway; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using MySqlConnector; +using Paramore.Brighter; +using Paramore.Brighter.Outbox.MySql; +using Paramore.Brighter.Outbox.Sqlite; +using Polly; + +namespace Greetingsweb.Database +{ + public static class SchemaCreation + { + private const string OUTBOX_TABLE_NAME = "Outbox"; + + public static IHost CheckDbIsUp(this IHost webHost) + { + using var scope = webHost.Services.CreateScope() ; + + var services = scope.ServiceProvider; + var env = services.GetService(); + var config = services.GetService(); + string connectionString = DbServerConnectionString(config, env); + + //We don't check in development as using Sqlite + if (env.IsDevelopment()) return webHost; + + WaitToConnect(connectionString); + CreateDatabaseIfNotExists(connectionString); + + return webHost; + } + + private static void CreateDatabaseIfNotExists(string connectionString) + { + //The migration does not create the Db, so we need to create it sot that it will add it + using var conn = new MySqlConnection(connectionString); + conn.Open(); + using var command = conn.CreateCommand(); + command.CommandText = "CREATE DATABASE IF NOT EXISTS Greetings"; + command.ExecuteScalar(); + } + + private static void WaitToConnect(string connectionString) + { + var policy = Policy.Handle().WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(2), + (exception, timespan) => { Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); }); + + policy.Execute(() => + { + using var conn = new MySqlConnection(connectionString); + conn.Open(); + }); + } + + + public static IHost CreateOutbox(this IHost webHost) + { + using (var scope = webHost.Services.CreateScope()) + { + var services = scope.ServiceProvider; + var env = services.GetService(); + var config = services.GetService(); + + CreateOutbox(config, env); + } + + return webHost; + } + + private static void CreateOutbox(IConfiguration config, IWebHostEnvironment env) + { + try + { + var connectionString = DbConnectionString(config, env); + + if (env.IsDevelopment()) + CreateOutboxDevelopment(connectionString); + else + CreateOutboxProduction(connectionString); + } + catch (System.Exception e) + { + Console.WriteLine($"Issue with creating Outbox table, {e.Message}"); + //Rethrow, if we can't create the Outbox, shut down + throw; + } + } + + private static void CreateOutboxDevelopment(string connectionString) + { + using var sqlConnection = new SqliteConnection(connectionString); + sqlConnection.Open(); + + using var exists = sqlConnection.CreateCommand(); + exists.CommandText = SqliteOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME); + using var reader = exists.ExecuteReader(CommandBehavior.SingleRow); + + if (reader.HasRows) return; + + using var command = sqlConnection.CreateCommand(); + command.CommandText = SqliteOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME); + command.ExecuteScalar(); + } + + private static void CreateOutboxProduction(string connectionString) + { + using var sqlConnection = new MySqlConnection(connectionString); + sqlConnection.Open(); + + using var existsQuery = sqlConnection.CreateCommand(); + existsQuery.CommandText = MySqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME); + bool exists = existsQuery.ExecuteScalar() != null; + + if (exists) return; + + using var command = sqlConnection.CreateCommand(); + command.CommandText = MySqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME); + command.ExecuteScalar(); + } + + private static string DbConnectionString(IConfiguration config, IWebHostEnvironment env) + { + //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities + return env.IsDevelopment() ? "Filename=Greetings.db;Cache=Shared" : config.GetConnectionString("Greetings"); + } + + private static string DbServerConnectionString(IConfiguration config, IWebHostEnvironment env) + { + return env.IsDevelopment() ? "Filename=Greetings.db;Cache=Shared" : config.GetConnectionString("GreetingsDb"); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile b/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile new file mode 100644 index 0000000000..7e3b0f269e --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile @@ -0,0 +1,10 @@ +FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim + +WORKDIR /app +COPY out/ . + +# Expose the port +EXPOSE 5000 +ENV ASPNETCORE_URLS=http://+:5000 +#run the site +ENTRYPOINT ["dotnet", "GreetingsAdapters.dll"] diff --git a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj new file mode 100644 index 0000000000..74a6e0de28 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj @@ -0,0 +1,47 @@ + + + + net5.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ContentIncludedByDefault Remove="out\web.config" /> + <_ContentIncludedByDefault Remove="out\appsettings.Development.json" /> + <_ContentIncludedByDefault Remove="out\appsettings.json" /> + <_ContentIncludedByDefault Remove="out\appsettings.Production.json" /> + <_ContentIncludedByDefault Remove="out\GreetingsAdapters.deps.json" /> + <_ContentIncludedByDefault Remove="out\GreetingsAdapters.runtimeconfig.json" /> + + + diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Mappers/GreetingMadeMessageMapper.cs b/samples/WebAPI_Dapper/GreetingsWeb/Mappers/GreetingMadeMessageMapper.cs new file mode 100644 index 0000000000..3a25ba6c9b --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Mappers/GreetingMadeMessageMapper.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using GreetingsPorts.Requests; +using Paramore.Brighter; + + +namespace Greetingsweb.Mappers +{ + public class GreetingMadeMessageMapper : IAmAMessageMapper + { + public Message MapToMessage(GreetingMade request) + { + var header = new MessageHeader(messageId: request.Id, topic: "GreetingMade", messageType: MessageType.MT_EVENT); + var body = new MessageBody(System.Text.Json.JsonSerializer.Serialize(request, new JsonSerializerOptions(JsonSerializerDefaults.General))); + var message = new Message(header, body); + return message; + } + + public GreetingMade MapToRequest(Message message) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Models/NewGreeting.cs b/samples/WebAPI_Dapper/GreetingsWeb/Models/NewGreeting.cs new file mode 100644 index 0000000000..d54591e75c --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Models/NewGreeting.cs @@ -0,0 +1,7 @@ +namespace Greetingsweb.Models +{ + public class NewGreeting + { + public string Greeting { get; set; } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Models/NewPerson.cs b/samples/WebAPI_Dapper/GreetingsWeb/Models/NewPerson.cs new file mode 100644 index 0000000000..d210a65341 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Models/NewPerson.cs @@ -0,0 +1,7 @@ +namespace Greetingsweb.Models +{ + public class NewPerson + { + public string Name { get; set; } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Program.cs b/samples/WebAPI_Dapper/GreetingsWeb/Program.cs new file mode 100644 index 0000000000..985f34fd8e --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Program.cs @@ -0,0 +1,51 @@ +using System.IO; +using Greetingsweb.Database; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Greetingsweb +{ + public class Program + { + public static void Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + + host.CheckDbIsUp(); + host.CreateOutbox(); + + host.Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, configBuilder) => + { + var env = context.HostingEnvironment; + configBuilder.AddJsonFile("appsettings.json", optional: false); + configBuilder.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); + configBuilder.AddEnvironmentVariables(prefix:"BRIGHTER_"); + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseKestrel(); + webBuilder.UseContentRoot(Directory.GetCurrentDirectory()); + webBuilder.CaptureStartupErrors(true); + webBuilder.UseSetting("detailedErrors", "true"); + webBuilder.ConfigureLogging((hostingContext, logging) => + { + logging.AddConsole(); + logging.AddDebug(); + }); + webBuilder.UseDefaultServiceProvider((context, options) => + { + var isDevelopment = context.HostingEnvironment.IsDevelopment(); + options.ValidateScopes = isDevelopment; + options.ValidateOnBuild = isDevelopment; + }); + webBuilder.UseStartup(); + }); + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json b/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json new file mode 100644 index 0000000000..f744143d72 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:8854", + "sslPort": 44355 + } + }, + "profiles": { + "Development": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Production": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Production" + } + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs new file mode 100644 index 0000000000..36beff0dbd --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs @@ -0,0 +1,204 @@ +using System; +using System.Data; +using System.Data.Common; +using GreetingsPorts.EntityGateway; +using GreetingsPorts.Handlers; +using Hellang.Middleware.ProblemDetails; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; +using MySqlConnector; +using Paramore.Brighter; +using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.Extensions.Hosting; +using Paramore.Brighter.MessagingGateway.RMQ; +using Paramore.Brighter.MySql; +using Paramore.Brighter.Outbox.MySql; +using Paramore.Brighter.Outbox.Sqlite; +using Paramore.Brighter.Sqlite; +using Paramore.Darker.AspNetCore; +using Polly; +using Polly.Registry; + +namespace Greetingsweb +{ + public class Startup + { + private const string _outBoxTableName = "Outbox"; + private IWebHostEnvironment _env; + + public Startup(IConfiguration configuration, IWebHostEnvironment env) + { + Configuration = configuration; + _env = env; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseProblemDetails(); + + if (env.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GreetingsAPI v1")); + } + + app.UseHttpsRedirection(); + app.UseRouting(); + + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + } + + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvcCore().AddApiExplorer(); + services.AddControllers(options => + { + options.RespectBrowserAcceptHeader = true; + }) + .AddXmlSerializerFormatters(); + services.AddProblemDetails(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "GreetingsAPI", Version = "v1" }); + }); + + ConfigureBrighter(services); + ConfigureDarker(services); + } + + private void CheckDbIsUp() + { + string connectionString = DbConnectionString(); + + var policy = Policy.Handle().WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(2), + (exception, timespan) => + { + Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); + }); + + policy.Execute(() => + { + //don't check this for SQlite in development + if (!_env.IsDevelopment()) + { + using (var conn = new MySqlConnection(connectionString)) + { + conn.Open(); + } + } + }); + } + + private void ConfigureBrighter(IServiceCollection services) + { + var retryPolicy = Policy.Handle() + .WaitAndRetry(new[] { TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150) }); + var circuitBreakerPolicy = Policy.Handle().CircuitBreaker(1, TimeSpan.FromMilliseconds(500)); + var retryPolicyAsync = Policy.Handle() + .WaitAndRetryAsync(new[] { TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150) }); + var circuitBreakerPolicyAsync = Policy.Handle().CircuitBreakerAsync(1, TimeSpan.FromMilliseconds(500)); + var policyRegistry = new PolicyRegistry() + { + { CommandProcessor.RETRYPOLICY, retryPolicy }, + { CommandProcessor.CIRCUITBREAKER, circuitBreakerPolicy }, + { CommandProcessor.RETRYPOLICYASYNC, retryPolicyAsync }, + { CommandProcessor.CIRCUITBREAKERASYNC, circuitBreakerPolicyAsync } + }; + + if (_env.IsDevelopment()) + { + services.AddBrighter(options => + { + //we want to use scoped, so make sure everything understands that which needs to + options.HandlerLifetime = ServiceLifetime.Scoped; + options.CommandProcessorLifetime = ServiceLifetime.Scoped; + options.MapperLifetime = ServiceLifetime.Singleton; + options.PolicyRegistry = policyRegistry; + }) + .UseExternalBus(new RmqProducerRegistryFactory( + new RmqMessagingGatewayConnection + { + AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")), + Exchange = new Exchange("paramore.brighter.exchange"), + }, + new RmqPublication[]{ + new RmqPublication + { + Topic = new RoutingKey("GreetingMade"), + MaxOutStandingMessages = 5, + MaxOutStandingCheckIntervalMilliSeconds = 500, + WaitForConfirmsTimeOutInMilliseconds = 1000, + MakeChannels = OnMissingChannel.Create + }} + ).Create() + ) + .UseSqliteOutbox(new SqliteConfiguration(DbConnectionString(), _outBoxTableName), typeof(SqliteConnectionProvider), ServiceLifetime.Singleton) + //.UseSqliteTransactionConnectionProvider(typeof(SqliteEntityFrameworkConnectionProvider), ServiceLifetime.Scoped) + .UseOutboxSweeper(options => + { + options.TimerInterval = 5; + options.MinimumMessageAge = 5000; + }) + .AutoFromAssemblies(); + } + else + { + services.AddBrighter(options => + { + options.HandlerLifetime = ServiceLifetime.Scoped; + options.MapperLifetime = ServiceLifetime.Singleton; + options.PolicyRegistry = policyRegistry; + }) + .UseExternalBus(new RmqProducerRegistryFactory( + new RmqMessagingGatewayConnection + { + AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@rabbitmq:5672")), + Exchange = new Exchange("paramore.brighter.exchange"), + }, + new RmqPublication[] { + new RmqPublication + { + Topic = new RoutingKey("GreetingMade"), + MaxOutStandingMessages = 5, + MaxOutStandingCheckIntervalMilliSeconds = 500, + WaitForConfirmsTimeOutInMilliseconds = 1000, + MakeChannels = OnMissingChannel.Create + }} + ).Create() + ) + .UseMySqlOutbox(new MySqlConfiguration(DbConnectionString(), _outBoxTableName), typeof(MySqlConnectionProvider), ServiceLifetime.Singleton) + //.UseMySqTransactionConnectionProvider(typeof(MySqlEntityFrameworkConnectionProvider), ServiceLifetime.Scoped) + .UseOutboxSweeper() + .AutoFromAssemblies(); + } + + } + + private void ConfigureDarker(IServiceCollection services) + { + services.AddDarker(options => + { + options.HandlerLifetime = ServiceLifetime.Scoped; + options.QueryProcessorLifetime = ServiceLifetime.Scoped; + }) + .AddHandlersFromAssemblies(typeof(FindPersonByNameHandlerAsync).Assembly); + } + + private string DbConnectionString() + { + //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities + return _env.IsDevelopment() ? "Filename=Greetings.db;Cache=Shared" : Configuration.GetConnectionString("Greetings"); + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Development.json b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Development.json new file mode 100644 index 0000000000..1d7a4e1ad3 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json new file mode 100644 index 0000000000..1091c4e249 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" + } + }, + "ConnectionStrings": { + "Greetings": "server=localhost; port=3306; uid=root; pwd=root; database=Greetings", + "GreetingsDb": "server=localhost; port=3306; uid=root; pwd=root" + } +} \ No newline at end of file diff --git a/samples/WebAPI_Dapper/GreetingsWeb/appsettings.json b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.json new file mode 100644 index 0000000000..6b9d1b5c56 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs new file mode 100644 index 0000000000..1de49d2ca9 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs @@ -0,0 +1,202 @@ +using System; +using System.Data; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using MySqlConnector; +using Paramore.Brighter.Inbox.MySql; +using Paramore.Brighter.Inbox.Sqlite; +using Paramore.Brighter.Outbox.MySql; +using Paramore.Brighter.Outbox.Sqlite; +using Polly; + +namespace SalutationAnalytics.Database +{ + public static class SchemaCreation + { + internal const string INBOX_TABLE_NAME = "Inbox"; + internal const string OUTBOX_TABLE_NAME = "Outbox"; + + public static IHost CheckDbIsUp(this IHost host) + { + using var scope = host.Services.CreateScope() ; + + var services = scope.ServiceProvider; + var env = services.GetService(); + var config = services.GetService(); + string connectionString = DbServerConnectionString(config, env); + + //We don't check in development as using Sqlite + if (env.IsDevelopment()) return host; + + WaitToConnect(connectionString); + CreateDatabaseIfNotExists(connectionString); + + return host; + } + + private static void CreateDatabaseIfNotExists(string connectionString) + { + //The migration does not create the Db, so we need to create it sot that it will add it + using var conn = new MySqlConnection(connectionString); + conn.Open(); + using var command = conn.CreateCommand(); + command.CommandText = "CREATE DATABASE IF NOT EXISTS Salutations"; + command.ExecuteScalar(); + } + + private static void WaitToConnect(string connectionString) + { + var policy = Policy.Handle().WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(2), + (exception, timespan) => { Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); }); + + policy.Execute(() => + { + using var conn = new MySqlConnection(connectionString); + conn.Open(); + }); + } + + + public static IHost CreateInbox(this IHost host) + { + using (var scope = host.Services.CreateScope()) + { + var services = scope.ServiceProvider; + var env = services.GetService(); + var config = services.GetService(); + + CreateInbox(config, env); + } + + return host; + } + + private static void CreateInbox(IConfiguration config, IHostEnvironment env) + { + try + { + var connectionString = DbConnectionString(config, env); + + if (env.IsDevelopment()) + CreateInboxDevelopment(connectionString); + else + CreateInboxProduction(connectionString); + } + catch (System.Exception e) + { + Console.WriteLine($"Issue with creating Inbox table, {e.Message}"); + throw; + } + } + + private static void CreateInboxDevelopment(string connectionString) + { + using var sqlConnection = new SqliteConnection(connectionString); + sqlConnection.Open(); + + using var exists = sqlConnection.CreateCommand(); + exists.CommandText = SqliteInboxBuilder.GetExistsQuery(INBOX_TABLE_NAME); + using var reader = exists.ExecuteReader(CommandBehavior.SingleRow); + + if (reader.HasRows) return; + + using var command = sqlConnection.CreateCommand(); + command.CommandText = SqliteInboxBuilder.GetDDL(INBOX_TABLE_NAME); + command.ExecuteScalar(); + } + + private static void CreateInboxProduction(string connectionString) + { + using var sqlConnection = new MySqlConnection(connectionString); + sqlConnection.Open(); + + using var existsQuery = sqlConnection.CreateCommand(); + existsQuery.CommandText = MySqlInboxBuilder.GetExistsQuery(INBOX_TABLE_NAME); + bool exists = existsQuery.ExecuteScalar() != null; + + if (exists) return; + + using var command = sqlConnection.CreateCommand(); + command.CommandText = MySqlInboxBuilder.GetDDL(INBOX_TABLE_NAME); + command.ExecuteScalar(); + } + + public static IHost CreateOutbox(this IHost webHost) + { + using (var scope = webHost.Services.CreateScope()) + { + var services = scope.ServiceProvider; + var env = services.GetService(); + var config = services.GetService(); + + CreateOutbox(config, env); + } + + return webHost; + } + + private static void CreateOutbox(IConfiguration config, IHostEnvironment env) + { + try + { + var connectionString = DbConnectionString(config, env); + + if (env.IsDevelopment()) + CreateOutboxDevelopment(connectionString); + else + CreateOutboxProduction(connectionString); + } + catch (System.Exception e) + { + Console.WriteLine($"Issue with creating Outbox table, {e.Message}"); + throw; + } + } + + private static void CreateOutboxDevelopment(string connectionString) + { + using var sqlConnection = new SqliteConnection(connectionString); + sqlConnection.Open(); + + using var exists = sqlConnection.CreateCommand(); + exists.CommandText = SqliteOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME); + using var reader = exists.ExecuteReader(CommandBehavior.SingleRow); + + if (reader.HasRows) return; + + using var command = sqlConnection.CreateCommand(); + command.CommandText = SqliteOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME); + command.ExecuteScalar(); + } + + private static void CreateOutboxProduction(string connectionString) + { + using var sqlConnection = new MySqlConnection(connectionString); + sqlConnection.Open(); + + using var existsQuery = sqlConnection.CreateCommand(); + existsQuery.CommandText = MySqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME); + bool exists = existsQuery.ExecuteScalar() != null; + + if (exists) return; + + using var command = sqlConnection.CreateCommand(); + command.CommandText = MySqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME); + command.ExecuteScalar(); + } + + private static string DbConnectionString(IConfiguration config, IHostEnvironment env) + { + //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities + return env.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : config.GetConnectionString("Salutations"); + } + + private static string DbServerConnectionString(IConfiguration config, IHostEnvironment env) + { + return env.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : config.GetConnectionString("SalutationsDb"); + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Dockerfile b/samples/WebAPI_Dapper/SalutationAnalytics/Dockerfile new file mode 100644 index 0000000000..dfaa24d0b8 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Dockerfile @@ -0,0 +1,7 @@ +FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim + +WORKDIR /app +COPY out/ . + +#run the site +ENTRYPOINT ["dotnet", "GreetingsWatcher.dll"] diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Mappers/GreetingMadeMessageMapper.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Mappers/GreetingMadeMessageMapper.cs new file mode 100644 index 0000000000..f96c47a9c7 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Mappers/GreetingMadeMessageMapper.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using Paramore.Brighter; +using SalutationPorts.Requests; + +namespace SalutationAnalytics.Mappers +{ + public class GreetingMadeMessageMapper : IAmAMessageMapper + { + public Message MapToMessage(GreetingMade request) + { + throw new System.NotImplementedException(); + } + + public GreetingMade MapToRequest(Message message) + { + return JsonSerializer.Deserialize(message.Body.Value, JsonSerialisationOptions.Options); + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Mappers/SalutationReceivedMessageMapper.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Mappers/SalutationReceivedMessageMapper.cs new file mode 100644 index 0000000000..bdbb2c1061 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Mappers/SalutationReceivedMessageMapper.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using Paramore.Brighter; +using SalutationPorts.Requests; + +namespace SalutationAnalytics.Mappers +{ + public class SalutationReceivedMessageMapper : IAmAMessageMapper + { + public Message MapToMessage(SalutationReceived request) + { + var header = new MessageHeader(messageId: request.Id, topic: "SalutationReceived", messageType: MessageType.MT_EVENT); + var body = new MessageBody(System.Text.Json.JsonSerializer.Serialize(request, new JsonSerializerOptions(JsonSerializerDefaults.General))); + var message = new Message(header, body); + return message; + } + + public SalutationReceived MapToRequest(Message message) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs new file mode 100644 index 0000000000..31c5b68f03 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs @@ -0,0 +1,137 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Paramore.Brighter; +using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.Inbox; +using Paramore.Brighter.Inbox.MySql; +using Paramore.Brighter.Inbox.Sqlite; +using Paramore.Brighter.MessagingGateway.RMQ; +using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; +using Paramore.Brighter.ServiceActivator.Extensions.Hosting; +using SalutationAnalytics.Database; +using SalutationPorts.Policies; +using SalutationPorts.Requests; + +namespace SalutationAnalytics +{ + class Program + { + public static async Task Main(string[] args) + { + var host = CreateHostBuilder(args).Build(); + host.CheckDbIsUp(); + host.CreateInbox(); + host.CreateOutbox(); + await host.RunAsync(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureHostConfiguration(configurationBuilder => + { + configurationBuilder.SetBasePath(Directory.GetCurrentDirectory()); + configurationBuilder.AddJsonFile("appsettings.json", optional: true); + configurationBuilder.AddJsonFile($"appsettings.{GetEnvironment()}.json", optional: true); + configurationBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); //NOTE: Although not web, we use this to grab the environment + configurationBuilder.AddEnvironmentVariables(prefix: "BRIGHTER_"); + configurationBuilder.AddCommandLine(args); + }) + .ConfigureLogging((context, builder) => + { + builder.AddConsole(); + builder.AddDebug(); + }) + .ConfigureServices((hostContext, services) => + { + var subscriptions = new Subscription[] + { + new RmqSubscription( + new SubscriptionName("paramore.sample.salutationanalytics"), + new ChannelName("SalutationAnalytics"), + new RoutingKey("GreetingMade"), + runAsync: true, + timeoutInMilliseconds: 200, + isDurable: true, + makeChannels: OnMissingChannel.Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere + }; + + var host = hostContext.HostingEnvironment.IsDevelopment() ? "localhost" : "rabbitmq"; + + var rmqConnection = new RmqMessagingGatewayConnection + { + AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@{host}:5672")), + Exchange = new Exchange("paramore.brighter.exchange") + }; + + var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection); + + services.AddServiceActivator(options => + { + options.Subscriptions = subscriptions; + options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory); + options.UseScoped = true; + options.HandlerLifetime = ServiceLifetime.Scoped; + options.MapperLifetime = ServiceLifetime.Singleton; + options.CommandProcessorLifetime = ServiceLifetime.Scoped; + options.PolicyRegistry = new SalutationPolicy(); + }) + .UseExternalBus(new RmqProducerRegistryFactory( + new RmqMessagingGatewayConnection + { + AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@{host}:5672")), + Exchange = new Exchange("paramore.brighter.exchange"), + }, + new RmqPublication[] { + new RmqPublication + { + Topic = new RoutingKey("SalutationReceived"), + MaxOutStandingMessages = 5, + MaxOutStandingCheckIntervalMilliSeconds = 500, + WaitForConfirmsTimeOutInMilliseconds = 1000, + MakeChannels = OnMissingChannel.Create + }} + ).Create() + ) + .AutoFromAssemblies() + .UseExternalInbox( + ConfigureInbox(hostContext), + new InboxConfiguration( + scope: InboxScope.All, + onceOnly: true, + actionOnExists: OnceOnlyAction.Warn + ) + ); + + services.AddHostedService(); + }) + .UseConsoleLifetime(); + + private static string GetEnvironment() + { + //NOTE: Hosting Context will always return Production outside of ASPNET_CORE at this point, so grab it directly + return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + } + + private static IAmAnInbox ConfigureInbox(HostBuilderContext hostContext) + { + if (hostContext.HostingEnvironment.IsDevelopment()) + { + return new SqliteInbox(new SqliteInboxConfiguration(DbConnectionString(hostContext), SchemaCreation.INBOX_TABLE_NAME)); + } + + return new MySqlInbox(new MySqlInboxConfiguration(DbConnectionString(hostContext), SchemaCreation.INBOX_TABLE_NAME)); + } + + private static string DbConnectionString(HostBuilderContext hostContext) + { + //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities + return hostContext.HostingEnvironment.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : hostContext.Configuration.GetConnectionString("Salutations"); + } + + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json b/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json new file mode 100644 index 0000000000..ffd6e91a82 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Development": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Production": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Production" + } + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj new file mode 100644 index 0000000000..d663f6065b --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj @@ -0,0 +1,38 @@ + + + + Exe + net5.0 + + + + + + + + + + + + + + + + + + + + + + true + PreserveNewest + PreserveNewest + + + true + PreserveNewest + PreserveNewest + + + + diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Development.json b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Development.json new file mode 100644 index 0000000000..1d7a4e1ad3 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json new file mode 100644 index 0000000000..dbbfec462a --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Warning", + "Microsoft": "Warning" + } + }, + "ConnectionStrings": { + "Salutations": "server=localhost; port=3306; uid=root; pwd=root; database=Salutations", + "SalutationsDb": "server=localhost; port=3306; uid=root; pwd=root" + } +} \ No newline at end of file diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.json b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.json new file mode 100644 index 0000000000..27bbd50072 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} \ No newline at end of file diff --git a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs new file mode 100644 index 0000000000..c2e5aceb41 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs @@ -0,0 +1,21 @@ +namespace SalutationEntities +{ + public class Salutation + { + private int _id; + public byte[] TimeStamp { get; set; } + public string Greeting { get; } + public Salutation(string greeting) + { + Greeting = greeting; + } + + public Salutation(int id, string greeting) + { + _id = id; + Greeting = greeting; + } + + } +} + diff --git a/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj b/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj new file mode 100644 index 0000000000..f208d303c9 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs b/samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs new file mode 100644 index 0000000000..d93985a37d --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs @@ -0,0 +1,9 @@ +using SalutationEntities; + +namespace SalutationPorts.EntityGateway +{ + public class SalutationsEntityGateway + { + + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs new file mode 100644 index 0000000000..8d65f24622 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Paramore.Brighter; +using Paramore.Brighter.Inbox.Attributes; +using Paramore.Brighter.Logging.Attributes; +using Paramore.Brighter.Policies.Attributes; +using SalutationEntities; +using SalutationPorts.EntityGateway; +using SalutationPorts.Requests; + +namespace SalutationPorts.Handlers +{ + public class GreetingMadeHandlerAsync : RequestHandlerAsync + { + private readonly SalutationsEntityGateway _uow; + private readonly IAmACommandProcessor _postBox; + + public GreetingMadeHandlerAsync(SalutationsEntityGateway uow, IAmACommandProcessor postBox) + { + _uow = uow; + _postBox = postBox; + } + + //[UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] -- we are using a global inbox, so need to be explicit!! + [RequestLoggingAsync(step: 1, timing: HandlerTiming.Before)] + [UsePolicyAsync(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)] + public override async Task HandleAsync(GreetingMade @event, CancellationToken cancellationToken = default(CancellationToken)) + { + var posts = new List(); + + //var tx = await _uow.Database.BeginTransactionAsync(cancellationToken); + try + { + var salutation = new Salutation(@event.Greeting); + + //_uow.Salutations.Add(salutation); + + posts.Add(await _postBox.DepositPostAsync(new SalutationReceived(DateTimeOffset.Now), cancellationToken: cancellationToken)); + + //await _uow.SaveChangesAsync(cancellationToken); + + //await tx.CommitAsync(cancellationToken); + } + catch (Exception e) + { + Console.WriteLine(e); + + //await tx.RollbackAsync(cancellationToken); + + Console.WriteLine("Salutation analytical record not saved"); + + throw; + } + + await _postBox.ClearOutboxAsync(posts, cancellationToken: cancellationToken); + + return await base.HandleAsync(@event, cancellationToken); + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs b/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs new file mode 100644 index 0000000000..4db47aa42d --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs @@ -0,0 +1,27 @@ +using System; +using Polly; +using Polly.Contrib.WaitAndRetry; +using Polly.Retry; + +namespace SalutationPorts.Policies +{ + public static class Retry + { + public const string RETRYPOLICYASYNC = "SalutationPorts.Policies.RetryPolicyAsync"; + public const string EXPONENTIAL_RETRYPOLICYASYNC = "SalutationPorts.Policies.ExponenttialRetryPolicyAsync"; + + public static AsyncRetryPolicy GetSimpleHandlerRetryPolicy() + { + return Policy.Handle().WaitAndRetryAsync(new[] + { + TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150) + }); + } + + public static AsyncRetryPolicy GetExponentialHandlerRetryPolicy() + { + var delay = Backoff.ExponentialBackoff(TimeSpan.FromMilliseconds(100), retryCount: 5, fastFirst: true); + return Policy.Handle().WaitAndRetryAsync(delay); + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs b/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs new file mode 100644 index 0000000000..ddf21c324f --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs @@ -0,0 +1,18 @@ +using Paramore.Brighter; + +namespace SalutationPorts.Policies +{ + public class SalutationPolicy : DefaultPolicy + { + public SalutationPolicy() + { + AddSalutationPolicies(); + } + + private void AddSalutationPolicies() + { + Add(Retry.RETRYPOLICYASYNC, Retry.GetSimpleHandlerRetryPolicy()); + Add(Retry.EXPONENTIAL_RETRYPOLICYASYNC, Retry.GetExponentialHandlerRetryPolicy()); + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/Requests/GreetingMade.cs b/samples/WebAPI_Dapper/SalutationPorts/Requests/GreetingMade.cs new file mode 100644 index 0000000000..e22c75a351 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/Requests/GreetingMade.cs @@ -0,0 +1,15 @@ +using System; +using Paramore.Brighter; + +namespace SalutationPorts.Requests +{ + public class GreetingMade : Event + { + public string Greeting { get; set; } + + public GreetingMade(string greeting) : base(Guid.NewGuid()) + { + Greeting = greeting; + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/Requests/SalutationReceived.cs b/samples/WebAPI_Dapper/SalutationPorts/Requests/SalutationReceived.cs new file mode 100644 index 0000000000..c2610ec790 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/Requests/SalutationReceived.cs @@ -0,0 +1,15 @@ +using System; +using Paramore.Brighter; + +namespace SalutationPorts.Requests +{ + public class SalutationReceived : Event + { + public DateTimeOffset ReceivedAt { get; } + + public SalutationReceived(DateTimeOffset receivedAt) : base(Guid.NewGuid()) + { + ReceivedAt = receivedAt; + } + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj new file mode 100644 index 0000000000..52bbbe0f06 --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj @@ -0,0 +1,16 @@ + + + + net5.0 + + + + + + + + + + + + From 16d6698508dea2c388281da7d726d6b00e0d131b Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Tue, 15 Mar 2022 09:31:21 +0000 Subject: [PATCH 02/16] Unit of Work needs to provide transaction management for Box work --- Brighter.sln | 28 +++++++++++++++ .../GreetingsPorts/GreetingsPorts.csproj | 2 ++ .../SalutationPorts/SalutationPorts.csproj | 2 ++ .../Salutations_MySqlMigrations.csproj | 2 +- .../Salutations_SqliteMigrations.csproj | 2 +- src/Paramore.Brighter.Dapper/UnitOfWork.cs | 7 ++++ .../MySqlDapperConnectionProvider.cs | 35 +++++++++++++++++++ 7 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/Paramore.Brighter.Dapper/UnitOfWork.cs create mode 100644 src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs diff --git a/Brighter.sln b/Brighter.sln index c251ae009f..1a1153e30e 100644 --- a/Brighter.sln +++ b/Brighter.sln @@ -249,6 +249,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationPorts", "samples\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationAnalytics", "samples\WebAPI_Dapper\SalutationAnalytics\SalutationAnalytics.csproj", "{1A714140-A61A-4702-A358-2E906C9FEE8E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.MySql.Dapper", "src\Paramore.Brighter.MySql.Dapper\Paramore.Brighter.MySql.Dapper.csproj", "{083115C7-B622-4E42-89E9-B934D1B38321}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Dapper", "src\Paramore.Brighter.Dapper\Paramore.Brighter.Dapper.csproj", "{0060E67E-13AC-466A-9DB6-A72EC762660C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1399,6 +1403,30 @@ Global {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|Mixed Platforms.Build.0 = Release|Any CPU {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|x86.ActiveCfg = Release|Any CPU {1A714140-A61A-4702-A358-2E906C9FEE8E}.Release|x86.Build.0 = Release|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Debug|Any CPU.Build.0 = Debug|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Debug|x86.ActiveCfg = Debug|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Debug|x86.Build.0 = Debug|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Release|Any CPU.ActiveCfg = Release|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Release|Any CPU.Build.0 = Release|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Release|x86.ActiveCfg = Release|Any CPU + {083115C7-B622-4E42-89E9-B934D1B38321}.Release|x86.Build.0 = Release|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|x86.ActiveCfg = Debug|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|x86.Build.0 = Debug|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Any CPU.Build.0 = Release|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|x86.ActiveCfg = Release|Any CPU + {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj index bbdad72230..be96adbbd5 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj +++ b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj @@ -10,6 +10,8 @@ + + diff --git a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj index 52bbbe0f06..5df0fd451f 100644 --- a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj +++ b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj @@ -10,6 +10,8 @@ + + diff --git a/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj b/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj index 4e3c584708..cda6832475 100644 --- a/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj +++ b/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj @@ -3,7 +3,7 @@ net5.0 enable - enable + disable diff --git a/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj b/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj index 6fa57b78ce..7596f218ef 100644 --- a/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj +++ b/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj @@ -3,7 +3,7 @@ net5.0 enable - enable + disable diff --git a/src/Paramore.Brighter.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.Dapper/UnitOfWork.cs new file mode 100644 index 0000000000..889fec75cf --- /dev/null +++ b/src/Paramore.Brighter.Dapper/UnitOfWork.cs @@ -0,0 +1,7 @@ +namespace Paramore.Brighter.Dapper +{ + public class UnitOfWork + { + + } +} diff --git a/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs b/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs new file mode 100644 index 0000000000..673f5cee8b --- /dev/null +++ b/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs @@ -0,0 +1,35 @@ +using System.Threading; +using System.Threading.Tasks; +using MySqlConnector; +using Paramore.Brighter.Dapper; + +namespace Paramore.Brighter.MySql.Dapper +{ + public class MySqlDapperConnectionProvider : IMySqlTransactionConnectionProvider where T : UnitOfWork + { + private readonly T _unitOfWork; + + public MySqlDapperConnectionProvider(T unitOfWork) + { + _unitOfWork = unitOfWork; + } + + public MySqlConnection GetConnection() + { + _unitOfWork.GetConnection(); + } + + public async Task GetConnectionAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + _unitOfWork.GetConnectionAsync(); + } + + public MySqlTransaction GetTransaction() + { + _unitOfWork.GetTransaction(); + } + + public bool HasOpenTransaction { get; } + public bool IsSharedConnection { get; } + } +} From 8a664142578bd215152f275ece2772f8a0b9443b Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Tue, 15 Mar 2022 09:33:01 +0000 Subject: [PATCH 03/16] missing projects --- .../Paramore.Brighter.Dapper.csproj | 12 ++++++++++++ .../Paramore.Brighter.MySql.Dapper.csproj | 15 +++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj create mode 100644 src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj diff --git a/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj b/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj new file mode 100644 index 0000000000..7366816912 --- /dev/null +++ b/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + enable + disable + + + + + + diff --git a/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj b/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj new file mode 100644 index 0000000000..e99fc246e2 --- /dev/null +++ b/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + enable + disable + 7.3 + + + + + + + + From 505dd060cd3a7bc3288cd22062ef6ebfde00c954 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Tue, 19 Apr 2022 08:02:49 +0100 Subject: [PATCH 04/16] snapshot --- Brighter.sln | 40 ++++++---- .../EntityGateway/GreetingsEntityGateway.cs | 9 --- .../GreetingsPorts/GreetingsPorts.csproj | 1 + .../Handlers/AddGreetingHandlerAsync.cs | 25 ++++--- .../GreetingsWeb/GreetingsWeb.csproj | 2 + samples/WebAPI_Dapper/GreetingsWeb/Startup.cs | 19 +++-- .../Salutations_MySqlMigrations.csproj | 2 - .../Salutations_SqliteMigrations.csproj | 2 - .../DbConnectionStringProvider.cs | 19 +++++ .../Paramore.Brighter.Dapper.csproj | 5 -- src/Paramore.Brighter.Dapper/UnitOfWork.cs | 7 -- .../MySqlDapperConnectionProvider.cs | 32 +++++--- .../Paramore.Brighter.MySql.Dapper.csproj | 3 - .../UnitOfWork.cs | 68 +++++++++++++++++ .../Paramore.Brighter.Sqlite.Dapper.csproj | 18 +++++ .../SqliteDapperConnectionProvider.cs | 47 ++++++++++++ .../UnitOfWork.cs | 74 +++++++++++++++++++ 17 files changed, 305 insertions(+), 68 deletions(-) delete mode 100644 samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs create mode 100644 src/Paramore.Brighter.Dapper/DbConnectionStringProvider.cs delete mode 100644 src/Paramore.Brighter.Dapper/UnitOfWork.cs create mode 100644 src/Paramore.Brighter.MySql.Dapper/UnitOfWork.cs create mode 100644 src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj create mode 100644 src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs create mode 100644 src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs diff --git a/Brighter.sln b/Brighter.sln index 1a1153e30e..091caebe4b 100644 --- a/Brighter.sln +++ b/Brighter.sln @@ -251,7 +251,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationAnalytics", "samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.MySql.Dapper", "src\Paramore.Brighter.MySql.Dapper\Paramore.Brighter.MySql.Dapper.csproj", "{083115C7-B622-4E42-89E9-B934D1B38321}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Dapper", "src\Paramore.Brighter.Dapper\Paramore.Brighter.Dapper.csproj", "{0060E67E-13AC-466A-9DB6-A72EC762660C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Sqlite.Dapper", "src\Paramore.Brighter.Sqlite.Dapper\Paramore.Brighter.Sqlite.Dapper.csproj", "{519968EE-19A6-4B1E-944F-BFE84E673A40}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Dapper", "src\Paramore.Brighter.Dapper\Paramore.Brighter.Dapper.csproj", "{8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1415,18 +1417,30 @@ Global {083115C7-B622-4E42-89E9-B934D1B38321}.Release|Mixed Platforms.Build.0 = Release|Any CPU {083115C7-B622-4E42-89E9-B934D1B38321}.Release|x86.ActiveCfg = Release|Any CPU {083115C7-B622-4E42-89E9-B934D1B38321}.Release|x86.Build.0 = Release|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|x86.ActiveCfg = Debug|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Debug|x86.Build.0 = Debug|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Any CPU.Build.0 = Release|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|x86.ActiveCfg = Release|Any CPU - {0060E67E-13AC-466A-9DB6-A72EC762660C}.Release|x86.Build.0 = Release|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Debug|x86.ActiveCfg = Debug|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Debug|x86.Build.0 = Debug|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Release|Any CPU.Build.0 = Release|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Release|x86.ActiveCfg = Release|Any CPU + {519968EE-19A6-4B1E-944F-BFE84E673A40}.Release|x86.Build.0 = Release|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Debug|x86.ActiveCfg = Debug|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Debug|x86.Build.0 = Debug|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Release|Any CPU.Build.0 = Release|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Release|x86.ActiveCfg = Release|Any CPU + {8F8079E8-A61A-46E1-A3FE-F800DB8FE05B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs deleted file mode 100644 index a7063b3c22..0000000000 --- a/samples/WebAPI_Dapper/GreetingsPorts/EntityGateway/GreetingsEntityGateway.cs +++ /dev/null @@ -1,9 +0,0 @@ -using GreetingsEntities; - -namespace GreetingsPorts.EntityGateway -{ - public class GreetingsEntityGateway - { - - } -} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj index be96adbbd5..d5902c59b0 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj +++ b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj @@ -5,6 +5,7 @@ + diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs index 26d8264180..f236c72505 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs @@ -1,20 +1,23 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Dapper; using GreetingsEntities; -using GreetingsPorts.EntityGateway; using GreetingsPorts.Requests; using Paramore.Brighter; +using Paramore.Brighter.Dapper; namespace GreetingsPorts.Handlers { public class AddGreetingHandlerAsync: RequestHandlerAsync { - private readonly GreetingsEntityGateway _uow; private readonly IAmACommandProcessor _postBox; - - public AddGreetingHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor postBox) + private readonly IUnitOfWork _uow; + + + public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox) { _uow = uow; _postBox = postBox; @@ -25,17 +28,19 @@ public AddGreetingHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor { var posts = new List(); - //We span a Db outside of EF's control, so start an explicit transactional scope - //var tx = await _uow.Database.BeginTransactionAsync(cancellationToken); + //We use the unit of work to grab connection and transaction, because Outbox needs + //to share them 'behind the scenes' + + var tx = await _uow.BeginOrGetTransactionAsync(cancellationToken); try { - //var person = await _uow.People - // .Where(p => p.Name == addGreeting.Name) - // .SingleAsync(cancellationToken); + var sql = "select Id, Name from People where Name = @Name"; + var people = await _uow.Database.QueryAsync(sql, new { Name = addGreeting.Name }); + var person = people.Single(); var greeting = new Greeting(addGreeting.Greeting); - //person.AddGreeting(greeting); + person.AddGreeting(greeting); //Now write the message we want to send to the Db in the same transaction. posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken)); diff --git a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj index 74a6e0de28..f3f9e88942 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj +++ b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj @@ -14,8 +14,10 @@ + + diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs index 36beff0dbd..fbfc2e4a37 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs @@ -1,25 +1,24 @@ using System; -using System.Data; -using System.Data.Common; -using GreetingsPorts.EntityGateway; using GreetingsPorts.Handlers; using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Data.Sqlite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; using MySqlConnector; using Paramore.Brighter; +using Paramore.Brighter.Dapper; using Paramore.Brighter.Extensions.DependencyInjection; using Paramore.Brighter.Extensions.Hosting; using Paramore.Brighter.MessagingGateway.RMQ; using Paramore.Brighter.MySql; +using Paramore.Brighter.MySql.Dapper; using Paramore.Brighter.Outbox.MySql; using Paramore.Brighter.Outbox.Sqlite; using Paramore.Brighter.Sqlite; +using Paramore.Brighter.Sqlite.Dapper; using Paramore.Darker.AspNetCore; using Polly; using Polly.Registry; @@ -118,7 +117,10 @@ private void ConfigureBrighter(IServiceCollection services) if (_env.IsDevelopment()) { - services.AddBrighter(options => + services.AddSingleton(new DbConnectionStringProvider(DbConnectionString())); + services.AddScoped(typeof(IUnitOfWork), typeof(Paramore.Brighter.Sqlite.Dapper.UnitOfWork)); + + services.AddBrighter(options => { //we want to use scoped, so make sure everything understands that which needs to options.HandlerLifetime = ServiceLifetime.Scoped; @@ -144,7 +146,7 @@ private void ConfigureBrighter(IServiceCollection services) ).Create() ) .UseSqliteOutbox(new SqliteConfiguration(DbConnectionString(), _outBoxTableName), typeof(SqliteConnectionProvider), ServiceLifetime.Singleton) - //.UseSqliteTransactionConnectionProvider(typeof(SqliteEntityFrameworkConnectionProvider), ServiceLifetime.Scoped) + .UseSqliteTransactionConnectionProvider(typeof(SqliteDapperConnectionProvider), ServiceLifetime.Scoped) .UseOutboxSweeper(options => { options.TimerInterval = 5; @@ -154,6 +156,9 @@ private void ConfigureBrighter(IServiceCollection services) } else { + services.AddSingleton(new DbConnectionStringProvider(DbConnectionString())); + services.AddScoped(typeof(IUnitOfWork), typeof(Paramore.Brighter.MySql.Dapper.UnitOfWork)); + services.AddBrighter(options => { options.HandlerLifetime = ServiceLifetime.Scoped; @@ -178,7 +183,7 @@ private void ConfigureBrighter(IServiceCollection services) ).Create() ) .UseMySqlOutbox(new MySqlConfiguration(DbConnectionString(), _outBoxTableName), typeof(MySqlConnectionProvider), ServiceLifetime.Singleton) - //.UseMySqTransactionConnectionProvider(typeof(MySqlEntityFrameworkConnectionProvider), ServiceLifetime.Scoped) + .UseMySqTransactionConnectionProvider(typeof(MySqlDapperConnectionProvider), ServiceLifetime.Scoped) .UseOutboxSweeper() .AutoFromAssemblies(); } diff --git a/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj b/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj index cda6832475..fa3f6a5984 100644 --- a/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj +++ b/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj @@ -2,8 +2,6 @@ net5.0 - enable - disable diff --git a/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj b/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj index 7596f218ef..2c067d95c6 100644 --- a/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj +++ b/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj @@ -2,8 +2,6 @@ net5.0 - enable - disable diff --git a/src/Paramore.Brighter.Dapper/DbConnectionStringProvider.cs b/src/Paramore.Brighter.Dapper/DbConnectionStringProvider.cs new file mode 100644 index 0000000000..ab256450c7 --- /dev/null +++ b/src/Paramore.Brighter.Dapper/DbConnectionStringProvider.cs @@ -0,0 +1,19 @@ +namespace Paramore.Brighter.Dapper +{ + /// + /// Dapper is a set of extension methods for a ADO.NET DbConnection. Usage of Dapper thus depends on a DbConnection + /// To allow us to use that same connection, and any ambient transaction when writing to the Outbox, we need to provide that + /// connection to Brighter code. We use a "unit of work" to wrap the connection, and provide a dependency for handlers etc. + /// to ensure they use the shared connection. To allow the unit of work to be "scoped" or "transient" we need to provide the unit + /// of work with the connection string. We wrap the connection string here, to make the dependency for the "unit of work" clear. + /// + public class DbConnectionStringProvider + { + public DbConnectionStringProvider(string dbConnectionString) + { + ConnectionString = dbConnectionString; + } + + public string ConnectionString { get; } + } +} diff --git a/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj b/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj index 7366816912..27560206de 100644 --- a/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj +++ b/src/Paramore.Brighter.Dapper/Paramore.Brighter.Dapper.csproj @@ -2,11 +2,6 @@ netstandard2.0 - enable - disable - - - diff --git a/src/Paramore.Brighter.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.Dapper/UnitOfWork.cs deleted file mode 100644 index 889fec75cf..0000000000 --- a/src/Paramore.Brighter.Dapper/UnitOfWork.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Paramore.Brighter.Dapper -{ - public class UnitOfWork - { - - } -} diff --git a/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs b/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs index 673f5cee8b..f5d330eb5c 100644 --- a/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs +++ b/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs @@ -1,35 +1,47 @@ using System.Threading; using System.Threading.Tasks; using MySqlConnector; -using Paramore.Brighter.Dapper; namespace Paramore.Brighter.MySql.Dapper { - public class MySqlDapperConnectionProvider : IMySqlTransactionConnectionProvider where T : UnitOfWork + public class MySqlDapperConnectionProvider : IMySqlTransactionConnectionProvider { - private readonly T _unitOfWork; + private readonly UnitOfWork _unitOfWork; - public MySqlDapperConnectionProvider(T unitOfWork) + public MySqlDapperConnectionProvider(UnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public MySqlConnection GetConnection() { - _unitOfWork.GetConnection(); + return (MySqlConnection)_unitOfWork.Database; } - public async Task GetConnectionAsync(CancellationToken cancellationToken = default(CancellationToken)) + public Task GetConnectionAsync(CancellationToken cancellationToken = default(CancellationToken)) { - _unitOfWork.GetConnectionAsync(); + var tcs = new TaskCompletionSource(); + tcs.SetResult(GetConnection()); + return tcs.Task; } public MySqlTransaction GetTransaction() { - _unitOfWork.GetTransaction(); + return (MySqlTransaction)_unitOfWork.BeginOrGetTransaction(); } - public bool HasOpenTransaction { get; } - public bool IsSharedConnection { get; } + public bool HasOpenTransaction + { + get + { + return _unitOfWork.HasTransaction(); + } + } + + public bool IsSharedConnection + { + get { return true; } + + } } } diff --git a/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj b/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj index e99fc246e2..9f750c6016 100644 --- a/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj +++ b/src/Paramore.Brighter.MySql.Dapper/Paramore.Brighter.MySql.Dapper.csproj @@ -2,9 +2,6 @@ netstandard2.0 - enable - disable - 7.3 diff --git a/src/Paramore.Brighter.MySql.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.MySql.Dapper/UnitOfWork.cs new file mode 100644 index 0000000000..c99e5ac83b --- /dev/null +++ b/src/Paramore.Brighter.MySql.Dapper/UnitOfWork.cs @@ -0,0 +1,68 @@ +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using MySqlConnector; +using Paramore.Brighter.Dapper; + +namespace Paramore.Brighter.MySql.Dapper +{ + public class UnitOfWork : IUnitOfWork + { + private readonly MySqlConnection _connection; + private MySqlTransaction _transaction; + + public UnitOfWork(DbConnectionStringProvider dbConnectionStringProvider) + { + _connection = new MySqlConnection(dbConnectionStringProvider.ConnectionString); + } + + public void Commit() + { + if (HasTransaction()) + { + _transaction.Commit(); + _transaction = null; + } + } + + public DbConnection Database + { + get { return _connection; } + } + + public DbTransaction BeginOrGetTransaction() + { + //ToDo: make this thread safe + if (!HasTransaction()) + { + _transaction = _connection.BeginTransaction(); + } + + return _transaction; + } + + public async Task BeginOrGetTransactionAsync(CancellationToken cancellationToken) + { + if (!HasTransaction()) + { + _transaction = await _connection.BeginTransactionAsync(cancellationToken); + } + + return _transaction; + } + + public bool HasTransaction() + { + return _transaction == null; + } + + public void Dispose() + { + if (_transaction != null) + { + _transaction.Rollback(); + } + _connection?.Close(); + } + } +} diff --git a/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj b/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj new file mode 100644 index 0000000000..ee419a1144 --- /dev/null +++ b/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + diff --git a/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs b/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs new file mode 100644 index 0000000000..5a7669a845 --- /dev/null +++ b/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs @@ -0,0 +1,47 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; + +namespace Paramore.Brighter.Sqlite.Dapper +{ + public class SqliteDapperConnectionProvider : ISqliteTransactionConnectionProvider + { + private readonly UnitOfWork _unitOfWork; + + public SqliteDapperConnectionProvider(UnitOfWork unitOfWork) + { + _unitOfWork = unitOfWork; + } + + public SqliteConnection GetConnection() + { + return (SqliteConnection)_unitOfWork.Database; + } + + public Task GetConnectionAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + var tcs = new TaskCompletionSource(); + tcs.SetResult(GetConnection()); + return tcs.Task; + } + + public SqliteTransaction GetTransaction() + { + return (SqliteTransaction)_unitOfWork.BeginOrGetTransaction(); + } + + public bool HasOpenTransaction + { + get + { + return _unitOfWork.HasTransaction(); + } + } + + public bool IsSharedConnection + { + get { return true; } + + } + } +} diff --git a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs new file mode 100644 index 0000000000..445d4a2f3d --- /dev/null +++ b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs @@ -0,0 +1,74 @@ +using System; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; +using Paramore.Brighter.Dapper; +using SQLitePCL; + +namespace Paramore.Brighter.Sqlite.Dapper +{ + public class UnitOfWork : IUnitOfWork + { + private readonly SqliteConnection _connection; + private SqliteTransaction _transaction; + + public UnitOfWork(string dbConnectionString) + { + _connection = new SqliteConnection(dbConnectionString); + } + + public void Commit() + { + if (HasTransaction()) + { + _transaction.Commit(); + _transaction = null; + } + } + + public DbConnection Database + { + get { return _connection; } + } + + public DbTransaction BeginOrGetTransaction() + { + if (!HasTransaction()) + { + _transaction = _connection.BeginTransaction(); + } + + return _transaction; + } + + public Task BeginOrGetTransactionAsync(CancellationToken cancellationToken) + { + //NOTE: Sqlite does not support async begin transaction, so we fake it + var tcs = new TaskCompletionSource(); + if (!HasTransaction()) + { + _transaction = _connection.BeginTransaction(); + } + + tcs.SetResult(_transaction); + + return tcs.Task; + } + + + public bool HasTransaction() + { + return _transaction == null; + } + + public void Dispose() + { + if (_transaction != null) + { + _transaction.Rollback(); + } + _connection?.Close(); + } + } +} From a0ea53bf2cb002ff43d96458cf6e1f4d0911e076 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Wed, 20 Apr 2022 08:58:44 +0100 Subject: [PATCH 05/16] add projects removed during merge --- Brighter.sln | 142 +++++++++++++++++- .../Paramore.Brighter.Sqlite.Dapper.csproj | 11 +- 2 files changed, 144 insertions(+), 9 deletions(-) diff --git a/Brighter.sln b/Brighter.sln index 6aa86e6d0f..7ce6f15424 100644 --- a/Brighter.sln +++ b/Brighter.sln @@ -237,6 +237,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutations_SqliteMigration EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.ServiceActivator.Extensions.Diagnostics", "src\Paramore.Brighter.ServiceActivator.Extensions.Diagnostics\Paramore.Brighter.ServiceActivator.Extensions.Diagnostics.csproj", "{72763FBA-675A-4784-8A57-E69AD41DC685}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI_Dapper", "WebAPI_Dapper", "{202BA107-89D5-4868-AC5A-3527114C0109}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationPorts", "samples\WebAPI_Dapper\SalutationPorts\SalutationPorts.csproj", "{4B889714-78E1-429F-AB98-53A1381F12A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationEntities", "samples\WebAPI_Dapper\SalutationEntities\SalutationEntities.csproj", "{33C7E2A3-B527-4921-800A-B4CA438BAF6E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SalutationAnalytics", "samples\WebAPI_Dapper\SalutationAnalytics\SalutationAnalytics.csproj", "{ECDB8F7A-2D71-457D-8081-97C43D393FE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GreetingsWeb", "samples\WebAPI_Dapper\GreetingsWeb\GreetingsWeb.csproj", "{526E4E1A-9E8E-4233-B851-D6172D013AF6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GreetingsPorts", "samples\WebAPI_Dapper\GreetingsPorts\GreetingsPorts.csproj", "{79A46154-4754-48B1-849F-374AD729C040}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GreetingsEntities", "samples\WebAPI_Dapper\GreetingsEntities\GreetingsEntities.csproj", "{4164912F-F69E-4AD7-A521-6D58253B5ABC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Sqlite.Dapper", "src\Paramore.Brighter.Sqlite.Dapper\Paramore.Brighter.Sqlite.Dapper.csproj", "{1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.MySql.Dapper", "src\Paramore.Brighter.MySql.Dapper\Paramore.Brighter.MySql.Dapper.csproj", "{191A929A-0AE4-4E2A-9608-E47F93FA0004}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Dapper", "src\Paramore.Brighter.Dapper\Paramore.Brighter.Dapper.csproj", "{5FDA646C-30DA-4F13-8399-A3C533D2D16E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1327,6 +1347,114 @@ Global {72763FBA-675A-4784-8A57-E69AD41DC685}.Release|Mixed Platforms.Build.0 = Release|Any CPU {72763FBA-675A-4784-8A57-E69AD41DC685}.Release|x86.ActiveCfg = Release|Any CPU {72763FBA-675A-4784-8A57-E69AD41DC685}.Release|x86.Build.0 = Release|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Debug|x86.Build.0 = Debug|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Release|Any CPU.Build.0 = Release|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Release|x86.ActiveCfg = Release|Any CPU + {4B889714-78E1-429F-AB98-53A1381F12A7}.Release|x86.Build.0 = Release|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Debug|x86.ActiveCfg = Debug|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Debug|x86.Build.0 = Debug|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Release|Any CPU.Build.0 = Release|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Release|x86.ActiveCfg = Release|Any CPU + {33C7E2A3-B527-4921-800A-B4CA438BAF6E}.Release|x86.Build.0 = Release|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Debug|x86.ActiveCfg = Debug|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Debug|x86.Build.0 = Debug|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Release|Any CPU.Build.0 = Release|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Release|x86.ActiveCfg = Release|Any CPU + {ECDB8F7A-2D71-457D-8081-97C43D393FE8}.Release|x86.Build.0 = Release|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Debug|x86.Build.0 = Debug|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Release|Any CPU.Build.0 = Release|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Release|x86.ActiveCfg = Release|Any CPU + {526E4E1A-9E8E-4233-B851-D6172D013AF6}.Release|x86.Build.0 = Release|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Debug|x86.ActiveCfg = Debug|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Debug|x86.Build.0 = Debug|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Release|Any CPU.Build.0 = Release|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Release|x86.ActiveCfg = Release|Any CPU + {79A46154-4754-48B1-849F-374AD729C040}.Release|x86.Build.0 = Release|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Debug|x86.ActiveCfg = Debug|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Debug|x86.Build.0 = Debug|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|Any CPU.Build.0 = Release|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|x86.ActiveCfg = Release|Any CPU + {4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|x86.Build.0 = Release|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|x86.ActiveCfg = Debug|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|x86.Build.0 = Debug|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Any CPU.Build.0 = Release|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|x86.ActiveCfg = Release|Any CPU + {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|x86.Build.0 = Release|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Any CPU.Build.0 = Debug|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|x86.ActiveCfg = Debug|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|x86.Build.0 = Debug|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Any CPU.ActiveCfg = Release|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Any CPU.Build.0 = Release|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|x86.ActiveCfg = Release|Any CPU + {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|x86.Build.0 = Release|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|x86.ActiveCfg = Debug|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|x86.Build.0 = Debug|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|Any CPU.Build.0 = Release|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|x86.ActiveCfg = Release|Any CPU + {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1399,13 +1527,13 @@ Global {F701369D-EDA3-4407-8655-6B81DD6EBCBA} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994} {82E64F30-8D74-4E01-A974-5A78EBAD916C} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994} {05647D1B-87A3-4440-B468-82866B206E49} = {C6B17EFD-4F05-4D45-AF3E-C4F3F790B994} - {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} = {235DE1F1-E71B-4817-8E27-3B34FF006E4C} - {C02BF6C5-B7D2-40FF-AF7D-3502722ABFD9} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} - {17C22BAB-65F2-43BF-9438-B759C75947CE} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} - {CDD81D42-229B-455B-8760-92C76CD3AC96} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} - {019F47CB-FA30-4912-9CF0-86C19CA742AB} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} - {5D1BB2DE-23D6-450B-8E70-56FABABC87D2} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} - {1A714140-A61A-4702-A358-2E906C9FEE8E} = {4D3A09F1-92BB-480D-88F8-D43AC16B03EC} + {202BA107-89D5-4868-AC5A-3527114C0109} = {235DE1F1-E71B-4817-8E27-3B34FF006E4C} + {4B889714-78E1-429F-AB98-53A1381F12A7} = {202BA107-89D5-4868-AC5A-3527114C0109} + {33C7E2A3-B527-4921-800A-B4CA438BAF6E} = {202BA107-89D5-4868-AC5A-3527114C0109} + {ECDB8F7A-2D71-457D-8081-97C43D393FE8} = {202BA107-89D5-4868-AC5A-3527114C0109} + {526E4E1A-9E8E-4233-B851-D6172D013AF6} = {202BA107-89D5-4868-AC5A-3527114C0109} + {79A46154-4754-48B1-849F-374AD729C040} = {202BA107-89D5-4868-AC5A-3527114C0109} + {4164912F-F69E-4AD7-A521-6D58253B5ABC} = {202BA107-89D5-4868-AC5A-3527114C0109} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B7C7E31-2E32-4E0D-9426-BC9AF22E9F4C} diff --git a/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj b/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj index ee419a1144..07ad19ca6d 100644 --- a/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj +++ b/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + net6.0;netstandard2.1 @@ -12,7 +12,14 @@ - + + + + + + + + From 0160eded2233a10ce93a79706ca487253db5e168 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Mon, 25 Apr 2022 18:08:34 +0100 Subject: [PATCH 06/16] Add an Sqlite migration --- Brighter.sln | 15 +++++++ .../GreetingsEntities/Greeting.cs | 10 ++++- .../GreetingsEntities.csproj | 2 +- .../WebAPI_Dapper/GreetingsEntities/Person.cs | 4 +- .../GreetingsPorts/GreetingsPorts.csproj | 4 +- .../Handlers/AddGreetingHandlerAsync.cs | 22 ++++----- .../Handlers/AddPersonHandlerAsync.cs | 13 +++--- .../Handlers/DeletePersonHandlerAsync.cs | 38 +++++++++++----- .../FIndGreetingsForPersonHandlerAsync.cs | 27 ++++++----- .../Handlers/FindPersonByNameHandlerAsync.cs | 20 +++++---- .../GreetingsWeb/Database/SchemaCreation.cs | 25 ++++++++++- .../GreetingsWeb/GreetingsWeb.csproj | 5 ++- samples/WebAPI_Dapper/GreetingsWeb/Program.cs | 3 ++ samples/WebAPI_Dapper/GreetingsWeb/Startup.cs | 45 ++++++++++++++++++- .../Migrations/202204221833_InitialCreate.cs | 29 ++++++++++++ .../SalutationAnalytics.csproj | 2 +- .../Handlers/AddGreetingHandlerAsync.cs | 1 + .../Handlers/AddPersonHandlerAsync.cs | 4 +- 18 files changed, 205 insertions(+), 64 deletions(-) create mode 100644 samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs diff --git a/Brighter.sln b/Brighter.sln index 7ce6f15424..1be9270bb7 100644 --- a/Brighter.sln +++ b/Brighter.sln @@ -257,6 +257,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.MySql.Dap EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Dapper", "src\Paramore.Brighter.Dapper\Paramore.Brighter.Dapper.csproj", "{5FDA646C-30DA-4F13-8399-A3C533D2D16E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Greetings_SqliteMigrations", "samples\WebAPI_Dapper\Greetings_SqliteMigrations\Greetings_SqliteMigrations.csproj", "{026230E1-F388-425A-98CB-6E17C174FE62}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1455,6 +1457,18 @@ Global {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|Mixed Platforms.Build.0 = Release|Any CPU {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|x86.ActiveCfg = Release|Any CPU {5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|x86.Build.0 = Release|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|x86.ActiveCfg = Debug|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|x86.Build.0 = Debug|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Any CPU.Build.0 = Release|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Release|x86.ActiveCfg = Release|Any CPU + {026230E1-F388-425A-98CB-6E17C174FE62}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1534,6 +1548,7 @@ Global {526E4E1A-9E8E-4233-B851-D6172D013AF6} = {202BA107-89D5-4868-AC5A-3527114C0109} {79A46154-4754-48B1-849F-374AD729C040} = {202BA107-89D5-4868-AC5A-3527114C0109} {4164912F-F69E-4AD7-A521-6D58253B5ABC} = {202BA107-89D5-4868-AC5A-3527114C0109} + {026230E1-F388-425A-98CB-6E17C174FE62} = {202BA107-89D5-4868-AC5A-3527114C0109} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B7C7E31-2E32-4E0D-9426-BC9AF22E9F4C} diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs index 44100d5920..05939f09b5 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs @@ -4,7 +4,7 @@ namespace GreetingsEntities { public class Greeting { - private int _id; + public int Id { get; } public string Message { get; set; } public Person Recipient { get; set; } @@ -12,10 +12,16 @@ public Greeting(string message) { Message = message; } + + public Greeting(string message, Person recipient) + { + Message = message; + Recipient = recipient; + } public Greeting(int id, string message, Person recipient) { - _id = id; + Id = id; Message = message; Recipient = recipient; } diff --git a/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj b/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj index cbfa58153a..4f444d8c8b 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj +++ b/samples/WebAPI_Dapper/GreetingsEntities/GreetingsEntities.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs index 53c8ce6e64..96f3368ad0 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs @@ -5,9 +5,9 @@ namespace GreetingsEntities { public class Person { - private int _id; private readonly List _greetings = new List(); public byte[] TimeStamp { get; set; } + public int Id { get; } public string Name { get; } public IReadOnlyList Greetings => _greetings; @@ -18,7 +18,7 @@ public Person(string name) public Person(int id, string name) { - _id = id; + Id = id; Name = name; } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj index d5902c59b0..66a5a0d603 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj +++ b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 @@ -12,7 +12,7 @@ - + diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs index f236c72505..b8e821bab1 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs @@ -3,11 +3,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Dapper; -using GreetingsEntities; -using GreetingsPorts.Requests; +using DapperExtensions; +using DapperExtensions.Predicate; using Paramore.Brighter; using Paramore.Brighter.Dapper; +using GreetingsEntities; +using GreetingsPorts.Requests; namespace GreetingsPorts.Handlers { @@ -34,8 +35,8 @@ public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox) var tx = await _uow.BeginOrGetTransactionAsync(cancellationToken); try { - var sql = "select Id, Name from People where Name = @Name"; - var people = await _uow.Database.QueryAsync(sql, new { Name = addGreeting.Name }); + var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, addGreeting.Name); + var people = await _uow.Database.GetListAsync(searchbyName, transaction: tx); var person = people.Single(); var greeting = new Greeting(addGreeting.Greeting); @@ -45,16 +46,17 @@ public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox) //Now write the message we want to send to the Db in the same transaction. posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken)); - //write the changed entity to the Db - //await _uow.SaveChangesAsync(cancellationToken); + //write the added child entity to the Db + await _uow.Database.InsertAsync(greeting, tx); - //write new person and the associated message to the Db - //await tx.CommitAsync(cancellationToken); + //commit both new greeting and outgoing message + await tx.CommitAsync(cancellationToken); } catch (Exception) { //it went wrong, rollback the entity change and the downstream message - //await tx.RollbackAsync(cancellationToken); + await tx.RollbackAsync(cancellationToken); + return await base.HandleAsync(addGreeting, cancellationToken); } //Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones. diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs index 9faf9ca0f8..ac6556e17e 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs @@ -1,28 +1,25 @@ using System.Threading; using System.Threading.Tasks; +using DapperExtensions; using GreetingsEntities; -using GreetingsPorts.EntityGateway; using GreetingsPorts.Requests; using Paramore.Brighter; +using Paramore.Brighter.Dapper; namespace GreetingsPorts.Handlers { public class AddPersonHandlerAsync : RequestHandlerAsync { - private readonly GreetingsEntityGateway _uow; - private readonly IAmACommandProcessor _postBox; + private readonly IUnitOfWork _uow; - public AddPersonHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor postBox) + public AddPersonHandlerAsync(IUnitOfWork uow) { _uow = uow; - _postBox = postBox; } public override async Task HandleAsync(AddPerson addPerson, CancellationToken cancellationToken = default(CancellationToken)) { - //_uow.Add(new Person(addPerson.Name)); - - //await _uow.SaveChangesAsync(cancellationToken); + await _uow.Database.InsertAsync(new Person(addPerson.Name)); return await base.HandleAsync(addPerson, cancellationToken); } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs index b292fb9fc0..6cdb3951d8 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs @@ -1,30 +1,46 @@ -using System.Threading; +using System; +using System.Linq; +using System.Threading; using System.Threading.Tasks; -using GreetingsPorts.EntityGateway; +using DapperExtensions; +using DapperExtensions.Predicate; +using GreetingsEntities; using GreetingsPorts.Requests; using Paramore.Brighter; +using Paramore.Brighter.Dapper; namespace GreetingsPorts.Handlers { public class DeletePersonHandlerAsync : RequestHandlerAsync { - private readonly GreetingsEntityGateway _uow; + private readonly IUnitOfWork _uow; - public DeletePersonHandlerAsync(GreetingsEntityGateway uow) + public DeletePersonHandlerAsync(IUnitOfWork uow) { _uow = uow; } public async override Task HandleAsync(DeletePerson deletePerson, CancellationToken cancellationToken = default(CancellationToken)) { - //var person = await _uow.People - // .Include(p => p.Greetings) - // .Where(p => p.Name == deletePerson.Name) - // .SingleAsync(cancellationToken); + var tx = await _uow.BeginOrGetTransactionAsync(cancellationToken); + try + { - //_uow.Remove(person); + var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, deletePerson.Name); + var people = await _uow.Database.GetListAsync(searchbyName, transaction: tx); + var person = people.Single(); + + var deleteById = Predicates.Field(g => g.Recipient, Operator.Eq, person); + await _uow.Database.DeleteAsync(deleteById, tx); + + await tx.CommitAsync(cancellationToken); + } + catch (Exception) + { + //it went wrong, rollback the entity change and the downstream message + await tx.RollbackAsync(cancellationToken); + return await base.HandleAsync(deletePerson, cancellationToken); + } - // await _uow.SaveChangesAsync(cancellationToken); - return await base.HandleAsync(deletePerson, cancellationToken); } } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs index b3dbf78ccf..e13c5481f6 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs @@ -1,34 +1,37 @@ -using System.Threading; +using System.Linq; +using System.Threading; using System.Threading.Tasks; -using GreetingsPorts.EntityGateway; +using DapperExtensions; +using DapperExtensions.Predicate; +using GreetingsEntities; using GreetingsPorts.Requests; using GreetingsPorts.Responses; +using Paramore.Brighter.Dapper; using Paramore.Darker; namespace GreetingsPorts.Handlers { public class FIndGreetingsForPersonHandlerAsync : QueryHandlerAsync { - private readonly GreetingsEntityGateway _uow; + private readonly IUnitOfWork _uow; - public FIndGreetingsForPersonHandlerAsync(GreetingsEntityGateway uow) + public FIndGreetingsForPersonHandlerAsync(IUnitOfWork uow) { _uow = uow; } public override async Task ExecuteAsync(FindGreetingsForPerson query, CancellationToken cancellationToken = new CancellationToken()) { - //var person = await _uow.People - // .Include(p => p.Greetings) - // .Where(p => p.Name == query.Name) - // .SingleAsync(cancellationToken); - - //if (person == null) return null; + + var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, query.Name); + var people = await _uow.Database.GetListAsync(searchbyName); + var person = people.Single(); + return new FindPersonsGreetings { - //Name = person.Name, - //Greetings = person.Greetings.Select(g => new Salutation(g.Greet())) + Name = person.Name, + Greetings = person.Greetings.Select(g => new Salutation(g.Greet())) }; } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs index 9b020c0043..0ec1a581f9 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs @@ -1,28 +1,32 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; -using GreetingsPorts.EntityGateway; +using DapperExtensions; +using DapperExtensions.Predicate; +using GreetingsEntities; using GreetingsPorts.Requests; using GreetingsPorts.Responses; +using Paramore.Brighter.Dapper; using Paramore.Darker; namespace GreetingsPorts.Handlers { public class FindPersonByNameHandlerAsync : QueryHandlerAsync { - private readonly GreetingsEntityGateway _uow; + private readonly IUnitOfWork _uow; - public FindPersonByNameHandlerAsync(GreetingsEntityGateway uow) + public FindPersonByNameHandlerAsync(IUnitOfWork uow) { _uow = uow; } public override async Task ExecuteAsync(FindPersonByName query, CancellationToken cancellationToken = new CancellationToken()) { - //return await _uow.People - // .Where(p => p.Name == query.Name) - // .Select(p => new FindPersonResult(p)) - // .SingleAsync(cancellationToken); - return null; + var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, query.Name); + var people = await _uow.Database.GetListAsync(searchbyName); + var person = people.Single(); + + return new FindPersonResult(person); } } } diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs index 63ba8a8721..c0980fc006 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs @@ -1,6 +1,6 @@ using System; using System.Data; -using GreetingsPorts.EntityGateway; +using FluentMigrator.Runner; using Microsoft.AspNetCore.Hosting; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Configuration; @@ -8,7 +8,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using MySqlConnector; -using Paramore.Brighter; using Paramore.Brighter.Outbox.MySql; using Paramore.Brighter.Outbox.Sqlite; using Polly; @@ -60,6 +59,28 @@ private static void WaitToConnect(string connectionString) }); } + public static IHost MigrateDatabase(this IHost webHost) + { + using (var scope = webHost.Services.CreateScope()) + { + var services = scope.ServiceProvider; + + try + { + var runner = services.GetRequiredService(); + runner.ListMigrations(); + runner.MigrateUp(); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + logger.LogError(ex, "An error occurred while migrating the database."); + } + } + + return webHost; + } + public static IHost CreateOutbox(this IHost webHost) { diff --git a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj index f3f9e88942..b7b2cee062 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj +++ b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj @@ -1,10 +1,12 @@ - net5.0 + net6.0 + + @@ -19,6 +21,7 @@ + diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Program.cs b/samples/WebAPI_Dapper/GreetingsWeb/Program.cs index 985f34fd8e..ec415b86fe 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Program.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Program.cs @@ -1,4 +1,5 @@ using System.IO; +using FluentMigrator.Runner; using Greetingsweb.Database; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -14,6 +15,7 @@ public static void Main(string[] args) var host = CreateHostBuilder(args).Build(); host.CheckDbIsUp(); + host.MigrateDatabase(); host.CreateOutbox(); host.Run(); @@ -38,6 +40,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) => { logging.AddConsole(); logging.AddDebug(); + logging.AddFluentMigratorConsole(); }); webBuilder.UseDefaultServiceProvider((context, options) => { diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs index fbfc2e4a37..196f80942a 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs @@ -1,4 +1,8 @@ using System; +using DapperExtensions; +using DapperExtensions.Sql; +using FluentMigrator.Runner; +using Greetings_SqliteMigrations.Migrations; using GreetingsPorts.Handlers; using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Builder; @@ -71,10 +75,49 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc("v1", new OpenApiInfo { Title = "GreetingsAPI", Version = "v1" }); }); + ConfigureMigration(services); + ConfigureDapper(services); ConfigureBrighter(services); ConfigureDarker(services); } - + + private void ConfigureMigration(IServiceCollection services) + { + if (_env.IsDevelopment()) + { + services + .AddFluentMigratorCore() + .ConfigureRunner(c => c.AddSQLite() + .WithGlobalConnectionString(DbConnectionString()) + .ScanIn(typeof(SqlliteInitialCreate).Assembly).For.Migrations() + ); + } + else + { + services + .AddFluentMigratorCore() + .ConfigureRunner(c => c.AddMySql5() + .WithGlobalConnectionString(DbConnectionString()) + .ScanIn(typeof(SqlliteInitialCreate).Assembly).For.Migrations() + ); + } + + } + + private void ConfigureDapper(IServiceCollection services) + { + if (_env.IsDevelopment()) + { + DapperExtensions.DapperExtensions.SqlDialect = new SqliteDialect(); + DapperAsyncExtensions.SqlDialect = new SqliteDialect(); + } + else + { + DapperExtensions.DapperExtensions.SqlDialect = new MySqlDialect(); + DapperAsyncExtensions.SqlDialect = new MySqlDialect(); + } + } + private void CheckDbIsUp() { string connectionString = DbConnectionString(); diff --git a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs new file mode 100644 index 0000000000..4b12c2b51b --- /dev/null +++ b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs @@ -0,0 +1,29 @@ +using FluentMigrator; + +namespace Greetings_SqliteMigrations.Migrations; + +public class SqlliteInitialCreate : Migration +{ + public override void Up() + { + Create.Table("Person") + .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity() + .WithColumn("Name").AsString().Unique() + .WithColumn("TimeStamp").AsBinary(); + + Create.Table("Greeting") + .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity() + .WithColumn("Message").AsString() + .WithColumn("Recipient_Id").AsInt32(); + + Create.ForeignKey() + .FromTable("Greeting").ForeignColumn("Recipient_Id") + .ToTable("People").PrimaryColumn("Id"); + } + + public override void Down() + { + Delete.Table("Greeting"); + Delete.Table("Person"); + } +} diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj index d663f6065b..908358b288 100644 --- a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj +++ b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj @@ -19,7 +19,7 @@ - + diff --git a/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs index 1aa59d431b..196c01931a 100644 --- a/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs +++ b/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs @@ -52,6 +52,7 @@ public AddGreetingHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor { //it went wrong, rollback the entity change and the downstream message await tx.RollbackAsync(cancellationToken); + return await base.HandleAsync(addGreeting, cancellationToken); } //Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones. diff --git a/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs b/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs index acb3034bb8..0e57e4d6b1 100644 --- a/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs +++ b/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs @@ -10,12 +10,10 @@ namespace GreetingsPorts.Handlers public class AddPersonHandlerAsync : RequestHandlerAsync { private readonly GreetingsEntityGateway _uow; - private readonly IAmACommandProcessor _postBox; - public AddPersonHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor postBox) + public AddPersonHandlerAsync(GreetingsEntityGateway uow) { _uow = uow; - _postBox = postBox; } public override async Task HandleAsync(AddPerson addPerson, CancellationToken cancellationToken = default(CancellationToken)) From 07ab1209e5224aa3173080f3c0c89d2981091ce2 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Tue, 26 Apr 2022 18:09:42 +0100 Subject: [PATCH 07/16] Run migrations at startup --- .../.idea/httpRequests/http-requests-log.http | 17 +++++++++++------ .../GreetingsWeb/Database/SchemaCreation.cs | 8 +++++--- samples/WebAPI_Dapper/GreetingsWeb/Startup.cs | 18 ++++++++++++++---- .../Migrations/202204221833_InitialCreate.cs | 1 + .../MySqlDapperConnectionProvider.cs | 5 +++-- .../SqliteDapperConnectionProvider.cs | 5 +++-- .../UnitOfWork.cs | 6 +++--- 7 files changed, 40 insertions(+), 20 deletions(-) diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index dccf5d6cea..2143f6692a 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -1,3 +1,14 @@ +POST http://localhost:5000/People/new +Content-Type: application/json + +{ + "Name" : "Tyrion" +} + +<> 2022-04-26T082842.500.json + +### + POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json @@ -530,9 +541,3 @@ Content-Type: application/json ### -GET http://localhost:5000/Greetings/Tyrion - -<> 2022-02-07T192855.200.json - -### - diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs index c0980fc006..fd6f4ad946 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs @@ -1,7 +1,9 @@ using System; using System.Data; +using System.Data.Common; using FluentMigrator.Runner; using Microsoft.AspNetCore.Hosting; +using Microsoft.Data.SqlClient; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -31,15 +33,14 @@ public static IHost CheckDbIsUp(this IHost webHost) if (env.IsDevelopment()) return webHost; WaitToConnect(connectionString); - CreateDatabaseIfNotExists(connectionString); + CreateDatabaseIfNotExists(new MySqlConnection(connectionString)); return webHost; } - private static void CreateDatabaseIfNotExists(string connectionString) + private static void CreateDatabaseIfNotExists(DbConnection conn) { //The migration does not create the Db, so we need to create it sot that it will add it - using var conn = new MySqlConnection(connectionString); conn.Open(); using var command = conn.CreateCommand(); command.CommandText = "CREATE DATABASE IF NOT EXISTS Greetings"; @@ -75,6 +76,7 @@ public static IHost MigrateDatabase(this IHost webHost) { var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred while migrating the database."); + throw; } } diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs index 196f80942a..ca95d55381 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs @@ -2,6 +2,8 @@ using DapperExtensions; using DapperExtensions.Sql; using FluentMigrator.Runner; +using FluentMigrator.Runner.Initialization; +using FluentMigrator.Runner.Processors; using Greetings_SqliteMigrations.Migrations; using GreetingsPorts.Handlers; using Hellang.Middleware.ProblemDetails; @@ -87,10 +89,12 @@ private void ConfigureMigration(IServiceCollection services) { services .AddFluentMigratorCore() - .ConfigureRunner(c => c.AddSQLite() - .WithGlobalConnectionString(DbConnectionString()) - .ScanIn(typeof(SqlliteInitialCreate).Assembly).For.Migrations() - ); + .ConfigureRunner(c => + { + c.AddSQLite() + .WithGlobalConnectionString(DbConnectionString()) + .ScanIn(typeof(SqlliteInitialCreate).Assembly).For.Migrations(); + }); } else { @@ -106,16 +110,22 @@ private void ConfigureMigration(IServiceCollection services) private void ConfigureDapper(IServiceCollection services) { + services.AddSingleton(new DbConnectionStringProvider(DbConnectionString())); + if (_env.IsDevelopment()) { DapperExtensions.DapperExtensions.SqlDialect = new SqliteDialect(); DapperAsyncExtensions.SqlDialect = new SqliteDialect(); + services.AddScoped(); } else { DapperExtensions.DapperExtensions.SqlDialect = new MySqlDialect(); DapperAsyncExtensions.SqlDialect = new MySqlDialect(); + services.AddScoped(); } + + } private void CheckDbIsUp() diff --git a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs index 4b12c2b51b..634f15cd62 100644 --- a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs +++ b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs @@ -2,6 +2,7 @@ namespace Greetings_SqliteMigrations.Migrations; +[Migration(1)] public class SqlliteInitialCreate : Migration { public override void Up() diff --git a/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs b/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs index f5d330eb5c..921dc7a387 100644 --- a/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs +++ b/src/Paramore.Brighter.MySql.Dapper/MySqlDapperConnectionProvider.cs @@ -1,14 +1,15 @@ using System.Threading; using System.Threading.Tasks; using MySqlConnector; +using Paramore.Brighter.Dapper; namespace Paramore.Brighter.MySql.Dapper { public class MySqlDapperConnectionProvider : IMySqlTransactionConnectionProvider { - private readonly UnitOfWork _unitOfWork; + private readonly IUnitOfWork _unitOfWork; - public MySqlDapperConnectionProvider(UnitOfWork unitOfWork) + public MySqlDapperConnectionProvider(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } diff --git a/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs b/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs index 5a7669a845..c4f852b703 100644 --- a/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs +++ b/src/Paramore.Brighter.Sqlite.Dapper/SqliteDapperConnectionProvider.cs @@ -1,14 +1,15 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Sqlite; +using Paramore.Brighter.Dapper; namespace Paramore.Brighter.Sqlite.Dapper { public class SqliteDapperConnectionProvider : ISqliteTransactionConnectionProvider { - private readonly UnitOfWork _unitOfWork; + private readonly IUnitOfWork _unitOfWork; - public SqliteDapperConnectionProvider(UnitOfWork unitOfWork) + public SqliteDapperConnectionProvider(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } diff --git a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs index 445d4a2f3d..da2fc51681 100644 --- a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs +++ b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs @@ -13,9 +13,9 @@ public class UnitOfWork : IUnitOfWork private readonly SqliteConnection _connection; private SqliteTransaction _transaction; - public UnitOfWork(string dbConnectionString) - { - _connection = new SqliteConnection(dbConnectionString); + public UnitOfWork(DbConnectionStringProvider dbConnectionStringProvider) + { + _connection = new SqliteConnection(dbConnectionStringProvider.ConnectionString); } public void Commit() From 6c08eb8ba2c6be386f3f57f72d438204e542bf1a Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Wed, 27 Apr 2022 18:21:18 +0100 Subject: [PATCH 08/16] Missing files --- .../Greetings_SqliteMigrations.csproj | 13 ++ samples/WebAPI_Dapper/README.md | 124 ++++++++++++++++++ samples/WebAPI_Dapper/build.sh | 15 +++ samples/WebAPI_Dapper/docker-compose.yml | 51 +++++++ samples/WebAPI_Dapper/tests.http | 31 +++++ samples/WebAPI_EFCore/README.md | 2 +- samples/WebAPI_EFCore/build.sh | 2 +- src/Paramore.Brighter.Dapper/IUnitOfWork.cs | 16 +++ 8 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj create mode 100644 samples/WebAPI_Dapper/README.md create mode 100644 samples/WebAPI_Dapper/build.sh create mode 100644 samples/WebAPI_Dapper/docker-compose.yml create mode 100644 samples/WebAPI_Dapper/tests.http create mode 100644 src/Paramore.Brighter.Dapper/IUnitOfWork.cs diff --git a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj new file mode 100644 index 0000000000..eb42023016 --- /dev/null +++ b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + disable + + + + + + + diff --git a/samples/WebAPI_Dapper/README.md b/samples/WebAPI_Dapper/README.md new file mode 100644 index 0000000000..8989cd84e3 --- /dev/null +++ b/samples/WebAPI_Dapper/README.md @@ -0,0 +1,124 @@ +# Table of content +- [Web API and EF Core Example](#web-api-and-ef-core-example) + * [Environments](#environments) + * [Architecture](#architecture) + + [Outbox](#outbox) + + [GreetingsAPI](#greetingsapi) + + [SalutationAnalytics](#salutationanalytics) + * [Build and Deploy](#build-and-deploy) + + [Building](#building) + + [Deploy](#deploy) + + [Possible issues](#possible-issues) + - [Sqlite Database Read-Only Errors](#sqlite-database-read-only-errors) + - [Queue Creation and Dropped Messages](#queue-creation-and-dropped-messages) + - [Connection issue with the RabbitMQ](#connection-issue-with-the-rabbitmq) + - [Helpful documentation links](#helpful-documentation-links) + * [Tests](#tests) +# Web API and EF Core Example +This sample shows a typical scenario when using WebAPI and Brighter/Darker. It demonstrates both using Brighter and Darker to implement the API endpoints, and using a work queue to handle asynchronous work that results from handling the API call. + +## Environments + +*Development* - runs locally on your machine, uses Sqlite as a data store; uses RabbitMQ for messaging, can be launched individually from the docker compose file; it represents a typical setup for development. + +*Production* - runs in Docker, uses MySql as a data store; uses RabbitMQ for messaging; it emulates a possible production environment. + +We provide launchSetting.json files for both, which allows you to run Production; you should launch MySQl and RabbitMQ from the docker compose file; useful for debugging MySQL operations. + +In case you are using Command Line Interface for running the project, consider adding --launch-profile: + +```sh +dotnet run --launch-profile Production -d +``` +## Architecture +### Outbox + Brighter does have an [Outbox pattern support](https://paramore.readthedocs.io/en/latest/OutboxPattern.html). In case you are new to it, consider reading it before diving deeper. +### GreetingsAPI + +We follow a _ports and adapters_ architectural style, dividing the app into the following modules: + +* **GreetingsAdapters**: The adapters' module, handles the primary adapter of HTTP requests and responses to the app + + * **GreetingsPorts**: the ports' module, handles requests from the primary adapter (HTTP) to the domain, and requests to secondary adapters. In a fuller app, the handlers for the primary adapter would correspond to our use case boundaries. The secondary port of the EntityGateway handles access to the DB via EF Core. We choose to treat EF Core as a port, not an adapter itself, here, as it wraps our underlying adapters for Sqlite or MySql. + +* **GreetingsEntities**: the domain model (or application in ports & adapters). In a fuller app, this would contain the logic that has a dependency on entity state. + +We 'depend on inwards' i.e. **GreetingsAdapters -> GreetingsPorts -> GreetingsEntities** + +The assemblies migrations: **Greetings_MySqlMigrations** and **Greetings_SqliteMigrations** hold generated code to configure the Db. Consider this adapter layer code - the use of separate modules allows us to switch migration per environment. + +### SalutationAnalytics + +This listens for a GreetingMade message and stores it. It demonstrates listening to a queue. It also demonstrates the use of scopes provided by Brighter's ServiceActivator, which work with EFCore. These support writing to an Outbox when this component raises a message in turn. + +We don't listen to that message, and without any listeners the RabbitMQ will drop the message we send, as it has no queues to give it to. We don't listen because we would just be repeating what we have shown here. + +We also add an Inbox here. The Inbox can be used to de-duplicate messages. In messaging, the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome. + + +## Build and Deploy + +### Building + +Use the build.sh file to: + +- Build both GreetingsAdapters and GreetingsWatcher and publish it to the /out directory. The Dockerfile assumes the app will be published here. +- Build the Docker image from the Dockerfile for each. + +(Why not use a multi-stage Docker build? We can't do this as the projects here reference projects not NuGet packages for Brighter libraries and there are not in the Docker build context.) + +A common error is to change something, forget to run build.sh and use an old Docker image. + +### Deploy + +We provide a docker compose file to allow you to run a 'Production' environment or to startup RabbitMQ for production: +```sh +docker compose up -d rabbitmq # will just start rabbitmq +``` + +```sh +docker compose up -d mysql # will just start mysql +``` + + +and so on. + +### Possible issues +#### Sqlite Database Read-Only Errors + +A Sqlite database will only have permissions for the process that created it. This can result in you receiving read-only errors between invocations of the sample. You either need to alter the permissions on your Db, or delete it between runs. + +Maintainers, please don't check the Sqlite files into source control. + +#### Queue Creation and Dropped Messages + +Queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RabbitMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue. + +Generally, the rule of thumb is: start the consumer and *then* start the producer. + +You can spot this by looking in the [RabbitMQ Management console](http://localhost:1567) and noting that no queue is bound to the routing key in the exchange. +You can use default credentials for the RabbitMQ Management console: +```sh +user :guest +passowrd: guest +``` +#### Connection issue with the RabbitMQ +When running RabbitMQ from the docker compose file (without any additional network setup, etc.) your RabbitMQ instance in docker will still be accessible by **localhost** as a host name. Consider this when running your application in the Production environment. +In Production, the application by default will have: +```sh +amqp://guest:guest@rabbitmq:5672 +``` + +as an Advanced Message Queuing Protocol (AMQP) connection string. +So one of the options will be replacing AMQP connection string with: +```sh +amqp://guest:guest@localhost:5672 +``` +In case you still struggle, consider following these steps: [RabbitMQ Troubleshooting Networking](https://www.rabbitmq.com/troubleshooting-networking.html) +#### Helpful documentation links +* [Brighter technical documentation](https://paramore.readthedocs.io/en/latest/index.html) +* [Rabbit Message Queue (RMQ) documentation](https://www.rabbitmq.com/documentation.html) + +## Tests + +We provide a tests.http file (supported by both JetBrains Rider and VS Code with the REST Client plugin) to allow you to test operations. \ No newline at end of file diff --git a/samples/WebAPI_Dapper/build.sh b/samples/WebAPI_Dapper/build.sh new file mode 100644 index 0000000000..a9522415f7 --- /dev/null +++ b/samples/WebAPI_Dapper/build.sh @@ -0,0 +1,15 @@ +pushd GreetingsAdapters || exit +rm -rf out +dotnet restore +dotnet build +dotnet publish -c Release -o out +docker build . +popd || exit +pushd SalutationAnalytics || exit +rm -rf out +dotnet restore +dotnet build +dotnet publish -c Release -o out +docker build . +popd || exit + diff --git a/samples/WebAPI_Dapper/docker-compose.yml b/samples/WebAPI_Dapper/docker-compose.yml new file mode 100644 index 0000000000..703b1a2372 --- /dev/null +++ b/samples/WebAPI_Dapper/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3.1' +services: + web: + build: ./GreetingsAdapters + hostname: greetingsapi + ports: + - "5000:5000" + environment: + - BRIGHTER_ConnectionStrings__Greetings=server=greetings_db; port=3306; uid=root; pwd=root; database=Greetings + - BRIGHTER_ConnectionStrings__GreetingsDb=server=greetings_db; port=3306; uid=root; pwd=root + - ASPNETCORE_ENVIRONMENT=Production + links: + - mysql:greetings_db + depends_on: + - mysql + - rabbitmq + worker: + build: ./GreetingsWatcher + hostname: greetingsworker + environment: + - ASPNETCORE_ENVIRONMENT=Production + depends_on: + - rabbitmq + mysql: + hostname: greetings_db + image: mysql + ports: + - "3306:3306" + security_opt: + - seccomp:unconfined + volumes: + - my-db:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: "root" + healthcheck: + test: mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD && test '0' -eq $$(ps aux | awk '{print $$11}' | grep -c -e '^mysql$$') + rabbitmq: + image: brightercommand/rabbitmq:3.8-management-delay + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq-home:/var/lib/rabbitmq + +volumes: + rabbitmq-home: + driver: local + my-db: + driver: local + + diff --git a/samples/WebAPI_Dapper/tests.http b/samples/WebAPI_Dapper/tests.http new file mode 100644 index 0000000000..56382375bb --- /dev/null +++ b/samples/WebAPI_Dapper/tests.http @@ -0,0 +1,31 @@ +### Clean up between runs if needed + +DELETE http://localhost:5000/People/Tyrion HTTP/1.1 + +### Add a Person + +POST http://localhost:5000/People/new HTTP/1.1 +Content-Type: application/json + +{ + "Name" : "Tyrion" +} + +### Now see that person + +GET http://localhost:5000/People/Tyrion HTTP/1.1 + + +### Now add some more greetings + +POST http://localhost:5000/Greetings/Tyrion/new HTTP/1.1 +Content-Type: application/json + +{ + "Greeting" : "I drink, and I know things" +} + +### And now look up Tyrion's greetings + +GET http://localhost:5000/Greetings/Tyrion HTTP/1.1 + diff --git a/samples/WebAPI_EFCore/README.md b/samples/WebAPI_EFCore/README.md index 8989cd84e3..e7070297cb 100644 --- a/samples/WebAPI_EFCore/README.md +++ b/samples/WebAPI_EFCore/README.md @@ -62,7 +62,7 @@ We also add an Inbox here. The Inbox can be used to de-duplicate messages. In me Use the build.sh file to: -- Build both GreetingsAdapters and GreetingsWatcher and publish it to the /out directory. The Dockerfile assumes the app will be published here. +- Build both GreetingsAdapters and SalutationAnalytics and publish it to the /out directory. The Dockerfile assumes the app will be published here. - Build the Docker image from the Dockerfile for each. (Why not use a multi-stage Docker build? We can't do this as the projects here reference projects not NuGet packages for Brighter libraries and there are not in the Docker build context.) diff --git a/samples/WebAPI_EFCore/build.sh b/samples/WebAPI_EFCore/build.sh index f9d070f29c..a9522415f7 100644 --- a/samples/WebAPI_EFCore/build.sh +++ b/samples/WebAPI_EFCore/build.sh @@ -5,7 +5,7 @@ dotnet build dotnet publish -c Release -o out docker build . popd || exit -pushd GreetingsWatcher || exit +pushd SalutationAnalytics || exit rm -rf out dotnet restore dotnet build diff --git a/src/Paramore.Brighter.Dapper/IUnitOfWork.cs b/src/Paramore.Brighter.Dapper/IUnitOfWork.cs new file mode 100644 index 0000000000..8f7c3123a1 --- /dev/null +++ b/src/Paramore.Brighter.Dapper/IUnitOfWork.cs @@ -0,0 +1,16 @@ +using System; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; + +namespace Paramore.Brighter.Dapper +{ + public interface IUnitOfWork : IDisposable + { + DbTransaction BeginOrGetTransaction(); + Task BeginOrGetTransactionAsync(CancellationToken cancellationToken); + void Commit(); + DbConnection Database { get; } + bool HasTransaction(); + } +} From 724390da8882cd7b6b2e1442b753534f15ec01f1 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Wed, 4 May 2022 19:10:03 +0100 Subject: [PATCH 09/16] DapperExtensions configured so that we can add a person via HTTP to the database --- .../.idea/httpRequests/http-requests-log.http | 197 ++++++++---------- .../GreetingsEntities/Greeting.cs | 11 +- .../WebAPI_Dapper/GreetingsEntities/Person.cs | 6 +- .../EntityMappers/GreetingsMapper.cs | 18 ++ .../EntityMappers/PersonMapper.cs | 17 ++ samples/WebAPI_Dapper/GreetingsWeb/Startup.cs | 8 +- .../Migrations/202204221833_InitialCreate.cs | 2 +- samples/WebAPI_Dapper/build.sh | 2 +- 8 files changed, 142 insertions(+), 119 deletions(-) create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs create mode 100644 samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index 2143f6692a..fcf59a2704 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -5,74 +5,66 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-26T082842.500.json +<> 2022-05-04T190828.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081959.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081958.200.json +<> 2022-05-04T185104.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081956.200.json +<> 2022-05-04T184924.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081955.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081953.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081950.200.json - ### POST http://localhost:5000/People/new @@ -82,8 +74,6 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-23T081942.200.json - ### POST http://localhost:5000/People/new @@ -95,59 +85,51 @@ Content-Type: application/json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080140.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080137.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080136.200.json +<> 2022-05-04T181431.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080133.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080131.200.json - ### POST http://localhost:5000/People/new @@ -157,73 +139,76 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-23T080107.200.json +<> 2022-05-04T120021.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191545.200.json +<> 2022-05-04T115956.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191543.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191542.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191540.200.json +### + +POST http://localhost:5000/People/new +Content-Type: application/json + +{ + "Name" : "Tyrion" +} ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191538.200.json +<> 2022-04-29T180302.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191537.200.json +<> 2022-04-29T180040.500.json ### @@ -234,18 +219,18 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-21T191532.200.json +<> 2022-04-29T174912.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T190227.200.json +<> 2022-04-26T082842.500.json ### @@ -256,7 +241,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190115.200.json +<> 2022-02-23T081959.200.json ### @@ -267,7 +252,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190113.200.json +<> 2022-02-23T081958.200.json ### @@ -278,7 +263,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190112.200.json +<> 2022-02-23T081956.200.json ### @@ -289,7 +274,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190109.200.json +<> 2022-02-23T081955.200.json ### @@ -300,18 +285,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190108.200.json +<> 2022-02-23T081953.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190103.200.json +<> 2022-02-23T081950.200.json ### @@ -322,19 +307,17 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-21T185929.500.json +<> 2022-02-23T081942.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T131223.200.json - ### POST http://localhost:5000/Greetings/Tyrion/new @@ -344,7 +327,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131221.200.json +<> 2022-02-23T080140.200.json ### @@ -355,7 +338,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131220.200.json +<> 2022-02-23T080137.200.json ### @@ -366,7 +349,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131218.200.json +<> 2022-02-23T080136.200.json ### @@ -377,7 +360,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131215.200.json +<> 2022-02-23T080133.200.json ### @@ -388,13 +371,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T131210.200.json - -### - -GET http://localhost:5000/People/Tyrion - -<> 2022-02-21T131206.200.json +<> 2022-02-23T080131.200.json ### @@ -405,7 +382,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-21T131202.200.json +<> 2022-02-23T080107.200.json ### @@ -416,7 +393,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193631.200.json +<> 2022-02-21T191545.200.json ### @@ -427,7 +404,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193630.200.json +<> 2022-02-21T191543.200.json ### @@ -438,7 +415,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193628.200.json +<> 2022-02-21T191542.200.json ### @@ -449,7 +426,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193626.200.json +<> 2022-02-21T191540.200.json ### @@ -460,29 +437,29 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193624.200.json +<> 2022-02-21T191538.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-02-07T193615.200.json +<> 2022-02-21T191537.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-07T192928.200.json +<> 2022-02-21T191532.200.json ### @@ -493,7 +470,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192925.200.json +<> 2022-02-21T190227.200.json ### @@ -504,7 +481,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192922.200.json +<> 2022-02-21T190115.200.json ### @@ -515,7 +492,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192920.200.json +<> 2022-02-21T190113.200.json ### @@ -526,7 +503,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192918.200.json +<> 2022-02-21T190112.200.json ### @@ -537,7 +514,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-07T192907.200.json +<> 2022-02-21T190109.200.json ### diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs index 05939f09b5..35193da1c3 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs @@ -1,13 +1,22 @@ using System; +using System.Runtime.CompilerServices; namespace GreetingsEntities { public class Greeting { - public int Id { get; } + public long Id { get; set; } public string Message { get; set; } public Person Recipient { get; set; } + public long RecipientId + { + get + { + return Recipient.Id; + } + } + public Greeting(string message) { Message = message; diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs index 96f3368ad0..6d389b8ec1 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs @@ -7,9 +7,9 @@ public class Person { private readonly List _greetings = new List(); public byte[] TimeStamp { get; set; } - public int Id { get; } - public string Name { get; } - public IReadOnlyList Greetings => _greetings; + public long Id { get; set; } + public string Name { get; set; } + public IEnumerable Greetings => _greetings; public Person(string name) { diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs new file mode 100644 index 0000000000..b7cda15b90 --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs @@ -0,0 +1,18 @@ +using System.Data; +using DapperExtensions.Mapper; +using GreetingsEntities; + +namespace GreetingsPorts.EntityMappers; + +public class GreetingsMapper : ClassMapper +{ + public GreetingsMapper() + { + TableName = nameof(Person); + Map(g=> g.Id).Column("Id").Key(KeyType.Identity); + Map(g => g.Message).Column("Message"); + Map(g => g.RecipientId).Column("RecipientId").Key(KeyType.ForeignKey); + ReferenceMap(g => g.Recipient).Reference((p, g) => p.Id == g.RecipientId ); + } + +} diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs new file mode 100644 index 0000000000..7a29337bbb --- /dev/null +++ b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs @@ -0,0 +1,17 @@ +using DapperExtensions.Mapper; +using GreetingsEntities; + +namespace GreetingsPorts.EntityMappers; + + public class PersonMapper : ClassMapper + { + public PersonMapper() + { + TableName = nameof(Person); + Map(p => p.Id).Column("Id").Key(KeyType.Identity); + Map(p => p.Name).Column("Name"); + Map(p => p.TimeStamp).Column("TimeStamp").Ignore(); + Map(p => p.Greetings).Ignore(); + ReferenceMap(p => p.Greetings).Reference((g, p) => g.RecipientId == p.Id); + } + } diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs index ca95d55381..6d95cfdc74 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs @@ -5,6 +5,7 @@ using FluentMigrator.Runner.Initialization; using FluentMigrator.Runner.Processors; using Greetings_SqliteMigrations.Migrations; +using GreetingsPorts.EntityMappers; using GreetingsPorts.Handlers; using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Builder; @@ -125,7 +126,8 @@ private void ConfigureDapper(IServiceCollection services) services.AddScoped(); } - + DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(PersonMapper).Assembly }); + DapperAsyncExtensions.SetMappingAssemblies(new[] {typeof(PersonMapper).Assembly}); } private void CheckDbIsUp() @@ -205,7 +207,7 @@ private void ConfigureBrighter(IServiceCollection services) options.TimerInterval = 5; options.MinimumMessageAge = 5000; }) - .AutoFromAssemblies(); + .AutoFromAssemblies(typeof(AddPersonHandlerAsync).Assembly); } else { @@ -238,7 +240,7 @@ private void ConfigureBrighter(IServiceCollection services) .UseMySqlOutbox(new MySqlConfiguration(DbConnectionString(), _outBoxTableName), typeof(MySqlConnectionProvider), ServiceLifetime.Singleton) .UseMySqTransactionConnectionProvider(typeof(MySqlDapperConnectionProvider), ServiceLifetime.Scoped) .UseOutboxSweeper() - .AutoFromAssemblies(); + .AutoFromAssemblies(typeof(AddPersonHandlerAsync).Assembly); } } diff --git a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs index 634f15cd62..3ec944642f 100644 --- a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs +++ b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs @@ -10,7 +10,7 @@ public override void Up() Create.Table("Person") .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity() .WithColumn("Name").AsString().Unique() - .WithColumn("TimeStamp").AsBinary(); + .WithColumn("TimeStamp").AsBinary().WithDefault(SystemMethods.CurrentDateTime); Create.Table("Greeting") .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity() diff --git a/samples/WebAPI_Dapper/build.sh b/samples/WebAPI_Dapper/build.sh index a9522415f7..052b5d5dc6 100644 --- a/samples/WebAPI_Dapper/build.sh +++ b/samples/WebAPI_Dapper/build.sh @@ -1,4 +1,4 @@ -pushd GreetingsAdapters || exit +pushd GreetingsWeb || exit rm -rf out dotnet restore dotnet build From e4bb895a796d10666e47e2c4c058db498f3d9ba9 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Thu, 12 May 2022 14:20:21 +0100 Subject: [PATCH 10/16] Interim, getting Dapper working --- .../.idea/httpRequests/http-requests-log.http | 213 ++++++++---------- .../GreetingsEntities/Greeting.cs | 20 +- .../WebAPI_Dapper/GreetingsEntities/Person.cs | 5 +- .../EntityMappers/GreetingsMapper.cs | 3 +- .../Handlers/AddGreetingHandlerAsync.cs | 14 +- .../Handlers/DeletePersonHandlerAsync.cs | 2 +- samples/WebAPI_Dapper/GreetingsWeb/Dockerfile | 2 +- 7 files changed, 120 insertions(+), 139 deletions(-) diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index fcf59a2704..a5609b56ea 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -1,19 +1,17 @@ -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T190828.500.json - ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### @@ -25,72 +23,73 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T185104.500.json +<> 2022-05-11T074536.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T184924.500.json - ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-05-09T080046.500.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-05-09T075859.500.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-05-09T075743.500.json + ### -POST http://localhost:5000/People/new -Content-Type: application/json +GET http://localhost:5000/People/Tyrion -{ - "Name" : "Tyrion" -} +<> 2022-05-09T074758.200.json ### @@ -101,16 +100,22 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T074750.500.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T181431.500.json +### + +GET http://localhost:5000/People/Tyrion + +<> 2022-05-09T073509.200.json ### @@ -121,6 +126,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073500.200.json + ### POST http://localhost:5000/People/new @@ -130,6 +137,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073359.500.json + ### POST http://localhost:5000/People/new @@ -139,7 +148,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T120021.500.json +<> 2022-05-09T073359.200.json ### @@ -150,16 +159,13 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T115956.500.json +<> 2022-05-09T073033.500.json ### -POST http://localhost:5000/People/new -Content-Type: application/json +GET http://localhost:5000/People/Tyrion -{ - "Name" : "Tyrion" -} +<> 2022-05-04T202600.500.json ### @@ -170,6 +176,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-04T202546.500.json + ### POST http://localhost:5000/People/new @@ -179,6 +187,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-04T190828.500.json + ### POST http://localhost:5000/People/new @@ -197,7 +207,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-29T180302.500.json +<> 2022-05-04T185104.500.json ### @@ -208,7 +218,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-29T180040.500.json +<> 2022-05-04T184924.500.json ### @@ -219,8 +229,6 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-29T174912.500.json - ### POST http://localhost:5000/People/new @@ -230,73 +238,61 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-26T082842.500.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081959.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081958.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081956.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081955.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081953.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081950.200.json +<> 2022-05-04T181431.500.json ### @@ -307,8 +303,6 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-23T081942.200.json - ### POST http://localhost:5000/People/new @@ -320,59 +314,53 @@ Content-Type: application/json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080140.200.json +<> 2022-05-04T120021.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080137.200.json +<> 2022-05-04T115956.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080136.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080133.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T080131.200.json - ### POST http://localhost:5000/People/new @@ -382,51 +370,49 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-23T080107.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191545.200.json +<> 2022-04-29T180302.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191543.200.json +<> 2022-04-29T180040.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191542.200.json +<> 2022-04-29T174912.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T191540.200.json +<> 2022-04-26T082842.500.json ### @@ -437,7 +423,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191538.200.json +<> 2022-02-23T081959.200.json ### @@ -448,18 +434,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T191537.200.json - -### - -POST http://localhost:5000/People/new -Content-Type: application/json - -{ - "Name" : "Tyrion" -} - -<> 2022-02-21T191532.200.json +<> 2022-02-23T081958.200.json ### @@ -470,7 +445,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190227.200.json +<> 2022-02-23T081956.200.json ### @@ -481,7 +456,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190115.200.json +<> 2022-02-23T081955.200.json ### @@ -492,7 +467,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190113.200.json +<> 2022-02-23T081953.200.json ### @@ -503,18 +478,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-02-21T190112.200.json +<> 2022-02-23T081950.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-21T190109.200.json +<> 2022-02-23T081942.200.json ### diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs index 35193da1c3..a60bb52897 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs @@ -7,15 +7,10 @@ public class Greeting { public long Id { get; set; } public string Message { get; set; } - public Person Recipient { get; set; } + //public Person Recipient { get; set; } + public long RecipientId { get; set; } - public long RecipientId - { - get - { - return Recipient.Id; - } - } + public Greeting() { /*Required by Dapperextensions*/} public Greeting(string message) { @@ -25,19 +20,22 @@ public Greeting(string message) public Greeting(string message, Person recipient) { Message = message; - Recipient = recipient; + //Recipient = recipient; + RecipientId = recipient.Id; } public Greeting(int id, string message, Person recipient) { Id = id; Message = message; - Recipient = recipient; + //Recipient = recipient; + RecipientId = recipient.Id; } public string Greet() { - return $"{Message} {Recipient.Name}!"; + //return $"{Message} {Recipient.Name}!"; + return $"{Message}!"; } } } diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs index 6d389b8ec1..11a9c50f7c 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs @@ -10,6 +10,8 @@ public class Person public long Id { get; set; } public string Name { get; set; } public IEnumerable Greetings => _greetings; + + public Person(){ /*Required for DapperExtensions*/} public Person(string name) { @@ -24,7 +26,8 @@ public Person(int id, string name) public void AddGreeting(Greeting greeting) { - greeting.Recipient = this; + //greeting.Recipient = this; + greeting.RecipientId = Id; _greetings.Add(greeting); } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs index b7cda15b90..0517d76b53 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs @@ -11,8 +11,9 @@ public GreetingsMapper() TableName = nameof(Person); Map(g=> g.Id).Column("Id").Key(KeyType.Identity); Map(g => g.Message).Column("Message"); + //Map(g => g.Recipient).Ignore(); Map(g => g.RecipientId).Column("RecipientId").Key(KeyType.ForeignKey); - ReferenceMap(g => g.Recipient).Reference((p, g) => p.Id == g.RecipientId ); } } + diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs index b8e821bab1..2e0bf7f8f1 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs @@ -9,20 +9,22 @@ using Paramore.Brighter.Dapper; using GreetingsEntities; using GreetingsPorts.Requests; +using Microsoft.Extensions.Logging; namespace GreetingsPorts.Handlers { public class AddGreetingHandlerAsync: RequestHandlerAsync { private readonly IAmACommandProcessor _postBox; + private readonly ILogger _logger; private readonly IUnitOfWork _uow; - public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox) + public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox, ILogger logger) { _uow = uow; _postBox = postBox; - + _logger = logger; } public override async Task HandleAsync(AddGreeting addGreeting, CancellationToken cancellationToken = default(CancellationToken)) @@ -41,7 +43,8 @@ public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox) var greeting = new Greeting(addGreeting.Greeting); - person.AddGreeting(greeting); + //person.AddGreeting(greeting); + greeting.RecipientId = person.Id; //Now write the message we want to send to the Db in the same transaction. posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken)); @@ -52,8 +55,9 @@ public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox) //commit both new greeting and outgoing message await tx.CommitAsync(cancellationToken); } - catch (Exception) - { + catch (Exception e) + { + _logger.LogError(e, "Exception thrown handling Add Greeting request"); //it went wrong, rollback the entity change and the downstream message await tx.RollbackAsync(cancellationToken); return await base.HandleAsync(addGreeting, cancellationToken); diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs index 6cdb3951d8..9ca088c239 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs @@ -29,7 +29,7 @@ public DeletePersonHandlerAsync(IUnitOfWork uow) var people = await _uow.Database.GetListAsync(searchbyName, transaction: tx); var person = people.Single(); - var deleteById = Predicates.Field(g => g.Recipient, Operator.Eq, person); + var deleteById = Predicates.Field(g => g.RecipientId, Operator.Eq, person.Id); await _uow.Database.DeleteAsync(deleteById, tx); await tx.CommitAsync(cancellationToken); diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile b/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile index 7e3b0f269e..b6633e7655 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile +++ b/samples/WebAPI_Dapper/GreetingsWeb/Dockerfile @@ -7,4 +7,4 @@ COPY out/ . EXPOSE 5000 ENV ASPNETCORE_URLS=http://+:5000 #run the site -ENTRYPOINT ["dotnet", "GreetingsAdapters.dll"] +ENTRYPOINT ["dotnet", "GreetingsWeb.dll"] From 8682ae7ba044e8f23d43075b54f2364a363867e5 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Mon, 16 May 2022 08:30:32 +0100 Subject: [PATCH 11/16] No insert support for parent/child entities, you just do it explicity i.e.not an ORM --- .../.idea/httpRequests/http-requests-log.http | 182 +++++++++--------- .../GreetingsEntities/Greeting.cs | 3 - .../WebAPI_Dapper/GreetingsEntities/Person.cs | 8 - .../EntityMappers/GreetingsMapper.cs | 5 +- .../Handlers/AddGreetingHandlerAsync.cs | 7 +- .../UnitOfWork.cs | 23 ++- 6 files changed, 104 insertions(+), 124 deletions(-) diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index a5609b56ea..85db7ee878 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -5,6 +5,8 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-16T082709.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -16,15 +18,13 @@ Content-Type: application/json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-11T074536.200.json - ### POST http://localhost:5000/Greetings/Tyrion/new @@ -45,34 +45,36 @@ Content-Type: application/json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } +<> 2022-05-16T081608.500.json + ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-05-09T080046.500.json +<> 2022-05-16T081106.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-05-09T075859.500.json +<> 2022-05-16T080747.500.json ### @@ -83,14 +85,6 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-09T075743.500.json - -### - -GET http://localhost:5000/People/Tyrion - -<> 2022-05-09T074758.200.json - ### POST http://localhost:5000/People/new @@ -100,7 +94,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-09T074750.500.json +<> 2022-05-16T080316.200.json ### @@ -111,34 +105,26 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -### - -GET http://localhost:5000/People/Tyrion - -<> 2022-05-09T073509.200.json +<> 2022-05-16T080309.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T073500.200.json - ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T073359.500.json - ### POST http://localhost:5000/People/new @@ -148,66 +134,73 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-09T073359.200.json +<> 2022-05-11T074536.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T073033.500.json - ### -GET http://localhost:5000/People/Tyrion +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json -<> 2022-05-04T202600.500.json +{ + "Greeting" : "I drink, and I know things" +} ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T202546.500.json - ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T190828.500.json +<> 2022-05-09T080046.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-05-09T075859.500.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T185104.500.json +<> 2022-05-09T075743.500.json + +### + +GET http://localhost:5000/People/Tyrion + +<> 2022-05-09T074758.200.json ### @@ -218,25 +211,22 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T184924.500.json +<> 2022-05-09T074750.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new -Content-Type: application/json +GET http://localhost:5000/People/Tyrion -{ - "Name" : "Tyrion" -} +<> 2022-05-09T073509.200.json ### @@ -247,6 +237,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073500.200.json + ### POST http://localhost:5000/People/new @@ -256,6 +248,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073359.500.json + ### POST http://localhost:5000/People/new @@ -265,6 +259,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073359.200.json + ### POST http://localhost:5000/People/new @@ -274,6 +270,14 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073033.500.json + +### + +GET http://localhost:5000/People/Tyrion + +<> 2022-05-04T202600.500.json + ### POST http://localhost:5000/People/new @@ -283,6 +287,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-04T202546.500.json + ### POST http://localhost:5000/People/new @@ -292,7 +298,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T181431.500.json +<> 2022-05-04T190828.500.json ### @@ -312,6 +318,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-04T185104.500.json + ### POST http://localhost:5000/People/new @@ -321,7 +329,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T120021.500.json +<> 2022-05-04T184924.500.json ### @@ -332,8 +340,6 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T115956.500.json - ### POST http://localhost:5000/People/new @@ -379,8 +385,6 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-29T180302.500.json - ### POST http://localhost:5000/People/new @@ -390,8 +394,6 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-29T180040.500.json - ### POST http://localhost:5000/People/new @@ -401,7 +403,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-29T174912.500.json +<> 2022-05-04T181431.500.json ### @@ -412,74 +414,64 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-04-26T082842.500.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081959.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081958.200.json +<> 2022-05-04T120021.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081956.200.json +<> 2022-05-04T115956.500.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081955.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081953.200.json - ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-02-23T081950.200.json - ### POST http://localhost:5000/People/new @@ -489,7 +481,5 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-02-23T081942.200.json - ### diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs index a60bb52897..0b73f5bdfd 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Greeting.cs @@ -20,7 +20,6 @@ public Greeting(string message) public Greeting(string message, Person recipient) { Message = message; - //Recipient = recipient; RecipientId = recipient.Id; } @@ -28,13 +27,11 @@ public Greeting(int id, string message, Person recipient) { Id = id; Message = message; - //Recipient = recipient; RecipientId = recipient.Id; } public string Greet() { - //return $"{Message} {Recipient.Name}!"; return $"{Message}!"; } } diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs index 11a9c50f7c..b5979ff5a0 100644 --- a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs +++ b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs @@ -23,13 +23,5 @@ public Person(int id, string name) Id = id; Name = name; } - - public void AddGreeting(Greeting greeting) - { - //greeting.Recipient = this; - greeting.RecipientId = Id; - _greetings.Add(greeting); - } - } } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs index 0517d76b53..ede3108fab 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs @@ -8,11 +8,10 @@ public class GreetingsMapper : ClassMapper { public GreetingsMapper() { - TableName = nameof(Person); + TableName = nameof(Greeting); Map(g=> g.Id).Column("Id").Key(KeyType.Identity); Map(g => g.Message).Column("Message"); - //Map(g => g.Recipient).Ignore(); - Map(g => g.RecipientId).Column("RecipientId").Key(KeyType.ForeignKey); + Map(g => g.RecipientId).Column("Recipient_Id").Key(KeyType.ForeignKey); } } diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs index 2e0bf7f8f1..f59a70ca96 100644 --- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs +++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs @@ -41,16 +41,13 @@ public AddGreetingHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox, IL var people = await _uow.Database.GetListAsync(searchbyName, transaction: tx); var person = people.Single(); - var greeting = new Greeting(addGreeting.Greeting); - - //person.AddGreeting(greeting); - greeting.RecipientId = person.Id; + var greeting = new Greeting(addGreeting.Greeting, person); //Now write the message we want to send to the Db in the same transaction. posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken)); //write the added child entity to the Db - await _uow.Database.InsertAsync(greeting, tx); + await _uow.Database.InsertAsync(greeting, tx); //commit both new greeting and outgoing message await tx.CommitAsync(cancellationToken); diff --git a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs index da2fc51681..a36a188061 100644 --- a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs +++ b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs @@ -1,4 +1,5 @@ using System; +using System.Data; using System.Data.Common; using System.Threading; using System.Threading.Tasks; @@ -42,29 +43,33 @@ public DbTransaction BeginOrGetTransaction() return _transaction; } - public Task BeginOrGetTransactionAsync(CancellationToken cancellationToken) + /// + /// Begins a transaction, if one not already started. Closes connection if required + /// + /// + /// + public async Task BeginOrGetTransactionAsync(CancellationToken cancellationToken) { - //NOTE: Sqlite does not support async begin transaction, so we fake it - var tcs = new TaskCompletionSource(); if (!HasTransaction()) { + if (_connection.State != ConnectionState.Open) + { + await _connection.OpenAsync(cancellationToken); + } _transaction = _connection.BeginTransaction(); } - - tcs.SetResult(_transaction); - return tcs.Task; + return _transaction; } - public bool HasTransaction() { - return _transaction == null; + return _transaction != null; } public void Dispose() { - if (_transaction != null) + if (HasTransaction()) { _transaction.Rollback(); } From cddadbb711e211f13818fa291b0e8c06bfbd95e6 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Mon, 16 May 2022 09:19:53 +0100 Subject: [PATCH 12/16] Fix issues around unit of work --- .../.idea/httpRequests/http-requests-log.http | 40 ++++++++++--------- .../UnitOfWork.cs | 9 ++++- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index 85db7ee878..397220a5a3 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -5,6 +5,28 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-16T091734.200.json + +### + +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json + +{ + "Greeting" : "I drink, and I know things" +} + +<> 2022-05-16T083253.200.json + +### + +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json + +{ + "Greeting" : "I drink, and I know things" +} + <> 2022-05-16T082709.200.json ### @@ -465,21 +487,3 @@ Content-Type: application/json ### -POST http://localhost:5000/People/new -Content-Type: application/json - -{ - "Name" : "Tyrion" -} - -### - -POST http://localhost:5000/People/new -Content-Type: application/json - -{ - "Name" : "Tyrion" -} - -### - diff --git a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs index a36a188061..a5fcbc9c7f 100644 --- a/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs +++ b/src/Paramore.Brighter.Sqlite.Dapper/UnitOfWork.cs @@ -71,9 +71,14 @@ public void Dispose() { if (HasTransaction()) { - _transaction.Rollback(); + //will throw if transaction completed, but no way to check transaction state via api + try { _transaction.Rollback(); } catch (Exception) { } + } + + if (_connection is { State: ConnectionState.Open }) + { + _connection.Close(); } - _connection?.Close(); } } } From 6191af334dd6b86186c98a35ba35a1975b51eb67 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Mon, 16 May 2022 17:06:33 +0100 Subject: [PATCH 13/16] ensure packages are up to date --- .../Paramore.Brighter.Sqlite.Dapper.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj b/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj index 07ad19ca6d..e1abace389 100644 --- a/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj +++ b/src/Paramore.Brighter.Sqlite.Dapper/Paramore.Brighter.Sqlite.Dapper.csproj @@ -15,11 +15,11 @@ - + - + From d7ac9fadd4bf203698a632524e121348332f1d01 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Tue, 17 May 2022 09:31:44 +0100 Subject: [PATCH 14/16] Add migrations code --- Brighter.sln | 15 +++ .../GreetingsWeb/Database/SchemaCreation.cs | 91 ++++++++++--------- .../Database/SchemaCreation.cs | 61 +++++++------ .../SalutationAnalytics/Program.cs | 65 ++++++++++++- .../SalutationAnalytics.csproj | 9 +- .../SalutationEntities/Salutation.cs | 9 +- .../SalutationEntities.csproj | 2 +- .../EntityGateway/SalutationsEntityGateway.cs | 9 -- .../EntityMappers/SalutationMapper.cs | 15 +++ .../Handlers/GreetingMadeHandler.cs | 30 +++--- .../SalutationPorts/SalutationPorts.csproj | 5 +- .../202205161812_SqliteMigrations.cs | 19 ++++ .../Salutations_SqliteMigrations.csproj | 13 +++ 13 files changed, 228 insertions(+), 115 deletions(-) delete mode 100644 samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs create mode 100644 samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs create mode 100644 samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs create mode 100644 samples/WebAPI_Dapper/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj diff --git a/Brighter.sln b/Brighter.sln index 1be9270bb7..0f01dccfb8 100644 --- a/Brighter.sln +++ b/Brighter.sln @@ -259,6 +259,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paramore.Brighter.Dapper", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Greetings_SqliteMigrations", "samples\WebAPI_Dapper\Greetings_SqliteMigrations\Greetings_SqliteMigrations.csproj", "{026230E1-F388-425A-98CB-6E17C174FE62}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Salutations_SqliteMigrations", "samples\WebAPI_Dapper\Salutations_SqliteMigrations\Salutations_SqliteMigrations.csproj", "{C601A031-963B-4EA9-82C7-1221B1EE9E51}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1469,6 +1471,18 @@ Global {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Mixed Platforms.Build.0 = Release|Any CPU {026230E1-F388-425A-98CB-6E17C174FE62}.Release|x86.ActiveCfg = Release|Any CPU {026230E1-F388-425A-98CB-6E17C174FE62}.Release|x86.Build.0 = Release|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|x86.ActiveCfg = Debug|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|x86.Build.0 = Debug|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Release|Any CPU.Build.0 = Release|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Release|x86.ActiveCfg = Release|Any CPU + {C601A031-963B-4EA9-82C7-1221B1EE9E51}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1549,6 +1563,7 @@ Global {79A46154-4754-48B1-849F-374AD729C040} = {202BA107-89D5-4868-AC5A-3527114C0109} {4164912F-F69E-4AD7-A521-6D58253B5ABC} = {202BA107-89D5-4868-AC5A-3527114C0109} {026230E1-F388-425A-98CB-6E17C174FE62} = {202BA107-89D5-4868-AC5A-3527114C0109} + {C601A031-963B-4EA9-82C7-1221B1EE9E51} = {202BA107-89D5-4868-AC5A-3527114C0109} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B7C7E31-2E32-4E0D-9426-BC9AF22E9F4C} diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs index fd6f4ad946..c5046ccb64 100644 --- a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs +++ b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs @@ -22,8 +22,8 @@ public static class SchemaCreation public static IHost CheckDbIsUp(this IHost webHost) { - using var scope = webHost.Services.CreateScope() ; - + using var scope = webHost.Services.CreateScope(); + var services = scope.ServiceProvider; var env = services.GetService(); var config = services.GetService(); @@ -38,51 +38,28 @@ public static IHost CheckDbIsUp(this IHost webHost) return webHost; } - private static void CreateDatabaseIfNotExists(DbConnection conn) - { - //The migration does not create the Db, so we need to create it sot that it will add it - conn.Open(); - using var command = conn.CreateCommand(); - command.CommandText = "CREATE DATABASE IF NOT EXISTS Greetings"; - command.ExecuteScalar(); - } - - private static void WaitToConnect(string connectionString) + public static IHost MigrateDatabase(this IHost webHost) { - var policy = Policy.Handle().WaitAndRetryForever( - retryAttempt => TimeSpan.FromSeconds(2), - (exception, timespan) => { Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); }); - - policy.Execute(() => + using (var scope = webHost.Services.CreateScope()) { - using var conn = new MySqlConnection(connectionString); - conn.Open(); - }); - } + var services = scope.ServiceProvider; - public static IHost MigrateDatabase(this IHost webHost) - { - using (var scope = webHost.Services.CreateScope()) - { - var services = scope.ServiceProvider; - - try - { - var runner = services.GetRequiredService(); - runner.ListMigrations(); - runner.MigrateUp(); - } - catch (Exception ex) - { - var logger = services.GetRequiredService>(); - logger.LogError(ex, "An error occurred while migrating the database."); - throw; - } - } - - return webHost; - } + try + { + var runner = services.GetRequiredService(); + runner.ListMigrations(); + runner.MigrateUp(); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + logger.LogError(ex, "An error occurred while migrating the database."); + throw; + } + } + return webHost; + } public static IHost CreateOutbox(this IHost webHost) { @@ -98,6 +75,16 @@ public static IHost CreateOutbox(this IHost webHost) return webHost; } + private static void CreateDatabaseIfNotExists(DbConnection conn) + { + //The migration does not create the Db, so we need to create it sot that it will add it + conn.Open(); + using var command = conn.CreateCommand(); + command.CommandText = "CREATE DATABASE IF NOT EXISTS Greetings"; + command.ExecuteScalar(); + } + + private static void CreateOutbox(IConfiguration config, IWebHostEnvironment env) { try @@ -158,6 +145,22 @@ private static string DbConnectionString(IConfiguration config, IWebHostEnvironm private static string DbServerConnectionString(IConfiguration config, IWebHostEnvironment env) { return env.IsDevelopment() ? "Filename=Greetings.db;Cache=Shared" : config.GetConnectionString("GreetingsDb"); - } + } + + private static void WaitToConnect(string connectionString) + { + var policy = Policy.Handle().WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(2), + (exception, timespan) => + { + Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); + }); + + policy.Execute(() => + { + using var conn = new MySqlConnection(connectionString); + conn.Open(); + }); + } } } diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs index 1de49d2ca9..6a1a8638e5 100644 --- a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs @@ -36,31 +36,7 @@ public static IHost CheckDbIsUp(this IHost host) return host; } - private static void CreateDatabaseIfNotExists(string connectionString) - { - //The migration does not create the Db, so we need to create it sot that it will add it - using var conn = new MySqlConnection(connectionString); - conn.Open(); - using var command = conn.CreateCommand(); - command.CommandText = "CREATE DATABASE IF NOT EXISTS Salutations"; - command.ExecuteScalar(); - } - - private static void WaitToConnect(string connectionString) - { - var policy = Policy.Handle().WaitAndRetryForever( - retryAttempt => TimeSpan.FromSeconds(2), - (exception, timespan) => { Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); }); - - policy.Execute(() => - { - using var conn = new MySqlConnection(connectionString); - conn.Open(); - }); - } - - - public static IHost CreateInbox(this IHost host) + public static IHost CreateInbox(this IHost host) { using (var scope = host.Services.CreateScope()) { @@ -74,8 +50,23 @@ public static IHost CreateInbox(this IHost host) return host; } - private static void CreateInbox(IConfiguration config, IHostEnvironment env) + public static IHost MigrateDatabase(this IHost host) + { + return host; + } + + private static void CreateDatabaseIfNotExists(string connectionString) { + //The migration does not create the Db, so we need to create it sot that it will add it + using var conn = new MySqlConnection(connectionString); + conn.Open(); + using var command = conn.CreateCommand(); + command.CommandText = "CREATE DATABASE IF NOT EXISTS Salutations"; + command.ExecuteScalar(); + } + + private static void CreateInbox(IConfiguration config, IHostEnvironment env) + { try { var connectionString = DbConnectionString(config, env); @@ -90,7 +81,7 @@ private static void CreateInbox(IConfiguration config, IHostEnvironment env) Console.WriteLine($"Issue with creating Inbox table, {e.Message}"); throw; } - } + } private static void CreateInboxDevelopment(string connectionString) { @@ -197,6 +188,18 @@ private static string DbConnectionString(IConfiguration config, IHostEnvironment private static string DbServerConnectionString(IConfiguration config, IHostEnvironment env) { return env.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : config.GetConnectionString("SalutationsDb"); - } - } + } + private static void WaitToConnect(string connectionString) + { + var policy = Policy.Handle().WaitAndRetryForever( + retryAttempt => TimeSpan.FromSeconds(2), + (exception, timespan) => { Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); }); + + policy.Execute(() => + { + using var conn = new MySqlConnection(connectionString); + conn.Open(); + }); + } + } } diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs index 31c5b68f03..734effc963 100644 --- a/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs @@ -1,11 +1,15 @@ using System; using System.IO; using System.Threading.Tasks; +using DapperExtensions; +using DapperExtensions.Sql; +using FluentMigrator.Runner; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Paramore.Brighter; +using Paramore.Brighter.Dapper; using Paramore.Brighter.Extensions.DependencyInjection; using Paramore.Brighter.Inbox; using Paramore.Brighter.Inbox.MySql; @@ -14,8 +18,10 @@ using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; using Paramore.Brighter.ServiceActivator.Extensions.Hosting; using SalutationAnalytics.Database; +using SalutationPorts.EntityMappers; using SalutationPorts.Policies; using SalutationPorts.Requests; +using Salutations_SqliteMigrations.Migrations; namespace SalutationAnalytics { @@ -25,6 +31,7 @@ public static async Task Main(string[] args) { var host = CreateHostBuilder(args).Build(); host.CheckDbIsUp(); + host.MigrateDatabase(); host.CreateInbox(); host.CreateOutbox(); await host.RunAsync(); @@ -47,7 +54,10 @@ private static IHostBuilder CreateHostBuilder(string[] args) => builder.AddDebug(); }) .ConfigureServices((hostContext, services) => - { + { + ConfigureMigration(hostContext, services); + ConfigureDapper(hostContext, services); + var subscriptions = new Subscription[] { new RmqSubscription( @@ -111,10 +121,50 @@ private static IHostBuilder CreateHostBuilder(string[] args) => }) .UseConsoleLifetime(); - private static string GetEnvironment() + private static void ConfigureMigration(HostBuilderContext hostBuilderContext, IServiceCollection services) { - //NOTE: Hosting Context will always return Production outside of ASPNET_CORE at this point, so grab it directly - return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + if (hostBuilderContext.HostingEnvironment.IsDevelopment()) + { + services + .AddFluentMigratorCore() + .ConfigureRunner(c => + { + c.AddSQLite() + .WithGlobalConnectionString(DbConnectionString(hostBuilderContext)) + .ScanIn(typeof(Salutations_SqliteMigrations.Migrations.SqliteInitialCreate).Assembly).For.Migrations(); + }); + } + else + { + services + .AddFluentMigratorCore() + .ConfigureRunner(c => c.AddMySql5() + .WithGlobalConnectionString(DbConnectionString(hostBuilderContext)) + .ScanIn(typeof(Salutations_SqliteMigrations.Migrations.SqliteInitialCreate).Assembly).For.Migrations() + ); + } + + } + + private static void ConfigureDapper(HostBuilderContext hostBuilderContext, IServiceCollection services) + { + services.AddSingleton(new DbConnectionStringProvider(DbConnectionString(hostBuilderContext))); + + if (hostBuilderContext.HostingEnvironment.IsDevelopment()) + { + DapperExtensions.DapperExtensions.SqlDialect = new SqliteDialect(); + DapperAsyncExtensions.SqlDialect = new SqliteDialect(); + services.AddScoped(); + } + else + { + DapperExtensions.DapperExtensions.SqlDialect = new MySqlDialect(); + DapperAsyncExtensions.SqlDialect = new MySqlDialect(); + services.AddScoped(); + } + + DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(SalutationMapper).Assembly }); + DapperAsyncExtensions.SetMappingAssemblies(new[] {typeof(SalutationMapper).Assembly}); } private static IAmAnInbox ConfigureInbox(HostBuilderContext hostContext) @@ -132,6 +182,11 @@ private static string DbConnectionString(HostBuilderContext hostContext) //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities return hostContext.HostingEnvironment.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : hostContext.Configuration.GetConnectionString("Salutations"); } - + + private static string GetEnvironment() + { + //NOTE: Hosting Context will always return Production outside of ASPNET_CORE at this point, so grab it directly + return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + } } } diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj index 908358b288..4da8ef2fbd 100644 --- a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj +++ b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj @@ -2,24 +2,29 @@ Exe - net5.0 + net6.0 + + + - + + + diff --git a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs index c2e5aceb41..b64c6fb51d 100644 --- a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs +++ b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs @@ -2,20 +2,13 @@ { public class Salutation { - private int _id; + public int Id { get; } public byte[] TimeStamp { get; set; } public string Greeting { get; } public Salutation(string greeting) { Greeting = greeting; } - - public Salutation(int id, string greeting) - { - _id = id; - Greeting = greeting; - } - } } diff --git a/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj b/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj index f208d303c9..dbc151713b 100644 --- a/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj +++ b/samples/WebAPI_Dapper/SalutationEntities/SalutationEntities.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 diff --git a/samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs b/samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs deleted file mode 100644 index d93985a37d..0000000000 --- a/samples/WebAPI_Dapper/SalutationPorts/EntityGateway/SalutationsEntityGateway.cs +++ /dev/null @@ -1,9 +0,0 @@ -using SalutationEntities; - -namespace SalutationPorts.EntityGateway -{ - public class SalutationsEntityGateway - { - - } -} diff --git a/samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs b/samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs new file mode 100644 index 0000000000..aacce5f63e --- /dev/null +++ b/samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs @@ -0,0 +1,15 @@ +using DapperExtensions.Mapper; +using SalutationEntities; + +namespace SalutationPorts.EntityMappers; + +public class SalutationMapper : ClassMapper +{ + public SalutationMapper() + { + TableName = nameof(Salutation); + Map(s => s.Id).Column("Id").Key(KeyType.Identity); + Map(s => s.Greeting).Column("Greeting"); + Map(s => s.TimeStamp).Column("TimeStamp").Ignore(); + } +} diff --git a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs index 8d65f24622..a880b119f9 100644 --- a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs +++ b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs @@ -2,25 +2,28 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using DapperExtensions; +using Microsoft.Extensions.Logging; using Paramore.Brighter; -using Paramore.Brighter.Inbox.Attributes; +using Paramore.Brighter.Dapper; using Paramore.Brighter.Logging.Attributes; using Paramore.Brighter.Policies.Attributes; using SalutationEntities; -using SalutationPorts.EntityGateway; using SalutationPorts.Requests; namespace SalutationPorts.Handlers { public class GreetingMadeHandlerAsync : RequestHandlerAsync { - private readonly SalutationsEntityGateway _uow; + private readonly IUnitOfWork _uow; private readonly IAmACommandProcessor _postBox; + private readonly ILogger _logger; - public GreetingMadeHandlerAsync(SalutationsEntityGateway uow, IAmACommandProcessor postBox) + public GreetingMadeHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox, ILogger logger) { _uow = uow; _postBox = postBox; + _logger = logger; } //[UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] -- we are using a global inbox, so need to be explicit!! @@ -30,28 +33,25 @@ public GreetingMadeHandlerAsync(SalutationsEntityGateway uow, IAmACommandProcess { var posts = new List(); - //var tx = await _uow.Database.BeginTransactionAsync(cancellationToken); + var tx = await _uow.Database.BeginTransactionAsync(cancellationToken); try { var salutation = new Salutation(@event.Greeting); - - //_uow.Salutations.Add(salutation); + + _uow.Database.InsertAsync(salutation, tx); posts.Add(await _postBox.DepositPostAsync(new SalutationReceived(DateTimeOffset.Now), cancellationToken: cancellationToken)); - //await _uow.SaveChangesAsync(cancellationToken); - - //await tx.CommitAsync(cancellationToken); + await tx.CommitAsync(cancellationToken); } catch (Exception e) { - Console.WriteLine(e); + _logger.LogError(e, "Could not save salutation"); - //await tx.RollbackAsync(cancellationToken); + //if it went wrong rollback entity write and Outbox write + await tx.RollbackAsync(cancellationToken); - Console.WriteLine("Salutation analytical record not saved"); - - throw; + return await base.HandleAsync(@event, cancellationToken); } await _postBox.ClearOutboxAsync(posts, cancellationToken: cancellationToken); diff --git a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj index 5df0fd451f..e54eee1aa4 100644 --- a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj +++ b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj @@ -1,17 +1,18 @@ - net5.0 + net6.0 + - + diff --git a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs b/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs new file mode 100644 index 0000000000..3936cc20bd --- /dev/null +++ b/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs @@ -0,0 +1,19 @@ +using FluentMigrator; + +namespace Salutations_SqliteMigrations.Migrations; + +public class SqliteInitialCreate : Migration +{ + public override void Up() + { + Create.Table("Salutation") + .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity() + .WithColumn("Greeting").AsString() + .WithColumn("TimeStamp").AsBinary().WithDefault(SystemMethods.CurrentDateTime); + } + + public override void Down() + { + Delete.Table("Salutation"); + } +} diff --git a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj b/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj new file mode 100644 index 0000000000..e157295180 --- /dev/null +++ b/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + disable + + + + + + + \ No newline at end of file From bb7ff3f189f8e035cb683c2bc3efaf178ae9f888 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Fri, 20 May 2022 20:09:38 +0100 Subject: [PATCH 15/16] Fix up dispatcher tests --- .../.idea/httpRequests/http-requests-log.http | 195 ++++++++++-------- .../Database/SchemaCreation.cs | 48 +++-- .../SalutationEntities/Salutation.cs | 6 +- .../Handlers/GreetingMadeHandler.cs | 4 +- .../202205161812_SqliteMigrations.cs | 3 +- src/Paramore.Brighter.Dapper/IUnitOfWork.cs | 21 ++ .../SqliteOutboxSync.cs | 20 +- 7 files changed, 180 insertions(+), 117 deletions(-) diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http index 397220a5a3..1b25120cb9 100644 --- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http +++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http @@ -5,8 +5,6 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-16T091734.200.json - ### POST http://localhost:5000/Greetings/Tyrion/new @@ -16,7 +14,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-16T083253.200.json +<> 2022-05-20T184959.200.json ### @@ -27,7 +25,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-16T082709.200.json +<> 2022-05-20T184958.200.json ### @@ -38,6 +36,8 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-20T184957.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -47,6 +47,8 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-20T184956.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -56,6 +58,8 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-20T184955.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -65,16 +69,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-20T184953.200.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-16T081608.500.json +<> 2022-05-20T184949.200.json ### @@ -85,18 +91,18 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-16T081106.200.json +<> 2022-05-20T184943.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-16T080747.500.json +<> 2022-05-18T083001.200.json ### @@ -107,16 +113,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-18T083000.200.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-16T080316.200.json +<> 2022-05-18T082959.200.json ### @@ -127,7 +135,7 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-16T080309.500.json +<> 2022-05-18T082958.200.json ### @@ -138,6 +146,8 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-18T082956.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -147,16 +157,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-18T082954.200.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-11T074536.200.json +<> 2022-05-18T082800.200.json ### @@ -167,15 +179,19 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-18T082735.200.json + ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } +<> 2022-05-18T082727.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -185,6 +201,8 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-18T082025.200.json + ### POST http://localhost:5000/Greetings/Tyrion/new @@ -194,18 +212,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-09T080046.500.json +<> 2022-05-18T082018.200.json ### -POST http://localhost:5000/Greetings/Tyrion/new +POST http://localhost:5000/People/new Content-Type: application/json { - "Greeting" : "I drink, and I know things" + "Name" : "Tyrion" } -<> 2022-05-09T075859.500.json +<> 2022-05-18T082011.500.json ### @@ -216,24 +234,18 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } -<> 2022-05-09T075743.500.json - -### - -GET http://localhost:5000/People/Tyrion - -<> 2022-05-09T074758.200.json +<> 2022-05-16T091734.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T074750.500.json +<> 2022-05-16T083253.200.json ### @@ -244,45 +256,44 @@ Content-Type: application/json "Greeting" : "I drink, and I know things" } +<> 2022-05-16T082709.200.json + ### -GET http://localhost:5000/People/Tyrion +POST http://localhost:5000/Greetings/Tyrion/new +Content-Type: application/json -<> 2022-05-09T073509.200.json +{ + "Greeting" : "I drink, and I know things" +} ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T073500.200.json - ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T073359.500.json - ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-09T073359.200.json - ### POST http://localhost:5000/People/new @@ -292,13 +303,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-09T073033.500.json - -### - -GET http://localhost:5000/People/Tyrion - -<> 2022-05-04T202600.500.json +<> 2022-05-16T081608.500.json ### @@ -309,7 +314,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T202546.500.json +<> 2022-05-16T081106.200.json ### @@ -320,15 +325,15 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T190828.500.json +<> 2022-05-16T080747.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### @@ -340,35 +345,35 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T185104.500.json +<> 2022-05-16T080316.200.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T184924.500.json +<> 2022-05-16T080309.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### @@ -380,62 +385,74 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-11T074536.200.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-05-09T080046.500.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T181431.500.json +<> 2022-05-09T075859.500.json ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } +<> 2022-05-09T075743.500.json + +### + +GET http://localhost:5000/People/Tyrion + +<> 2022-05-09T074758.200.json + ### POST http://localhost:5000/People/new @@ -445,16 +462,22 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T074750.500.json + ### -POST http://localhost:5000/People/new +POST http://localhost:5000/Greetings/Tyrion/new Content-Type: application/json { - "Name" : "Tyrion" + "Greeting" : "I drink, and I know things" } -<> 2022-05-04T120021.500.json +### + +GET http://localhost:5000/People/Tyrion + +<> 2022-05-09T073509.200.json ### @@ -465,7 +488,7 @@ Content-Type: application/json "Name" : "Tyrion" } -<> 2022-05-04T115956.500.json +<> 2022-05-09T073500.200.json ### @@ -476,6 +499,8 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073359.500.json + ### POST http://localhost:5000/People/new @@ -485,5 +510,7 @@ Content-Type: application/json "Name" : "Tyrion" } +<> 2022-05-09T073359.200.json + ### diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs index 6a1a8638e5..f727b948a3 100644 --- a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs +++ b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs @@ -1,9 +1,11 @@ using System; using System.Data; +using FluentMigrator.Runner; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using MySqlConnector; using Paramore.Brighter.Inbox.MySql; using Paramore.Brighter.Inbox.Sqlite; @@ -20,8 +22,8 @@ public static class SchemaCreation public static IHost CheckDbIsUp(this IHost host) { - using var scope = host.Services.CreateScope() ; - + using var scope = host.Services.CreateScope(); + var services = scope.ServiceProvider; var env = services.GetService(); var config = services.GetService(); @@ -36,7 +38,7 @@ public static IHost CheckDbIsUp(this IHost host) return host; } - public static IHost CreateInbox(this IHost host) + public static IHost CreateInbox(this IHost host) { using (var scope = host.Services.CreateScope()) { @@ -50,10 +52,28 @@ public static IHost CreateInbox(this IHost host) return host; } - public static IHost MigrateDatabase(this IHost host) - { - return host; - } + public static IHost MigrateDatabase(this IHost host) + { + using (var scope = host.Services.CreateScope()) + { + var services = scope.ServiceProvider; + + try + { + var runner = services.GetRequiredService(); + runner.ListMigrations(); + runner.MigrateUp(); + } + catch (Exception ex) + { + var logger = services.GetRequiredService>(); + logger.LogError(ex, "An error occurred while migrating the database."); + throw; + } + } + + return host; + } private static void CreateDatabaseIfNotExists(string connectionString) { @@ -66,7 +86,7 @@ private static void CreateDatabaseIfNotExists(string connectionString) } private static void CreateInbox(IConfiguration config, IHostEnvironment env) - { + { try { var connectionString = DbConnectionString(config, env); @@ -81,7 +101,7 @@ private static void CreateInbox(IConfiguration config, IHostEnvironment env) Console.WriteLine($"Issue with creating Inbox table, {e.Message}"); throw; } - } + } private static void CreateInboxDevelopment(string connectionString) { @@ -114,7 +134,7 @@ private static void CreateInboxProduction(string connectionString) command.CommandText = MySqlInboxBuilder.GetDDL(INBOX_TABLE_NAME); command.ExecuteScalar(); } - + public static IHost CreateOutbox(this IHost webHost) { using (var scope = webHost.Services.CreateScope()) @@ -189,11 +209,15 @@ private static string DbServerConnectionString(IConfiguration config, IHostEnvir { return env.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : config.GetConnectionString("SalutationsDb"); } + private static void WaitToConnect(string connectionString) { var policy = Policy.Handle().WaitAndRetryForever( retryAttempt => TimeSpan.FromSeconds(2), - (exception, timespan) => { Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); }); + (exception, timespan) => + { + Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}"); + }); policy.Execute(() => { @@ -201,5 +225,5 @@ private static void WaitToConnect(string connectionString) conn.Open(); }); } - } + } } diff --git a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs index b64c6fb51d..b06e59dbc3 100644 --- a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs +++ b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs @@ -4,11 +4,13 @@ public class Salutation { public int Id { get; } public byte[] TimeStamp { get; set; } - public string Greeting { get; } + public string Greeting { get; } + + public Salutation() { /* ORM needs to create */ } + public Salutation(string greeting) { Greeting = greeting; } } } - diff --git a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs index a880b119f9..3f912857ed 100644 --- a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs +++ b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs @@ -33,12 +33,12 @@ public GreetingMadeHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox, I { var posts = new List(); - var tx = await _uow.Database.BeginTransactionAsync(cancellationToken); + var tx = await _uow.BeginOrGetTransactionAsync(cancellationToken); try { var salutation = new Salutation(@event.Greeting); - _uow.Database.InsertAsync(salutation, tx); + await _uow.Database.InsertAsync(salutation, tx); posts.Add(await _postBox.DepositPostAsync(new SalutationReceived(DateTimeOffset.Now), cancellationToken: cancellationToken)); diff --git a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs b/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs index 3936cc20bd..0afb14f529 100644 --- a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs +++ b/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs @@ -2,7 +2,8 @@ namespace Salutations_SqliteMigrations.Migrations; -public class SqliteInitialCreate : Migration +[Migration(1)] +public class SqliteInitialCreate : Migration { public override void Up() { diff --git a/src/Paramore.Brighter.Dapper/IUnitOfWork.cs b/src/Paramore.Brighter.Dapper/IUnitOfWork.cs index 8f7c3123a1..0659d3199f 100644 --- a/src/Paramore.Brighter.Dapper/IUnitOfWork.cs +++ b/src/Paramore.Brighter.Dapper/IUnitOfWork.cs @@ -5,11 +5,32 @@ namespace Paramore.Brighter.Dapper { + /// + /// Creates a unit of work, so that Brighter can access the active transaction for the Outbox + /// public interface IUnitOfWork : IDisposable { + /// + /// Begins a new transaction against the database. Will open the connection if it is not already open, + /// + /// A transaction DbTransaction BeginOrGetTransaction(); + + /// + /// Begins a new transaction asynchronously against the database. Will open the connection if it is not already open, + /// + /// + /// A transaction Task BeginOrGetTransactionAsync(CancellationToken cancellationToken); + + /// + /// Commits any pending transactions + /// void Commit(); + + /// + /// The .NET DbConnection to the Database + /// DbConnection Database { get; } bool HasTransaction(); } diff --git a/src/Paramore.Brighter.Outbox.Sqlite/SqliteOutboxSync.cs b/src/Paramore.Brighter.Outbox.Sqlite/SqliteOutboxSync.cs index 22021e76ac..76e227baf1 100644 --- a/src/Paramore.Brighter.Outbox.Sqlite/SqliteOutboxSync.cs +++ b/src/Paramore.Brighter.Outbox.Sqlite/SqliteOutboxSync.cs @@ -239,7 +239,7 @@ public async Task> GetAsync(IEnumerable messageIds, i var connection = _connectionProvider.GetConnection(); using (var command = connection.CreateCommand()) { - var inClause = GenerateInClauseAndAddParameters(command, messageIds.ToList()); + var inClause = string.Join(",", messageIds.ToList().Select((s, i) => "'" + s + "'").ToArray()); var sql = $"SELECT * FROM {_configuration.OutBoxTableName} WHERE MessageId IN ( {inClause} )"; command.CommandText = sql; @@ -486,18 +486,6 @@ private SqliteParameter CreateSqlParameter(string parameterName, object value) return new SqliteParameter(parameterName, value); } - private string GenerateInClauseAndAddParameters(SqliteCommand command, List messageIds) - { - var paramNames = messageIds.Select((s, i) => "@p" + i).ToArray(); - - for (int i = 0; i < paramNames.Count(); i++) - { - command.Parameters.Add(CreateSqlParameter(paramNames[i], messageIds[i])); - } - - return string.Join(",", paramNames); - } - private T ExecuteCommand(Func execute, string sql, int outboxTimeout, params SqliteParameter[] parameters) { @@ -562,10 +550,10 @@ private SqliteParameter[] InitAddDbParameters(Message message) private SqliteCommand InitMarkDispatchedCommand(SqliteConnection connection, IEnumerable messageIds, DateTime? dispatchedAt) { var command = connection.CreateCommand(); - var inClause = GenerateInClauseAndAddParameters(command, messageIds.ToList()); - var sql = $"UPDATE {_configuration.OutBoxTableName} SET Dispatched = @DispatchedAt WHERE MessageId IN ( {inClause} )"; + var inClause = string.Join(",", messageIds.ToList().Select((s, i) => "'" + s + "'").ToArray()); + var dispatchTime = dispatchedAt.HasValue ? "datetime('" + dispatchedAt.Value.ToString("yyyy-MM-dd HH:mm:ss") + "')" : "datetime('now')"; + var sql = $"UPDATE {_configuration.OutBoxTableName} SET Dispatched = {dispatchTime} WHERE MessageId IN ( {inClause} )"; command.CommandText = sql; - command.Parameters.Add(CreateSqlParameter("@DispatchedAt", dispatchedAt.HasValue ? dispatchedAt.Value.ToString("s"): DateTime.UtcNow.ToString("s"))); return command; } From 7d4daf9380d6e2471f642002cba205dff004ba92 Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Fri, 20 May 2022 20:35:33 +0100 Subject: [PATCH 16/16] Update release_notes.md and force build --- release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/release_notes.md b/release_notes.md index 97eba91d5c..5767166b3c 100644 --- a/release_notes.md +++ b/release_notes.md @@ -37,6 +37,7 @@ This section lists features in master, available by [AppVeyor](https://ci.appvey - Provided a short form of the BrighterMessaging constructor, that queries object provided for async versions of interfaces - Changed IsAsync to RunAsync on a Subscription for clarity - Supports an async pipeline: callbacks should happen on the same thread as the handler (and the pump), avoiding thread pool threads + - Fixed issue in SQlite with SQL to mark a message as dispatched ## Release 8.1.1399 ## - Update nuget libs