From d94f37479cb427d1dc7baf8b3fb181bf08f47228 Mon Sep 17 00:00:00 2001 From: dingsongjie Date: Wed, 19 Feb 2020 17:57:59 +0800 Subject: [PATCH] 完成 efcore 与 orleans 结合的全部测试 --- samples/apis/Backet.Api/Controllers/BacketController.cs | 18 ++++++++++++++++-- samples/apis/Backet.Api/Domain/AggregatesModel/BacketAggregate/Backet.cs | 4 ++++ samples/apis/Backet.Api/Grains/Abstraction/IBacketGrain.cs | 2 ++ samples/apis/Backet.Api/Grains/BacketGrain.cs | 20 ++++++++++++++++++++ samples/apis/Backet.Api/Startup.cs | 4 ++-- samples/apis/Product.Api/Apis/DefaultController.cs | 16 ++++++++++++++++ src/Pole.Application/EventBus/IBus.cs | 13 +++++++++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Context/ConfigureEntryStateDelegate.cs | 7 +++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Context/GrainStorageContext.cs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GetCompoundKeyDelegate.cs | 7 +++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GrainStorageConvention.cs | 30 +++++++++++++++++------------- src/Pole.Orleans.Provider.EntityframeworkCore/DefaultGrainStateEntryConfigurator.cs | 17 +++++++++++++---- src/Pole.Orleans.Provider.EntityframeworkCore/Exceptions/GrainStorageConfigurationException.cs | 20 ++++++++++++++++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageOptionsExtensions.cs | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageServiceCollectionExtensions.cs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageSiloHostBuilderExtensions.cs | 39 +++++++++++++++++++++++++++++++++++++++ src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorage.cs | 37 +++++++++++++++++++++++++++++-------- src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorageOptionsExtensions.cs | 2 +- src/Pole.Orleans.Provider.EntityframeworkCore/ReadWriteStateAsyncDelegate.cs | 8 ++++++++ 19 files changed, 587 insertions(+), 30 deletions(-) create mode 100644 samples/apis/Product.Api/Apis/DefaultController.cs create mode 100644 src/Pole.Application/EventBus/IBus.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Context/ConfigureEntryStateDelegate.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Context/GrainStorageContext.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GetCompoundKeyDelegate.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Exceptions/GrainStorageConfigurationException.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageOptionsExtensions.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageServiceCollectionExtensions.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageSiloHostBuilderExtensions.cs create mode 100644 src/Pole.Orleans.Provider.EntityframeworkCore/ReadWriteStateAsyncDelegate.cs diff --git a/samples/apis/Backet.Api/Controllers/BacketController.cs b/samples/apis/Backet.Api/Controllers/BacketController.cs index e288b54..489c80a 100644 --- a/samples/apis/Backet.Api/Controllers/BacketController.cs +++ b/samples/apis/Backet.Api/Controllers/BacketController.cs @@ -19,12 +19,26 @@ namespace Backet.Api.Controllers this.clusterClient = clusterClient; } [HttpPost("api/backet/AddBacket")] - public void AddBacket([FromBody]Backet.Api.Grains.Abstraction.BacketDto backet) + public Task AddBacket([FromBody]Backet.Api.Grains.Abstraction.BacketDto backet) { var newId = "da8a489fa7b4409294ee1b358fbbfba5"; backet.Id = newId; var grain = clusterClient.GetGrain(newId); - grain.AddBacket(backet); + return grain.AddBacket(backet); + } + [HttpPost("api/backet/UpdateBacket")] + public Task UpdateBacket() + { + var id = "da8a489fa7b4409294ee1b358fbbfba5"; + var grain = clusterClient.GetGrain(id); + return grain.UpdateBacket("88"); + } + [HttpPost("api/backet/AddItem")] + public Task AddItem() + { + var id = "da8a489fa7b4409294ee1b358fbbfba5"; + var grain = clusterClient.GetGrain(id); + return grain.AddBacketItem("55","测试3",1000); } } } \ No newline at end of file diff --git a/samples/apis/Backet.Api/Domain/AggregatesModel/BacketAggregate/Backet.cs b/samples/apis/Backet.Api/Domain/AggregatesModel/BacketAggregate/Backet.cs index 9f76529..731702b 100644 --- a/samples/apis/Backet.Api/Domain/AggregatesModel/BacketAggregate/Backet.cs +++ b/samples/apis/Backet.Api/Domain/AggregatesModel/BacketAggregate/Backet.cs @@ -20,6 +20,10 @@ namespace Backet.Api.Domain.AggregatesModel.BacketAggregate BacketItems.Add(backetItem); SetBacketTotalPrice(); } + public void ModifyItemProductId(string productId) + { + BacketItems.ForEach(m => m.ProductId = productId); + } private void SetBacketTotalPrice() { foreach (var item in BacketItems) diff --git a/samples/apis/Backet.Api/Grains/Abstraction/IBacketGrain.cs b/samples/apis/Backet.Api/Grains/Abstraction/IBacketGrain.cs index 94f369d..8b67a97 100644 --- a/samples/apis/Backet.Api/Grains/Abstraction/IBacketGrain.cs +++ b/samples/apis/Backet.Api/Grains/Abstraction/IBacketGrain.cs @@ -9,6 +9,8 @@ namespace Backet.Api.Grains.Abstraction public interface IBacketGrain: IGrainWithStringKey { Task AddBacket(BacketDto backet); + Task UpdateBacket(string userId); + Task AddBacketItem(string productId, string productName, long price); } public class BacketItemDto { diff --git a/samples/apis/Backet.Api/Grains/BacketGrain.cs b/samples/apis/Backet.Api/Grains/BacketGrain.cs index 7bfab62..3fe5493 100644 --- a/samples/apis/Backet.Api/Grains/BacketGrain.cs +++ b/samples/apis/Backet.Api/Grains/BacketGrain.cs @@ -26,5 +26,25 @@ namespace Backet.Api.Grains await WriteStateAsync(); return true; } + + public async Task AddBacketItem(string productId,string productName,long price) + { + if (State == null) return false; + + State.AddBacketItem(productId, productName, price); + Update(State); + await WriteStateAsync(); + return true; + } + + public async Task UpdateBacket(string userId) + { + if (State == null) return false; + State.UserId = userId; + State.ModifyItemProductId(userId); + Update(State); + await WriteStateAsync(); + return true; + } } } diff --git a/samples/apis/Backet.Api/Startup.cs b/samples/apis/Backet.Api/Startup.cs index 19383aa..ca4f1e5 100644 --- a/samples/apis/Backet.Api/Startup.cs +++ b/samples/apis/Backet.Api/Startup.cs @@ -34,8 +34,8 @@ namespace Backet.Api services.ConfigureGrainStorageOptions( options => options - .UseQuery(context => context.Backets.AsNoTracking() - //.Include(box => box.BacketItems) + .UseQuery(context => context.Backets + .Include(box => box.BacketItems) )); diff --git a/samples/apis/Product.Api/Apis/DefaultController.cs b/samples/apis/Product.Api/Apis/DefaultController.cs new file mode 100644 index 0000000..a814874 --- /dev/null +++ b/samples/apis/Product.Api/Apis/DefaultController.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Product.Api.Apis +{ + public class DefaultController : Controller + { + public IActionResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/src/Pole.Application/EventBus/IBus.cs b/src/Pole.Application/EventBus/IBus.cs new file mode 100644 index 0000000..8698425 --- /dev/null +++ b/src/Pole.Application/EventBus/IBus.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Pole.Application.EventBus +{ + public interface IBus + { + Task Publish(TReliableEvent @event, object callbackParemeter, CancellationToken cancellationToken = default) where TReliableEvent : class; + } +} diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Context/ConfigureEntryStateDelegate.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Context/ConfigureEntryStateDelegate.cs new file mode 100644 index 0000000..36fefda --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Context/ConfigureEntryStateDelegate.cs @@ -0,0 +1,7 @@ +using Microsoft.EntityFrameworkCore.ChangeTracking; + +namespace Orleans.Providers.EntityFramework +{ + public delegate void ConfigureEntryStateDelegate(EntityEntry entry) + where TGrainState : class; +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Context/GrainStorageContext.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Context/GrainStorageContext.cs new file mode 100644 index 0000000..757537d --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Context/GrainStorageContext.cs @@ -0,0 +1,42 @@ +using System.Threading; + +namespace Orleans.Providers.EntityFramework +{ + /// + /// An async local context to apply modifications to current behavior of write and clear operations. + /// + /// + public static class GrainStorageContext + where TEntity : class + { + // ReSharper disable once StaticMemberInGenericType + private static readonly AsyncLocal IsConfiguredLocal + = new AsyncLocal(); + + private static readonly AsyncLocal> + ConfigureStateDelegateLocal + = new AsyncLocal>(); + + internal static bool IsConfigured => IsConfiguredLocal.Value; + + internal static ConfigureEntryStateDelegate ConfigureStateDelegate + => ConfigureStateDelegateLocal.Value; + + /// + /// Configures the entry state. + /// Use it to modify what gets changed during the write operations. + /// + /// The delegate to be called before saving context's state. + public static void ConfigureEntryState(ConfigureEntryStateDelegate configureState) + { + ConfigureStateDelegateLocal.Value = configureState; + IsConfiguredLocal.Value = true; + } + + public static void Clear() + { + ConfigureStateDelegateLocal.Value = null; + IsConfiguredLocal.Value = false; + } + } +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GetCompoundKeyDelegate.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GetCompoundKeyDelegate.cs new file mode 100644 index 0000000..f2b2052 --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GetCompoundKeyDelegate.cs @@ -0,0 +1,7 @@ +using System; +using Orleans.Runtime; + +namespace Orleans.Providers.EntityFramework.Conventions +{ + public delegate ValueType GetCompoundKeyDelegate(IAddressable addressable, out string keyExt); +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GrainStorageConvention.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GrainStorageConvention.cs index 8b1a52d..f1fcee9 100644 --- a/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GrainStorageConvention.cs +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Conventions/GrainStorageConvention.cs @@ -259,17 +259,9 @@ namespace Pole.Orleans.Provider.EntityframeworkCore.Conventions throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions).FullName}"); Func> compiledQuery = null; - if (options.DbSetAccessor != null) - { - var predicate = CreateKeyPredicate(options); - compiledQuery = EF.CompileAsyncQuery((TContext context, string grainKey) - => options.DbSetAccessor(context) - .SingleOrDefault(predicate)); - } - else - { - compiledQuery = CreateCompiledQuery(options); - } + + compiledQuery = CreateCompiledQuery(options); + @@ -415,16 +407,28 @@ namespace Pole.Orleans.Provider.EntityframeworkCore.Conventions var keyParameter = Expression.Parameter(typeof(TKey), "grainKey"); var predicate = CreateKeyPredicate(options, keyParameter); - var queryable = Expression.Call( + MethodCallExpression queryable = null; + + if (options.DbSetAccessor.Method.IsStatic) + { + queryable = Expression.Call( options.DbSetAccessor.Method, Expression.Constant(options.DbSetAccessor), contextParameter); + } + else + { + queryable = Expression.Call( + Expression.Constant(options.DbSetAccessor.Target), + options.DbSetAccessor.Method, + contextParameter); + } var compiledLambdaBody = Expression.Call( typeof(Queryable).GetMethods().Single(mi => mi.Name == nameof(Queryable.SingleOrDefault) && mi.GetParameters().Count() == 2) .MakeGenericMethod(typeof(TEntity)), - queryable, + queryable, Expression.Quote(predicate)); var lambdaExpression = Expression.Lambda>( diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/DefaultGrainStateEntryConfigurator.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/DefaultGrainStateEntryConfigurator.cs index 3a36a79..30b850d 100644 --- a/src/Pole.Orleans.Provider.EntityframeworkCore/DefaultGrainStateEntryConfigurator.cs +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/DefaultGrainStateEntryConfigurator.cs @@ -13,11 +13,20 @@ namespace Pole.Orleans.Provider.EntityframeworkCore { public void ConfigureSaveEntry(ConfigureSaveEntryContext context) { - EntityEntry entry = context.DbContext.Entry(context.Entity); + if (context.IsPersisted) + { + // todo update necessary table + //EntityEntry entry = context.DbContext.Set().Update(context.Entity); + } + else + { + EntityEntry entry = context.DbContext.Set().Add(context.Entity); + } - entry.State = context.IsPersisted - ? EntityState.Modified - : EntityState.Added; + + //entry.State = context.IsPersisted + // ? EntityState.Modified + // : EntityState.Added; } } } diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Exceptions/GrainStorageConfigurationException.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Exceptions/GrainStorageConfigurationException.cs new file mode 100644 index 0000000..58faade --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Exceptions/GrainStorageConfigurationException.cs @@ -0,0 +1,20 @@ +using System; + +namespace Orleans.Providers.EntityFramework.Exceptions +{ + // todo: Use for configuration errors + public class GrainStorageConfigurationException : Exception + { + public GrainStorageConfigurationException() + { + } + + public GrainStorageConfigurationException(string message) : base(message) + { + } + + public GrainStorageConfigurationException(string message, Exception innerException) : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageOptionsExtensions.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageOptionsExtensions.cs new file mode 100644 index 0000000..245d05f --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageOptionsExtensions.cs @@ -0,0 +1,261 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Orleans.Runtime; +using Pole.Orleans.Provider.EntityframeworkCore; + +namespace Orleans.Providers.EntityFramework.Extensions +{ + public static class GrainStorageOptionsExtensions + { + public static GrainStorageOptions UseQuery( + this GrainStorageOptions options, + Func> queryFunc) + where TContext : DbContext + where TGrainState : class + { + options.DbSetAccessor = queryFunc; + return options; + } + + public static GrainStorageOptions ConfigureIsPersisted( + this GrainStorageOptions options, + Func isPersistedFunc) + where TContext : DbContext + where TGrainState : class + { + options.IsPersistedFunc = isPersistedFunc; + return options; + } + + /// + /// Instructs the storage provider to precompile read query. + /// This will lead to better performance for complex queries. + /// Default is to precompile. + /// + /// + /// + /// + /// + /// + /// + public static GrainStorageOptions PreCompileReadQuery( + this GrainStorageOptions options, + bool value = true) + where TContext : DbContext + where TGrainState : class + { + options.PreCompileReadQuery = value; + return options; + } + + + /// + /// Overrides the default implementation used to query grain state from database. + /// + /// + /// + /// + /// + /// + /// + public static GrainStorageOptions ConfigureReadState( + this GrainStorageOptions options, + Func> readStateAsyncFunc) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + + options.ReadStateAsync = readStateAsyncFunc ?? throw new ArgumentNullException(nameof(readStateAsyncFunc)); + return options; + } + + + /// + /// Instruct the storage that the current entity should use etags. + /// If no valid properties were found on the entity and exception would be thrown. + /// + /// + /// + /// + /// + /// + public static GrainStorageOptions UseETag( + this GrainStorageOptions options) + where TContext : DbContext + where TGrainState : class + { + options.ShouldUseETag = true; + return options; + } + + public static GrainStorageOptions UseETag( + this GrainStorageOptions options, + Expression> expression) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + var memberExpression = expression.Body as MemberExpression + ?? throw new ArgumentException( + $"{nameof(expression)} must be a MemberExpression."); + + options.ETagPropertyName = memberExpression.Member.Name; + options.ShouldUseETag = true; + + return options; + } + + public static GrainStorageOptions UseETag( + this GrainStorageOptions options, + string propertyName) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + + options.ETagPropertyName = propertyName; + options.ShouldUseETag = true; + + return options; + } + + public static GrainStorageOptions UseKey( + this GrainStorageOptions options, + Expression> expression) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + var memberExpression = expression.Body as MemberExpression + ?? throw new ArgumentException( + $"{nameof(expression)} must be a MemberExpression."); + + options.KeyPropertyName = memberExpression.Member.Name; + + return options; + } + + public static GrainStorageOptions UseKey( + this GrainStorageOptions options, + Expression> expression) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + var memberExpression = expression.Body as MemberExpression + ?? throw new GrainStorageConfigurationException( + $"{nameof(expression)} must be a MemberExpression."); + + options.KeyPropertyName = memberExpression.Member.Name; + + return options; + } + + public static GrainStorageOptions UseKey( + this GrainStorageOptions options, + Expression> expression) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + var memberExpression = expression.Body as MemberExpression + ?? throw new ArgumentException( + $"{nameof(expression)} must be a MemberExpression."); + + options.KeyPropertyName = memberExpression.Member.Name; + + return options; + } + + public static GrainStorageOptions UseKey( + this GrainStorageOptions options, + string propertyName) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + + options.KeyPropertyName = propertyName; + + return options; + } + + public static GrainStorageOptions UseKeyExt( + this GrainStorageOptions options, + Expression> expression) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + var memberExpression = expression.Body as MemberExpression + ?? throw new ArgumentException( + $"{nameof(expression)} must be a MemberExpression."); + + options.KeyExtPropertyName = memberExpression.Member.Name; + + return options; + } + + public static GrainStorageOptions UseKeyExt( + this GrainStorageOptions options, + string propertyName) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + + options.KeyExtPropertyName = propertyName; + + return options; + } + + public static GrainStorageOptions CheckPersistenceOn( + this GrainStorageOptions options, + Expression> expression) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (expression == null) throw new ArgumentNullException(nameof(expression)); + + var memberExpression = expression.Body as MemberExpression + ?? throw new ArgumentException( + $"{nameof(expression)} must be a MemberExpression."); + + options.PersistenceCheckPropertyName = memberExpression.Member.Name; + + return options; + } + + public static GrainStorageOptions CheckPersistenceOn( + this GrainStorageOptions options, + string propertyName) + where TContext : DbContext + where TGrainState : class + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); + + options.PersistenceCheckPropertyName = propertyName; + + return options; + } + } +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageServiceCollectionExtensions.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageServiceCollectionExtensions.cs new file mode 100644 index 0000000..c8d0d6a --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageServiceCollectionExtensions.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Orleans.Providers.EntityFramework.Conventions; +using Orleans.Runtime; +using Orleans.Storage; +using Pole.Orleans.Provider.EntityframeworkCore; +using Pole.Orleans.Provider.EntityframeworkCore.Conventions; + +namespace Orleans.Providers.EntityFramework.Extensions +{ + public static class GrainStorageServiceCollectionExtensions + { + public static IServiceCollection ConfigureGrainStorageOptions( + this IServiceCollection services, + Action> configureOptions = null) + where TContext : DbContext + where TGrain : Grain + where TEntity : class, new() + { + return services + .AddSingleton>, + GrainStoragePostConfigureOptions>() + .Configure>(typeof(TGrain).FullName, options => + { + configureOptions?.Invoke(options); + }); + } + + public static IServiceCollection ConfigureGrainStorageOptions( + this IServiceCollection services, + Action> configureOptions = null) + where TContext : DbContext + where TGrain : Grain + where TGrainState : new() + where TEntity : class + { + return services + .AddSingleton>, + GrainStoragePostConfigureOptions>() + .Configure>(typeof(TGrain).FullName, options => + { + configureOptions?.Invoke(options); + }); + } + + public static IServiceCollection AddEfGrainStorage( + this IServiceCollection services, + string providerName = ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME) + where TContext : DbContext + { + services.TryAddSingleton(typeof(IEntityTypeResolver), typeof(EntityTypeResolver)); + services.TryAddSingleton(typeof(IGrainStorageConvention), typeof(GrainStorageConvention)); + services.TryAddSingleton(typeof(IGrainStateEntryConfigurator<,,>), + typeof(DefaultGrainStateEntryConfigurator<,,>)); + services.AddSingleton(typeof(EntityFrameworkGrainStorage)); + + services.TryAddSingleton(sp => + sp.GetServiceByName(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)); + + services.AddSingletonNamedService(providerName, + (sp, name) => sp.GetRequiredService>()); + + return services; + } + + } +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageSiloHostBuilderExtensions.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageSiloHostBuilderExtensions.cs new file mode 100644 index 0000000..d5935f3 --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/Extensions/GrainStorageSiloHostBuilderExtensions.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using Orleans.Hosting; + +namespace Orleans.Providers.EntityFramework.Extensions +{ + public static class GrainStorageSiloHostBuilderExtensions + { + public static ISiloHostBuilder AddEfGrainStorageAsDefault(this ISiloHostBuilder builder) + where TContext : DbContext + { + return builder.AddEfGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME); + } + + public static ISiloHostBuilder AddEfGrainStorage(this ISiloHostBuilder builder, + string providerName) + where TContext : DbContext + { + + return builder + .ConfigureServices(services => + { + services.AddEfGrainStorage(providerName); + }); + } + public static ISiloBuilder AddEfGrainStorageAsDefault(this ISiloBuilder builder) + where TContext : DbContext + { + return builder.AddEfGrainStorage(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME); + } + + public static ISiloBuilder AddEfGrainStorage(this ISiloBuilder builder, + string providerName) + where TContext : DbContext + { + return builder + .ConfigureServices(services => { services.AddEfGrainStorage(providerName); }); + } + } +} \ No newline at end of file diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorage.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorage.cs index aaf7c9f..7169b0d 100644 --- a/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorage.cs +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorage.cs @@ -7,9 +7,12 @@ using Microsoft.Extensions.Options; using Orleans; using Orleans.Runtime; using Orleans.Storage; +using Pole.Core.Domain; +using Pole.Core.EventBus.Event; using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; @@ -19,7 +22,7 @@ namespace Pole.Orleans.Provider.EntityframeworkCore where TContext : DbContext where TGrain : Grain where TGrainState : class, new() - where TEntity : class + where TEntity : Entity { private readonly GrainStorageOptions _options; private readonly IServiceScopeFactory _scopeFactory; @@ -75,13 +78,18 @@ namespace Pole.Orleans.Provider.EntityframeworkCore else { bool isPersisted = _options.IsPersistedFunc(entity); + if (isPersisted) + { + TEntity entityInDb = await _options.ReadStateAsync(context, grainReference) + .ConfigureAwait(false); + Copy(entity, entityInDb); + } + else + { + context.Set().Add(entity); + } + - _entryConfigurator.ConfigureSaveEntry( - new ConfigureSaveEntryContext( - context, entity) - { - IsPersisted = isPersisted - }); } try @@ -118,7 +126,20 @@ namespace Pole.Orleans.Provider.EntityframeworkCore } } - + public static void Copy(T from, T to) where T : Entity + { + if (ReferenceEquals(from, null)) + throw new ArgumentNullException("from"); + if (ReferenceEquals(to, null)) + throw new ArgumentNullException("to"); + Type type = from.GetType(); + PropertyInfo[] Properties = type.GetProperties(); + foreach (PropertyInfo p in Properties) + { + if (p.Name == "DomainEvents" || p.Name == "Id") continue; + p.SetValue(to, p.GetValue(from)); + } + } private GrainStorageOptions GetOrCreateDefaultOptions(string grainType) { var options diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorageOptionsExtensions.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorageOptionsExtensions.cs index 49f9b34..b7ea6fd 100644 --- a/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorageOptionsExtensions.cs +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/GrainStorageOptionsExtensions.cs @@ -13,7 +13,7 @@ namespace Pole.Orleans.Provider.EntityframeworkCore { public static GrainStorageOptions UseQuery( this GrainStorageOptions options, - Func> queryFunc) + Func>queryFunc) where TContext : DbContext where TGrainState : class { diff --git a/src/Pole.Orleans.Provider.EntityframeworkCore/ReadWriteStateAsyncDelegate.cs b/src/Pole.Orleans.Provider.EntityframeworkCore/ReadWriteStateAsyncDelegate.cs new file mode 100644 index 0000000..e816f00 --- /dev/null +++ b/src/Pole.Orleans.Provider.EntityframeworkCore/ReadWriteStateAsyncDelegate.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; +using Orleans.Runtime; + +namespace Orleans.Providers.EntityFramework +{ + internal delegate Task ReadWriteStateAsyncDelegate(string grainType, GrainReference grainReference, + IGrainState grainState, object storageOptions); +} \ No newline at end of file -- libgit2 0.25.0