diff --git a/Directory.Build.props b/Directory.Build.props
index 99c2facf..7f008f29 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,6 +6,7 @@
2.0.11
0.8.2
1.7.0
+ 3.6.2
\ No newline at end of file
diff --git a/EShop.sln b/EShop.sln
index cd7f5691..98b9f223 100644
--- a/EShop.sln
+++ b/EShop.sln
@@ -359,6 +359,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.Dapr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore", "plugins\Inventories\DaprActors\src\EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore\EasyAbp.EShop.Plugins.Inventories.DaprActors.AspNetCore.csproj", "{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj", "{83F6434F-74DC-4389-870D-46510E28C029}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrleansGrains", "OrleansGrains", "{88D17635-75D7-48A1-B622-E6FB3DCACEF8}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8E978749-7972-4703-8A94-6A90080C78DE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj", "{AB3477DB-3457-4167-A086-BAD104D69604}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo\EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj", "{0D613460-A0AD-4EAF-B719-785FE65E97E8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.OrleansGrainsInventory.Domain", "plugins\Inventories\OrleansGrains\src\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj", "{DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests", "plugins\Inventories\OrleansGrains\test\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests\EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj", "{D652EBF0-27CA-44C2-BB78-F446B87377C7}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -933,6 +949,26 @@ Global
{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884}.Release|Any CPU.Build.0 = Release|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {83F6434F-74DC-4389-870D-46510E28C029}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB3477DB-3457-4167-A086-BAD104D69604}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1112,6 +1148,14 @@ Global
{485204B1-7603-4EA0-B3A4-73CB89B0D5BC} = {6E6FE4B9-4117-4F57-B219-EE47E4046096}
{733C51A3-19C8-45C4-8B22-3FD40CAF4EFB} = {485204B1-7603-4EA0-B3A4-73CB89B0D5BC}
{3F0EA314-CCF4-4BB2-A8C1-79FAE4442884} = {F468A386-5660-4888-981A-6ECF15182D32}
+ {88D17635-75D7-48A1-B622-E6FB3DCACEF8} = {9AC27747-E175-487F-92C9-434DEE543273}
+ {8E978749-7972-4703-8A94-6A90080C78DE} = {88D17635-75D7-48A1-B622-E6FB3DCACEF8}
+ {83F6434F-74DC-4389-870D-46510E28C029} = {8E978749-7972-4703-8A94-6A90080C78DE}
+ {AB3477DB-3457-4167-A086-BAD104D69604} = {8E978749-7972-4703-8A94-6A90080C78DE}
+ {0D613460-A0AD-4EAF-B719-785FE65E97E8} = {8E978749-7972-4703-8A94-6A90080C78DE}
+ {DB1C55BF-0C0D-488C-9AFC-992A3DED2EAD} = {8E978749-7972-4703-8A94-6A90080C78DE}
+ {F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77} = {88D17635-75D7-48A1-B622-E6FB3DCACEF8}
+ {D652EBF0-27CA-44C2-BB78-F446B87377C7} = {F58B6EEF-5AFF-4B79-BC71-A2D8C71F5E77}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}
diff --git a/docs/plugins/inventories/orleans-grains/README.md b/docs/plugins/inventories/orleans-grains/README.md
new file mode 100644
index 00000000..c72baf83
--- /dev/null
+++ b/docs/plugins/inventories/orleans-grains/README.md
@@ -0,0 +1,58 @@
+# EShop.Plugins.Inventories.OrleansGrains
+
+[](https://abp.io)
+[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions)
+[](https://www.nuget.org/packages/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions)
+[](https://discord.gg/S6QaezrCRq)
+[](https://www.github.com/EasyAbp/EShop)
+
+EShop product-inventory implementation of [Orleans Grains](https://docs.microsoft.com/en-us/dotnet/orleans/grains).
+
+## Installation
+
+1. Install the following NuGet packages. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-nuget-packages))
+
+ * EasyAbp.EShop.Products.OrleansGrainsInventory.Domain _(install at EasyAbp.EShop.Products.Domain location)_
+ * EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo _(install at a host project to run Grains)_
+
+2. Add `DependsOn(typeof(EShopXxxModule))` attribute to configure the module dependencies. ([see how](https://github.com/EasyAbp/EasyAbpGuide/blob/master/docs/How-To.md#add-module-dependencies))
+
+3. Open `Program.cs` in the host project to create an Orleans Silo. (see Microsoft's [document](https://docs.microsoft.com/en-us/dotnet/orleans/host/configuration-guide/server-configuration) for more information)
+
+ ```csharp
+ builder.Host.AddAppSettingsSecretsJson()
+ .UseAutofac()
+ .UseSerilog()
+ .UseOrleans(c =>
+ {
+ c.UseLocalhostClustering() // for test only
+ .AddMemoryGrainStorage(InventoryGrain.StorageProviderName) // for test only
+ .Configure(options =>
+ {
+ options.ClusterId = "my-first-cluster";
+ options.ServiceId = "MyEShopApp";
+ })
+ .ConfigureApplicationParts(
+ parts => parts.AddApplicationPart(typeof(InventoryGrain).Assembly).WithReferences());
+ });
+ ```
+
+## Usage
+
+1. Configure the OrleansGrains inventory provider as default.
+ ```csharp
+ Configure(options =>
+ {
+ // Configure as the default inventory provider
+ options.DefaultInventoryProviderName = "OrleansGrains";
+
+ // Configure as the default inventory provider for MyProductGroup
+ options.Groups.Configure(group =>
+ {
+ group.DefaultInventoryProviderName = "OrleansGrains";
+ });
+ });
+ ```
+ > Better to use `OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderName` instead of `"OrleansGrains"` as the provider name.
+
+2. Create a product and set `InventoryProviderName` to `OrleansGrains`. Then the product is specified to use the Orleans Grains inventory provider.
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/README.md b/plugins/Inventories/OrleansGrains/README.md
new file mode 120000
index 00000000..1dd0889c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/README.md
@@ -0,0 +1 @@
+../../../docs/plugins/inventories/orleans-grains/README.md
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj
new file mode 100644
index 00000000..c15ef67d
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsAbstractionsModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsAbstractionsModule.cs
new file mode 100644
index 00000000..a327dd08
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsAbstractionsModule.cs
@@ -0,0 +1,11 @@
+using EasyAbp.EShop.Products;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[DependsOn(
+ typeof(EShopProductsDomainSharedModule)
+)]
+public class EShopPluginsInventoriesOrleansGrainsAbstractionsModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/IInventoryGrain.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/IInventoryGrain.cs
new file mode 100644
index 00000000..7deb1fe6
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/IInventoryGrain.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Orleans;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+public interface IInventoryGrain : IGrainWithStringKey
+{
+ Task GetInventoryStateAsync();
+
+ Task IncreaseInventoryAsync(int quantity, bool decreaseSold);
+
+ Task ReduceInventoryAsync(int quantity, bool increaseSold);
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryStateModel.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryStateModel.cs
new file mode 100644
index 00000000..5d7482ec
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryStateModel.cs
@@ -0,0 +1,8 @@
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+public class InventoryStateModel
+{
+ public int Inventory { get; set; }
+
+ public long Sold { get; set; }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Abstractions/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj
new file mode 100644
index 00000000..5d010900
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsSiloModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsSiloModule.cs
new file mode 100644
index 00000000..6d974fdf
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.Silo/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsSiloModule.cs
@@ -0,0 +1,14 @@
+using EasyAbp.EShop.Products;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[DependsOn(
+ typeof(EShopPluginsInventoriesOrleansGrainsModule)
+)]
+public class EShopPluginsInventoriesOrleansGrainsSiloModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj
new file mode 100644
index 00000000..5e949e66
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp.EShop.Plugins.Inventories.OrleansGrains.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsModule.cs
new file mode 100644
index 00000000..087bb7b9
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/EShopPluginsInventoriesOrleansGrainsModule.cs
@@ -0,0 +1,10 @@
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[DependsOn(
+ typeof(EShopPluginsInventoriesOrleansGrainsAbstractionsModule)
+)]
+public class EShopPluginsInventoriesOrleansGrainsModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryGrain.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryGrain.cs
new file mode 100644
index 00000000..454fdb86
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/EasyAbp/EShop/Plugins/Inventories/OrleansGrains/InventoryGrain.cs
@@ -0,0 +1,68 @@
+using System.Threading.Tasks;
+using Orleans;
+using Orleans.Providers;
+using Orleans.Runtime;
+
+namespace EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+[StorageProvider(ProviderName = StorageProviderName)]
+public class InventoryGrain : Grain, IInventoryGrain
+{
+ public const string StorageProviderName = "EShopInventoryStorage";
+
+ public virtual Task GetInventoryStateAsync() => Task.FromResult(State);
+
+ public virtual async Task IncreaseInventoryAsync(int quantity, bool decreaseSold)
+ {
+ InternalIncreaseInventory(quantity, decreaseSold);
+
+ await WriteStateAsync();
+ }
+
+ public async Task ReduceInventoryAsync(int quantity, bool increaseSold)
+ {
+ InternalReduceInventory(quantity, increaseSold);
+
+ await WriteStateAsync();
+ }
+
+ protected virtual void InternalIncreaseInventory(int quantity, bool decreaseSold)
+ {
+ if (quantity < 0)
+ {
+ throw new OrleansException("Quantity should not be less than 0.");
+ }
+
+ if (decreaseSold && State.Sold - quantity < 0)
+ {
+ throw new OrleansException("Target Sold cannot be less than 0.");
+ }
+
+ State.Inventory = checked(State.Inventory + quantity);
+
+ if (decreaseSold)
+ {
+ State.Sold -= quantity;
+ }
+ }
+
+ protected virtual void InternalReduceInventory(int quantity, bool increaseSold)
+ {
+ if (quantity < 0)
+ {
+ throw new OrleansException("Quantity should not be less than 0.");
+ }
+
+ if (quantity > State.Inventory)
+ {
+ throw new OrleansException("Insufficient inventory.");
+ }
+
+ if (increaseSold)
+ {
+ State.Sold = checked(State.Sold + quantity);
+ }
+
+ State.Inventory -= quantity;
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Plugins.Inventories.OrleansGrains/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj
new file mode 100644
index 00000000..708e1aa1
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/EShopProductsOrleansGrainsInventoryDomainModule.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/EShopProductsOrleansGrainsInventoryDomainModule.cs
new file mode 100644
index 00000000..84838e9b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/EShopProductsOrleansGrainsInventoryDomainModule.cs
@@ -0,0 +1,28 @@
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using EasyAbp.EShop.Products.Options;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+[DependsOn(
+ typeof(EShopProductsDomainModule),
+ typeof(EShopPluginsInventoriesOrleansGrainsAbstractionsModule)
+)]
+public class EShopProductsOrleansGrainsInventoryDomainModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Configure(options =>
+ {
+ options.InventoryProviders.Configure(
+ OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderName, provider =>
+ {
+ provider.DisplayName =
+ OrleansGrainsProductInventoryProvider.OrleansGrainsProductInventoryProviderDisplayName;
+ provider.Description = OrleansGrainsProductInventoryProvider
+ .OrleansGrainsProductInventoryProviderDescription;
+ provider.ProviderType = typeof(OrleansGrainsProductInventoryProvider);
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/IInventoryGrainProvider.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/IInventoryGrainProvider.cs
new file mode 100644
index 00000000..5f5d1966
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/IInventoryGrainProvider.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+public interface IInventoryGrainProvider
+{
+ Task GetAsync(string grainKey);
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/InventoryGrainProvider.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/InventoryGrainProvider.cs
new file mode 100644
index 00000000..0ef6d00f
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/InventoryGrainProvider.cs
@@ -0,0 +1,21 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using Orleans;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+public class InventoryGrainProvider : IInventoryGrainProvider, ITransientDependency
+{
+ private readonly IGrainFactory _grainFactory;
+
+ public InventoryGrainProvider(IGrainFactory grainFactory)
+ {
+ _grainFactory = grainFactory;
+ }
+
+ public virtual Task GetAsync(string grainKey)
+ {
+ return Task.FromResult(_grainFactory.GetGrain(grainKey));
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/OrleansGrainsProductInventoryProvider.cs b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/OrleansGrainsProductInventoryProvider.cs
new file mode 100644
index 00000000..54a95b54
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/EasyAbp/EShop/Products/OrleansGrainsInventory/OrleansGrainsProductInventoryProvider.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using EasyAbp.EShop.Products.ProductInventories;
+using Microsoft.Extensions.Logging;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory;
+
+public class OrleansGrainsProductInventoryProvider : IProductInventoryProvider, ITransientDependency
+{
+ public static string OrleansGrainsProductInventoryProviderName { get; set; } = "OrleansGrains";
+ public static string OrleansGrainsProductInventoryProviderDisplayName { get; set; } = "OrleansGrains";
+ public static string OrleansGrainsProductInventoryProviderDescription { get; set; } = "OrleansGrains";
+
+ public string InventoryProviderName { get; } = OrleansGrainsProductInventoryProviderName;
+
+ private readonly ILogger _logger;
+ protected IInventoryGrainProvider InventoryGrainProvider { get; }
+
+ public OrleansGrainsProductInventoryProvider(
+ IInventoryGrainProvider inventoryGrainProvider,
+ ILogger logger)
+ {
+ InventoryGrainProvider = inventoryGrainProvider;
+ _logger = logger;
+ }
+
+ public virtual async Task GetInventoryDataAsync(InventoryQueryModel model)
+ {
+ var grain = await GetGrainAsync(model);
+
+ var stateModel = await grain.GetInventoryStateAsync();
+
+ return new InventoryDataModel
+ {
+ Inventory = stateModel.Inventory,
+ Sold = stateModel.Sold
+ };
+ }
+
+ public virtual async Task> GetSkuIdInventoryDataMappingAsync(
+ IList models)
+ {
+ var result = new Dictionary();
+
+ foreach (var model in models)
+ {
+ result.Add(model.ProductSkuId, await GetInventoryDataAsync(model));
+ }
+
+ return result;
+ }
+
+ public virtual async Task TryIncreaseInventoryAsync(InventoryQueryModel model, int quantity,
+ bool decreaseSold)
+ {
+ var grain = await GetGrainAsync(model);
+
+ try
+ {
+ await grain.IncreaseInventoryAsync(quantity, decreaseSold);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Grain threw: {Message}", e.Message);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public virtual async Task TryReduceInventoryAsync(InventoryQueryModel model, int quantity, bool increaseSold)
+ {
+ var grain = await GetGrainAsync(model);
+
+ var stateModel = await grain.GetInventoryStateAsync();
+
+ if (stateModel.Inventory < quantity)
+ {
+ return false;
+ }
+
+ try
+ {
+ await grain.ReduceInventoryAsync(quantity, increaseSold);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Grain threw: {Message}", e.Message);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ protected virtual async Task GetGrainAsync(InventoryQueryModel model)
+ {
+ return await InventoryGrainProvider.GetAsync(GetGrainId(model));
+ }
+
+ protected virtual string GetGrainId(InventoryQueryModel model)
+ {
+ return $"eshop_inventory_{(model.TenantId.HasValue ? model.TenantId.Value : "host")}_{model.ProductSkuId}";
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xml b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xml
new file mode 100644
index 00000000..1715698c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xsd b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xsd
new file mode 100644
index 00000000..ffa6fc4b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/src/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EShopProductsOrleansGrainsInventoryDomainTestModule.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EShopProductsOrleansGrainsInventoryDomainTestModule.cs
new file mode 100644
index 00000000..38074ed7
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EShopProductsOrleansGrainsInventoryDomainTestModule.cs
@@ -0,0 +1,16 @@
+using Volo.Abp;
+using Volo.Abp.Authorization;
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+[DependsOn(
+ typeof(AbpAutofacModule),
+ typeof(AbpTestBaseModule),
+ typeof(AbpAuthorizationModule),
+ typeof(EShopProductsOrleansGrainsInventoryDomainModule)
+)]
+public class EShopProductsOrleansGrainsInventoryDomainTestModule : AbpModule
+{
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj
new file mode 100644
index 00000000..ee834e1b
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ EasyAbp.EShop.Products.OrleansGrainsInventory.Domain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/FakeInventoryGrain.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/FakeInventoryGrain.cs
new file mode 100644
index 00000000..49f7199a
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/FakeInventoryGrain.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+public class FakeInventoryGrain : IInventoryGrain, ITransientDependency
+{
+ private InventoryStateModel StateModel { get; } = new()
+ {
+ Inventory = 100,
+ Sold = 0
+ };
+
+ public Task GetInventoryStateAsync()
+ {
+ return Task.FromResult(StateModel);
+ }
+
+ public Task IncreaseInventoryAsync(int quantity, bool decreaseSold)
+ {
+ StateModel.Inventory += quantity;
+
+ if (decreaseSold)
+ {
+ StateModel.Sold -= quantity;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public Task ReduceInventoryAsync(int quantity, bool increaseSold)
+ {
+ StateModel.Inventory -= quantity;
+
+ if (increaseSold)
+ {
+ StateModel.Sold += quantity;
+ }
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/OrleansGrainsProductInventoryProviderTests.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/OrleansGrainsProductInventoryProviderTests.cs
new file mode 100644
index 00000000..3570b22c
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/OrleansGrainsProductInventoryProviderTests.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using EasyAbp.EShop.Products.ProductInventories;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using Xunit;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+public class OrleansGrainsProductInventoryProviderTests : ProductsOrleansGrainsInventoryTestBase
+{
+ [Fact]
+ public async Task Should_Get_Inventory()
+ {
+ var inventoryProvider = ServiceProvider.GetRequiredService();
+
+ var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(100);
+ inventoryDataModel.Sold.ShouldBe(0);
+ }
+
+ [Fact]
+ public async Task Should_Change_Inventory()
+ {
+ var inventoryProvider = ServiceProvider.GetRequiredService();
+
+ var result = await inventoryProvider.TryReduceInventoryAsync(new InventoryQueryModel(), 2, true);
+
+ result.ShouldBeTrue();
+
+ var inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(98);
+ inventoryDataModel.Sold.ShouldBe(2);
+
+ result = await inventoryProvider.TryIncreaseInventoryAsync(new InventoryQueryModel(), 1, true);
+
+ result.ShouldBeTrue();
+
+ inventoryDataModel = await inventoryProvider.GetInventoryDataAsync(new InventoryQueryModel());
+
+ inventoryDataModel.ShouldNotBeNull();
+ inventoryDataModel.Inventory.ShouldBe(99);
+ inventoryDataModel.Sold.ShouldBe(1);
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/ProductsOrleansGrainsInventoryTestBase.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/ProductsOrleansGrainsInventoryTestBase.cs
new file mode 100644
index 00000000..1830da9d
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/ProductsOrleansGrainsInventoryTestBase.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp;
+using Volo.Abp.Modularity;
+using Volo.Abp.Testing;
+using Volo.Abp.Uow;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain
+{
+ /* All test classes are derived from this class, directly or indirectly. */
+ public abstract class
+ ProductsOrleansGrainsInventoryTestBase : AbpIntegratedTest
+ {
+ protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
+ {
+ options.UseAutofac();
+ }
+
+ protected virtual Task WithUnitOfWorkAsync(Func func)
+ {
+ return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
+ }
+
+ protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options, Func action)
+ {
+ using (var scope = ServiceProvider.CreateScope())
+ {
+ var uowManager = scope.ServiceProvider.GetRequiredService();
+
+ using (var uow = uowManager.Begin(options))
+ {
+ await action();
+
+ await uow.CompleteAsync();
+ }
+ }
+ }
+
+ protected virtual Task WithUnitOfWorkAsync(Func> func)
+ {
+ return WithUnitOfWorkAsync(new AbpUnitOfWorkOptions(), func);
+ }
+
+ protected virtual async Task WithUnitOfWorkAsync(AbpUnitOfWorkOptions options,
+ Func> func)
+ {
+ using (var scope = ServiceProvider.CreateScope())
+ {
+ var uowManager = scope.ServiceProvider.GetRequiredService();
+
+ using (var uow = uowManager.Begin(options))
+ {
+ var result = await func();
+ await uow.CompleteAsync();
+ return result;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/TestInventoryGrainProvider.cs b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/TestInventoryGrainProvider.cs
new file mode 100644
index 00000000..42c9990e
--- /dev/null
+++ b/plugins/Inventories/OrleansGrains/test/EasyAbp.EShop.Products.OrleansGrainsInventory.Domain.Tests/TestInventoryGrainProvider.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading.Tasks;
+using EasyAbp.EShop.Plugins.Inventories.OrleansGrains;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.DependencyInjection;
+
+namespace EasyAbp.EShop.Products.OrleansGrainsInventory.Domain;
+
+[Dependency(ReplaceServices = true)]
+public class TestInventoryGrainProvider : IInventoryGrainProvider, ITransientDependency
+{
+ private IInventoryGrain Grain { get; set; }
+
+ private readonly IServiceProvider _serviceProvider;
+
+ public TestInventoryGrainProvider(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
+
+ public Task GetAsync(string grainKey)
+ {
+ return Task.FromResult(Grain ??= _serviceProvider.GetRequiredService());
+ }
+}
\ No newline at end of file