Commit d827c767 by 丁松杰

添加 orleans.provider. entityframeworkcore 部分代码

parent 5de72aaf
Showing with 773 additions and 2 deletions
......@@ -47,9 +47,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Product.IntegrationEvents",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pole.EventBus.Rabbitmq", "src\Pole.EventBus.Rabbitmq\Pole.EventBus.Rabbitmq.csproj", "{BDF62A19-FFBD-4EE1-A07A-68472E680A95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pole.Core.Test", "test\Pole.Core.Test\Pole.Core.Test.csproj", "{23EA8735-DB2E-4599-8902-8FCBCBE4799C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pole.Core.Test", "test\Pole.Core.Test\Pole.Core.Test.csproj", "{23EA8735-DB2E-4599-8902-8FCBCBE4799C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pole.EventStorage.PostgreSql", "src\Pole.EventStorage.PostgreSql\Pole.EventStorage.PostgreSql.csproj", "{548EFDBB-252F-48DD-87F4-58ABFBD4963C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pole.EventStorage.PostgreSql", "src\Pole.EventStorage.PostgreSql\Pole.EventStorage.PostgreSql.csproj", "{548EFDBB-252F-48DD-87F4-58ABFBD4963C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pole.Orleans.Provider.EntityframeworkCore", "src\Pole.Orleans.Provider.EntityframeworkCore\Pole.Orleans.Provider.EntityframeworkCore.csproj", "{0DA75F4A-BF47-4B52-B932-48BB6A709934}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
......@@ -129,6 +131,10 @@ Global
{548EFDBB-252F-48DD-87F4-58ABFBD4963C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{548EFDBB-252F-48DD-87F4-58ABFBD4963C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{548EFDBB-252F-48DD-87F4-58ABFBD4963C}.Release|Any CPU.Build.0 = Release|Any CPU
{0DA75F4A-BF47-4B52-B932-48BB6A709934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DA75F4A-BF47-4B52-B932-48BB6A709934}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DA75F4A-BF47-4B52-B932-48BB6A709934}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DA75F4A-BF47-4B52-B932-48BB6A709934}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -155,6 +161,7 @@ Global
{BDF62A19-FFBD-4EE1-A07A-68472E680A95} = {9932C965-8B38-4F70-9E43-86DC56860E2B}
{23EA8735-DB2E-4599-8902-8FCBCBE4799C} = {655E719B-4A3E-467C-A541-E0770AB81DE1}
{548EFDBB-252F-48DD-87F4-58ABFBD4963C} = {9932C965-8B38-4F70-9E43-86DC56860E2B}
{0DA75F4A-BF47-4B52-B932-48BB6A709934} = {9932C965-8B38-4F70-9E43-86DC56860E2B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DB0775A3-F293-4043-ADB7-72BAC081E87E}
......
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public delegate void ConfigureEntryStateDelegate<TGrainState>(EntityEntry<TGrainState> entry)
where TGrainState : class;
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public class ConfigureSaveEntryContext<TContext, TEntity>
{
public ConfigureSaveEntryContext(TContext dbContext, TEntity entity)
{
DbContext = dbContext;
Entity = entity;
}
public TContext DbContext { get; }
public TEntity Entity { get; }
public bool IsPersisted { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore.Conventions
{
public class GrainStorageConventionOptions
{
public string DefaultGrainKeyPropertyName { get; set; } = "Id";
public string DefaultGrainKeyExtPropertyName { get; set; } = "KeyExt";
public string DefaultPersistenceCheckPropertyName { get; set; } = "Id";
}
}
using Microsoft.EntityFrameworkCore;
using Orleans;
using Orleans.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace Pole.Orleans.Provider.EntityframeworkCore.Conventions
{
public interface IGrainStorageConvention
{
/// <summary>
/// Creates a method that returns an IQueryable'<typeparam name="TEntity"></typeparam>
/// against <typeparam name="TContext"></typeparam> type.
/// </summary>
/// <typeparam name="TContext"></typeparam>
/// <typeparam name="TEntity"></typeparam>
/// <returns></returns>
Func<TContext, IQueryable<TEntity>>
CreateDefaultDbSetAccessorFunc<TContext, TEntity>()
where TContext : DbContext
where TEntity : class;
Func<TContext, IAddressable, Task<TEntity>>
CreateDefaultReadStateFunc<TContext, TGrain, TEntity>(
GrainStorageOptions<TContext, TGrain, TEntity> options)
where TContext : DbContext
where TEntity : class;
Func<TContext, IAddressable, Task<TEntity>>
CreatePreCompiledDefaultReadStateFunc<TContext, TGrain, TEntity>(
GrainStorageOptions<TContext, TGrain, TEntity> options)
where TContext : DbContext
where TEntity : class;
void SetDefaultKeySelectors<TContext, TGrain, TEntity>(
GrainStorageOptions<TContext, TGrain, TEntity> options)
where TContext : DbContext
where TEntity : class;
// todo: support composite key grains
/// <summary>
/// Creates a method that determines if a state object is persisted in the database.
/// This is used to decide whether an insert or an update operation is needed.
/// </summary>
/// <param name="options"></param>
/// <typeparam name="TEntity"></typeparam>
/// <returns></returns>
Func<TEntity, bool> CreateIsPersistedFunc<TEntity>(GrainStorageOptions options)
where TEntity : class;
/// <summary>
/// Tries to find and configure an ETag property on the state model
/// </summary>
/// <param name="options"></param>
/// <param name="throwIfNotFound">Indicates if failure of finding an ETag property should throw</param>
/// <typeparam name="TContext"></typeparam>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TGrain"></typeparam>
void FindAndConfigureETag<TContext, TGrain, TEntity>(
GrainStorageOptions<TContext, TGrain, TEntity> options,
bool throwIfNotFound)
where TContext : DbContext
where TEntity : class;
/// <summary>
/// Configures the ETag property using the provided property name
/// </summary>
/// <param name="propertyName"></param>
/// <param name="options"></param>
/// <typeparam name="TContext"></typeparam>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TGrain"></typeparam>
void ConfigureETag<TContext, TGrain, TEntity>(
string propertyName,
GrainStorageOptions<TContext, TGrain, TEntity> options)
where TContext : DbContext
where TEntity : class;
Action<IGrainState, TEntity> GetSetterFunc<TGrainState, TEntity>()
where TEntity : class;
Func<IGrainState, TEntity> GetGetterFunc<TGrainState, TEntity>()
where TEntity : class;
}
public interface IGrainStorageConvention<TContext, TGrain, TEntity>
where TContext : DbContext
where TEntity : class
{
/// <summary>
/// Creates a method that returns an IQueryable'<typeparam name="TGrainState"></typeparam>
/// against <typeparam name="TContext"></typeparam> type.
/// </summary>
/// <typeparam name="TContext"></typeparam>
/// <typeparam name="TGrainState"></typeparam>
/// <returns></returns>
Func<TContext, IQueryable<TEntity>>
CreateDefaultDbSetAccessorFunc();
/// <summary>
/// Creates a method that generates an expression to be used by entity framework to
/// fetch a single state.
/// </summary>
/// <typeparam name="TGrainState">Type of grain state</typeparam>
/// <returns></returns>
Func<IAddressable, Expression<Func<TEntity, bool>>>
CreateGrainStateQueryExpressionGeneratorFunc();
Func<TContext, IAddressable, Task<TEntity>> CreateDefaultReadStateFunc();
Func<TContext, IAddressable, Task<TEntity>> CreatePreCompiledDefaultReadStateFunc(
GrainStorageOptions<TContext, TGrain, TEntity> options);
void SetDefaultKeySelector(GrainStorageOptions<TContext, TGrain, TEntity> options);
Action<IGrainState, TEntity> GetSetterFunc();
Func<IGrainState, TEntity> GetGetterFunc();
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public class DefaultGrainStateEntryConfigurator<TContext, TGrain, TEntity>
: IGrainStateEntryConfigurator<TContext, TGrain, TEntity>
where TContext : DbContext
where TEntity : class
{
public void ConfigureSaveEntry(ConfigureSaveEntryContext<TContext, TEntity> context)
{
EntityEntry<TEntity> entry = context.DbContext.Entry(context.Entity);
entry.State = context.IsPersisted
? EntityState.Modified
: EntityState.Added;
}
}
}
using Microsoft.EntityFrameworkCore;
using Orleans;
using Orleans.Runtime;
using Orleans.Storage;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public class EntityFrameworkGrainStorage<TContext> : IGrainStorage
where TContext : DbContext
{
private readonly IServiceProvider _serviceProvider;
private readonly ITypeResolver _typeResolver;
private readonly IEntityTypeResolver _entityTypeResolver;
private readonly ConcurrentDictionary<string, IGrainStorage> _storage
= new ConcurrentDictionary<string, IGrainStorage>();
public EntityFrameworkGrainStorage(
IServiceProvider serviceProvider,
ITypeResolver typeResolver,
IEntityTypeResolver entityTypeResolver)
{
_serviceProvider = serviceProvider;
_entityTypeResolver = entityTypeResolver;
_typeResolver = typeResolver;
}
public Task ReadStateAsync(string grainType, GrainReference grainReference, IGrainState grainState)
{
if (!_storage.TryGetValue(grainType, out IGrainStorage storage))
storage = CreateStorage(grainType, grainState);
return storage.ReadStateAsync(grainType, grainReference, grainState);
}
public Task WriteStateAsync(string grainType, GrainReference grainReference, IGrainState grainState)
{
if (!_storage.TryGetValue(grainType, out IGrainStorage storage))
storage = CreateStorage(grainType, grainState);
return storage.WriteStateAsync(grainType, grainReference, grainState);
}
public Task ClearStateAsync(string grainType, GrainReference grainReference, IGrainState grainState)
{
if (!_storage.TryGetValue(grainType, out IGrainStorage storage))
storage = CreateStorage(grainType, grainState);
return storage.ClearStateAsync(grainType, grainReference, grainState);
}
private IGrainStorage CreateStorage(
string grainType
, IGrainState grainState)
{
Type grainImplType = _typeResolver.ResolveType(grainType);
Type stateType = _entityTypeResolver.ResolveStateType(grainType, grainState);
Type entityType = _entityTypeResolver.ResolveEntityType(grainType, grainState);
Type storageType = typeof(GrainStorage<,,,>)
.MakeGenericType(typeof(TContext),
grainImplType, stateType, entityType);
IGrainStorage storage;
try
{
storage = (IGrainStorage)Activator.CreateInstance(storageType, grainType, _serviceProvider);
}
catch (Exception e)
{
if (e.InnerException == null)
throw;
// Unwrap target invocation exception
throw e.InnerException;
}
_storage.TryAdd(grainType, storage);
return storage;
}
}
}
using Orleans;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public class EntityTypeResolver : IEntityTypeResolver
{
public virtual Type ResolveEntityType(string grainType, IGrainState grainState)
{
return ResolveStateType(grainType, grainState);
}
public virtual Type ResolveStateType(string grainType, IGrainState grainState)
{
// todo: hack, the declared type of the grain state is only accessible like so
return grainState.GetType().IsGenericType
? grainState.GetType().GenericTypeArguments[0]
: grainState.State.GetType();
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public class EntityWapper<TEntity> where TEntity : class
{
public TEntity Entity { get; set; }
public bool ToAdd { get; set; }
public bool IsAdded { get; set; }
}
}
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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
internal class GrainStorage<TContext, TGrain, TGrainState, TEntity> : IGrainStorage
where TContext : DbContext
where TGrain : Grain<TGrainState>
where TGrainState : class, new()
where TEntity : class
{
private readonly GrainStorageOptions<TContext, TGrain, TEntity> _options;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<GrainStorage<TContext, TGrain, TGrainState, TEntity>> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IGrainStateEntryConfigurator<TContext, TGrain, TEntity> _entryConfigurator;
public GrainStorage(string grainType, IServiceProvider serviceProvider)
{
if (grainType == null) throw new ArgumentNullException(nameof(grainType));
_serviceProvider = serviceProvider
?? throw new ArgumentNullException(nameof(serviceProvider));
_entryConfigurator = (IGrainStateEntryConfigurator<TContext, TGrain, TEntity>)serviceProvider.GetRequiredService(
typeof(IGrainStateEntryConfigurator<TContext, TGrain, TEntity>));
var loggerFactory = _serviceProvider.GetService<ILoggerFactory>();
_logger = loggerFactory?.CreateLogger<GrainStorage<TContext, TGrain, TGrainState, TEntity>>()
?? NullLogger<GrainStorage<TContext, TGrain, TGrainState, TEntity>>.Instance;
_scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
_options = GetOrCreateDefaultOptions(grainType);
}
public async Task ReadStateAsync(string grainType, GrainReference grainReference, IGrainState grainState)
{
using (IServiceScope scope = _scopeFactory.CreateScope())
using (var context = scope.ServiceProvider.GetRequiredService<TContext>())
{
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<TContext>())
{
if (GrainStorageContext<TEntity>.IsConfigured)
{
EntityEntry<TEntity> entry = context.Entry(entity);
GrainStorageContext<TEntity>.ConfigureStateDelegate(entry);
}
else
{
bool isPersisted = _options.IsPersistedFunc(entity);
_entryConfigurator.ConfigureSaveEntry(
new ConfigureSaveEntryContext<TContext, TEntity>(
context, entity)
{
IsPersisted = isPersisted
});
}
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<TContext>())
{
context.Remove(entity);
await context.SaveChangesAsync()
.ConfigureAwait(false);
}
}
private GrainStorageOptions<TContext, TGrain, TEntity> GetOrCreateDefaultOptions(string grainType)
{
var options
= _serviceProvider.GetOptionsByName<GrainStorageOptions<TContext, TGrain, TEntity>>(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<GrainStorageOptions<TContext, TGrain, TEntity>>)
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<TContext, TGrain, TState> extension method.");
return options;
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
// 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)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public static class GrainStorageContext<TEntity>
where TEntity : class
{
// ReSharper disable once StaticMemberInGenericType
private static readonly AsyncLocal<bool> IsConfiguredLocal
= new AsyncLocal<bool>();
private static readonly AsyncLocal<ConfigureEntryStateDelegate<TEntity>>
ConfigureStateDelegateLocal
= new AsyncLocal<ConfigureEntryStateDelegate<TEntity>>();
internal static bool IsConfigured => IsConfiguredLocal.Value;
internal static ConfigureEntryStateDelegate<TEntity> ConfigureStateDelegate
=> ConfigureStateDelegateLocal.Value;
/// <summary>
/// Configures the entry state.
/// Use it to modify what gets changed during the write operations.
/// </summary>
/// <param name="configureState">The delegate to be called before saving context's state.</param>
public static void ConfigureEntryState(ConfigureEntryStateDelegate<TEntity> configureState)
{
ConfigureStateDelegateLocal.Value = configureState;
IsConfiguredLocal.Value = true;
}
public static void Clear()
{
ConfigureStateDelegateLocal.Value = null;
IsConfiguredLocal.Value = false;
}
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Orleans;
using Orleans.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public abstract class GrainStorageOptions
{
internal string KeyPropertyName { get; set; }
internal string KeyExtPropertyName { get; set; }
internal string ETagPropertyName { get; set; }
internal string PersistenceCheckPropertyName { get; set; }
internal IProperty ETagProperty { get; set; }
internal bool CheckForETag { get; set; }
internal Func<object, string> ConvertETagObjectToStringFunc { get; set; }
internal Type ETagType { get; set; }
public bool ShouldUseETag { get; set; }
internal bool IsConfigured { get; set; }
internal bool PreCompileReadQuery { get; set; } = true;
}
public class GrainStorageOptions<TContext, TGrain, TEntity> : GrainStorageOptions
where TContext : DbContext
where TEntity : class
{
internal Func<TContext, IQueryable<TEntity>> DbSetAccessor { get; set; }
internal Func<TEntity, bool> IsPersistedFunc { get; set; }
internal Func<TEntity, string> GetETagFunc { get; set; }
internal Func<TEntity, Guid> GuidKeySelector { get; set; }
internal Func<TEntity, string> KeyExtSelector { get; set; }
internal Func<TEntity, long> LongKeySelector { get; set; }
internal Func<TContext, IAddressable, Task<TEntity>> ReadStateAsync { get; set; }
internal Action<IGrainState, TEntity> SetEntity { get; set; }
internal Func<IGrainState, TEntity> GetEntity { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Orleans;
using Pole.Orleans.Provider.EntityframeworkCore.Conventions;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public class GrainStoragePostConfigureOptions<TContext, TGrain, TGrainState, TEntity>
: IPostConfigureOptions<GrainStorageOptions<TContext, TGrain, TEntity>>
where TContext : DbContext
where TGrain : Grain<TGrainState>
where TGrainState : new()
where TEntity : class
{
public IGrainStorageConvention<TContext, TGrain, TEntity> Convention { get; }
public IGrainStorageConvention DefaultConvention { get; }
public GrainStoragePostConfigureOptions(IServiceProvider serviceProvider)
{
DefaultConvention =
(IGrainStorageConvention)serviceProvider.GetRequiredService(typeof(IGrainStorageConvention));
Convention = (IGrainStorageConvention<TContext, TGrain, TEntity>)
serviceProvider.GetService(typeof(IGrainStorageConvention<TContext, TGrain, TEntity>));
}
public void PostConfigure(string name, GrainStorageOptions<TContext, TGrain, TEntity> options)
{
if (!string.Equals(name, typeof(TGrain).FullName))
throw new Exception("Post configure on wrong grain type.");
if (options.IsPersistedFunc == null)
options.IsPersistedFunc =
DefaultConvention.CreateIsPersistedFunc<TEntity>(options);
// Configure ETag
if (options.ShouldUseETag)
{
if (!string.IsNullOrWhiteSpace(options.ETagPropertyName))
DefaultConvention.ConfigureETag(options.ETagPropertyName, options);
}
if (options.ReadStateAsync == null)
{
if (options.DbSetAccessor == null)
options.DbSetAccessor = Convention?.CreateDefaultDbSetAccessorFunc()
?? DefaultConvention.CreateDefaultDbSetAccessorFunc<TContext, TEntity>();
if (Convention != null)
Convention.SetDefaultKeySelector(options);
else
DefaultConvention.SetDefaultKeySelectors(options);
if (options.PreCompileReadQuery)
{
options.ReadStateAsync
= Convention?.CreatePreCompiledDefaultReadStateFunc(options)
?? DefaultConvention
.CreatePreCompiledDefaultReadStateFunc(options);
}
else
{
options.ReadStateAsync
= Convention?.CreateDefaultReadStateFunc()
?? DefaultConvention
.CreateDefaultReadStateFunc(options);
}
}
if (options.SetEntity == null)
options.SetEntity =
Convention?.GetSetterFunc()
?? DefaultConvention.GetSetterFunc<TGrainState, TEntity>();
if (options.GetEntity == null)
options.GetEntity =
Convention?.GetGetterFunc()
?? DefaultConvention.GetGetterFunc<TGrainState, TEntity>();
DefaultConvention.FindAndConfigureETag(options, options.ShouldUseETag);
// todo: Validate options
options.IsConfigured = true;
}
}
}
using Orleans;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public interface IEntityTypeResolver
{
Type ResolveEntityType(string grainType, IGrainState grainState);
Type ResolveStateType(string grainType, IGrainState grainState);
}
}
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public interface IGrainStateEntryConfigurator<TContext, TGrain, TEntity>
where TContext : DbContext
where TEntity : class
{
void ConfigureSaveEntry(ConfigureSaveEntryContext<TContext, TEntity> context);
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pole.Core\Pole.Core.csproj" />
</ItemGroup>
</Project>
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore.Utils
{
internal static class ReflectionHelper
{
public static PropertyInfo GetPropertyInfo<T>(string propertyName)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
Type statetype = typeof(T);
PropertyInfo idProperty
= statetype.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (idProperty == null)
throw new GrainStorageConfigurationException(
$"Could not find \"{propertyName}\" property on type \"{statetype.FullName}\". Either configure the state locator predicate manually or update your model.");
if (!idProperty.CanRead)
throw new GrainStorageConfigurationException(
$"The property \"{propertyName}\" of type \"{statetype.FullName}\" must have a public getter.");
return idProperty;
}
public static Func<T, TProperty> GetAccessorDelegate<T, TProperty>(PropertyInfo pInfo)
{
return (Func<T, TProperty>)Delegate.CreateDelegate(
typeof(Func<T, TProperty>),
null,
pInfo.GetMethod);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment