using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; 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; namespace Pole.Orleans.Provider.EntityframeworkCore { internal class GrainStorage : IGrainStorage where TContext : DbContext where TGrain : Grain where TGrainState : class, new() where TEntity : Entity { private readonly GrainStorageOptions _options; private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger> _logger; private readonly IServiceProvider _serviceProvider; public GrainStorage(string grainType, IServiceProvider serviceProvider) { if (grainType == null) throw new ArgumentNullException(nameof(grainType)); _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); var loggerFactory = _serviceProvider.GetService(); _logger = loggerFactory?.CreateLogger>() ?? NullLogger>.Instance; _scopeFactory = serviceProvider.GetRequiredService(); _options = GetOrCreateDefaultOptions(grainType); } public async Task ReadStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { using (IServiceScope scope = _scopeFactory.CreateScope()) using (var context = scope.ServiceProvider.GetRequiredService()) { TEntity entity = await _options.ReadStateAsync(context, grainReference) .ConfigureAwait(false); _options.SetEntity(grainState, entity); if (entity != null && _options.CheckForETag) grainState.ETag = _options.GetETagFunc(entity); } } public async Task WriteStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { TEntity entity = _options.GetEntity(grainState); using (IServiceScope scope = _scopeFactory.CreateScope()) using (var context = scope.ServiceProvider.GetRequiredService()) { if (GrainStorageContext.IsConfigured) { EntityEntry entry = context.Entry(entity); GrainStorageContext.ConfigureStateDelegate(entry); } else { bool isPersisted = _options.IsPersistedFunc(entity); if (isPersisted) { if (_options.IsRelatedData) { TEntity entityInDb = await _options.ReadStateAsync(context, grainReference) .ConfigureAwait(false); Copy(entity, entityInDb); } else { context.Entry(entity).State = EntityState.Modified; } } else { context.Set().Add(entity); } } try { await context.SaveChangesAsync() .ConfigureAwait(false); if (_options.CheckForETag) grainState.ETag = _options.GetETagFunc(entity); } catch (DbUpdateConcurrencyException e) { if (!_options.CheckForETag) throw new InconsistentStateException(e.Message, e); object storedETag = e.Entries.First().OriginalValues[_options.ETagProperty]; throw new InconsistentStateException(e.Message, _options.ConvertETagObjectToStringFunc(storedETag), grainState.ETag, e); } } } public async Task ClearStateAsync(string grainType, GrainReference grainReference, IGrainState grainState) { TEntity entity = _options.GetEntity(grainState); using (IServiceScope scope = _scopeFactory.CreateScope()) using (var context = scope.ServiceProvider.GetRequiredService()) { context.Remove(entity); await context.SaveChangesAsync() .ConfigureAwait(false); } } 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 = _serviceProvider.GetOptionsByName>(grainType); if (options.IsConfigured) return options; // Try generating a default options for the grain Type optionsType = typeof(GrainStoragePostConfigureOptions<,,,>) .MakeGenericType( typeof(TContext), typeof(TGrain), typeof(TGrainState), typeof(TEntity)); var postConfigure = (IPostConfigureOptions>) Activator.CreateInstance(optionsType, _serviceProvider); postConfigure.PostConfigure(grainType, options); _logger.LogInformation($"GrainStorageOptions is not configured for grain {grainType} " + "and default options will be used. If default configuration is not desired, " + "consider configuring options for grain using " + "using IServiceCollection.ConfigureGrainStorageOptions extension method."); return options; } } }