private static Expression <Func <TEntity, bool> > CreateKeyPredicate <TEntity, TKey>( GrainStorageOptions options, ParameterExpression grainKeyParameter) { ParameterExpression stateParam = Expression.Parameter(typeof(TEntity), "state"); MemberExpression stateKeyParam = Expression.Property(stateParam, options.KeyPropertyName); BinaryExpression equals = Expression.Equal(grainKeyParameter, stateKeyParam); return(Expression.Lambda <Func <TEntity, bool> >(equals, stateParam)); }
/// <summary> /// Overrides the default implementation used to query grain state from database. /// </summary> /// <typeparam name="TContext"></typeparam> /// <typeparam name="TGrainState"></typeparam> /// <typeparam name="TGrain"></typeparam> /// <param name="options"></param> /// <param name="readStateAsyncFunc"></param> /// <returns></returns> public static GrainStorageOptions <TContext, TGrain, TGrainState> ConfigureReadState <TContext, TGrain, TGrainState>( this GrainStorageOptions <TContext, TGrain, TGrainState> options, Func <TContext, IAddressable, Task <TGrainState> > 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); }
/// <summary> /// Configures the expression used to query grain state from database. /// </summary> /// <typeparam name="TContext"></typeparam> /// <typeparam name="TGrainState"></typeparam> /// <param name="options"></param> /// <param name="expressionFunc"></param> /// <returns></returns> public static GrainStorageOptions <TContext, TGrain, TGrainState> UseQueryExpression <TContext, TGrain, TGrainState>( this GrainStorageOptions <TContext, TGrain, TGrainState> options, Func <IAddressable, Expression <Func <TGrainState, bool> > > expressionFunc) where TContext : DbContext where TGrainState : class, new() { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (expressionFunc == null) { throw new ArgumentNullException(nameof(expressionFunc)); } options.QueryExpressionGeneratorFunc = expressionFunc; return(options); }
public static GrainStorageOptions <TContext, TGrain, TGrainState> CheckPersistenceOn <TContext, TGrain, TGrainState>( this GrainStorageOptions <TContext, TGrain, TGrainState> 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); }
public static GrainStorageOptions <TContext, TGrain, TGrainState> UseKeyExt <TContext, TGrain, TGrainState>( this GrainStorageOptions <TContext, TGrain, TGrainState> options, string propertyName) where TContext : DbContext where TGrainState : class, new() { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (propertyName == null) { throw new ArgumentNullException(nameof(propertyName)); } options.KeyExtPropertyName = propertyName; return(options); }
public static Func <TContext, TKey, string, Task <TEntity> > CreateCompoundQuery <TContext, TGrain, TEntity, TKey>( GrainStorageOptions <TContext, TGrain, TEntity> options) where TContext : DbContext where TEntity : class { var contextParameter = Expression.Parameter(typeof(TContext), "context"); var stateParameter = Expression.Parameter(typeof(TEntity), "state"); var keyParameter = Expression.Parameter(typeof(TKey), "grainKey"); var keyExtParameter = Expression.Parameter(typeof(string), "grainKeyExt"); var keyProperty = Expression.Property(stateParameter, options.KeyPropertyName); var keyExtProperty = Expression.Property(stateParameter, options.KeyExtPropertyName); var equalsExp = Expression.And( Expression.Equal( keyProperty, keyParameter), Expression.Equal( keyExtProperty, keyExtParameter)); var predicate = Expression.Lambda(equalsExp, stateParameter); var queryable = Expression.Call( options.DbSetAccessor.Method, Expression.Constant(options.DbSetAccessor), contextParameter); var compiledLambdaBody = Expression.Call( typeof(EntityFrameworkQueryableExtensions).GetMethods().Single(mi => mi.Name == nameof(EntityFrameworkQueryableExtensions.SingleOrDefaultAsync) && mi.GetParameters().Count() == 3) .MakeGenericMethod(typeof(TEntity)), queryable, Expression.Quote(predicate), Expression.Constant(default(CancellationToken), typeof(CancellationToken))); var lambdaExpression = Expression.Lambda <Func <TContext, TKey, string, Task <TEntity> > >( compiledLambdaBody, contextParameter, keyParameter, keyExtParameter); return(lambdaExpression.Compile()); }
public static GrainStorageOptions <TContext, TGrain, TGrainState> UseETag <TContext, TGrain, TGrainState>( this GrainStorageOptions <TContext, TGrain, TGrainState> 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); }
private static Expression <Func <TEntity, bool> > CreateCompoundKeyPredicate <TEntity, TKey>( GrainStorageOptions options, string grainKeyParamName = "__grainKey", string grainKeyExtParamName = "__grainKeyExt") { ParameterExpression stateParam = Expression.Parameter(typeof(TEntity), "state"); ParameterExpression grainKeyParam = Expression.Parameter(typeof(TKey), grainKeyParamName); MemberExpression stateKeyParam = Expression.Property(stateParam, options.KeyPropertyName); ParameterExpression grainKeyExtParam = Expression.Parameter(typeof(string), grainKeyExtParamName); MemberExpression stateKeyExtParam = Expression.Property(stateParam, options.KeyExtPropertyName); BinaryExpression keyEquals = Expression.Equal(grainKeyParam, stateKeyParam); BinaryExpression keyExtEquals = Expression.Equal(grainKeyExtParam, stateKeyExtParam); BinaryExpression equals = Expression.And(keyEquals, keyExtEquals); return(Expression.Lambda <Func <TEntity, bool> >(equals, stateParam)); }
private static Func <TContext, TKey, Task <TEntity> > CreateCompiledQuery <TContext, TGrain, TEntity, TKey>( GrainStorageOptions <TContext, TGrain, TEntity> options) where TContext : DbContext where TEntity : class { var contextParameter = Expression.Parameter(typeof(TContext), "context"); var keyParameter = Expression.Parameter(typeof(TKey), "grainKey"); var predicate = CreateKeyPredicate <TEntity, TKey>(options, keyParameter); 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, Expression.Quote(predicate)); var lambdaExpression = Expression.Lambda <Func <TContext, TKey, TEntity> >( compiledLambdaBody, contextParameter, keyParameter); return(EF.CompileAsyncQuery(lambdaExpression)); }
public static GrainStorageOptions <TContext, TGrain, TGrainState> CheckPersistenceOn <TContext, TGrain, TGrainState, TProperty>( this GrainStorageOptions <TContext, TGrain, TGrainState> options, Expression <Func <TGrainState, TProperty> > 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 <TContext, TGrain, TGrainState> UseKey <TContext, TGrain, TGrainState>( this GrainStorageOptions <TContext, TGrain, TGrainState> options, Expression <Func <TGrainState, long> > 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 <TContext, TGrain, TGrainState> UseETag <TContext, TGrain, TGrainState, TProperty>( this GrainStorageOptions <TContext, TGrain, TGrainState> options, Expression <Func <TGrainState, TProperty> > expression) where TContext : DbContext where TGrainState : class, new() { 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); }
private static void ConfigureETag <TContext, TGrain, TGrainState>( IProperty property, GrainStorageOptions <TContext, TGrain, TGrainState> options) where TContext : DbContext { if (property == null) { throw new ArgumentNullException(nameof(property)); } if (!property.IsConcurrencyToken) { throw new GrainStorageConfigurationException($"Property {property.Name} is not a concurrency token."); } options.CheckForETag = true; options.ETagPropertyName = property.Name; options.ETagProperty = property; options.ETagType = property.ClrType; options.GetETagFunc = CreateGetETagFunc <TGrainState>(property.Name); options.ConvertETagObjectToStringFunc = CreateConvertETagObjectToStringFunc(); }
CreateDefaultReadStateFunc <TContext, TGrain, TEntity>( GrainStorageOptions <TContext, TGrain, TEntity> options) where TContext : DbContext where TEntity : class { if (typeof(IGrainWithGuidKey).IsAssignableFrom(typeof(TGrain))) { if (options.GuidKeySelector == null) { throw new GrainStorageConfigurationException($"GuidKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } return((TContext context, IAddressable grainRef) => { Guid key = grainRef.GetPrimaryKey(); return options.DbSetAccessor(context) .SingleOrDefaultAsync( state => options.GuidKeySelector(state) == key); }); } if (typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(typeof(TGrain))) { if (options.GuidKeySelector == null) { throw new GrainStorageConfigurationException($"GuidKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } if (options.KeyExtSelector == null) { throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } return((TContext context, IAddressable grainRef) => { Guid key = grainRef.GetPrimaryKey(out string keyExt); return options.DbSetAccessor(context) .SingleOrDefaultAsync(state => options.GuidKeySelector(state) == key && options.KeyExtSelector(state) == keyExt); }); } if (typeof(IGrainWithIntegerKey).IsAssignableFrom(typeof(TGrain))) { if (options.LongKeySelector == null) { throw new GrainStorageConfigurationException($"LongKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } return((TContext context, IAddressable grainRef) => { long key = grainRef.GetPrimaryKeyLong(); return options.DbSetAccessor(context) .SingleOrDefaultAsync(state => options.LongKeySelector(state) == key); }); } if (typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(typeof(TGrain))) { if (options.LongKeySelector == null) { throw new GrainStorageConfigurationException($"LongKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } if (options.KeyExtSelector == null) { throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } return((TContext context, IAddressable grainRef) => { long key = grainRef.GetPrimaryKeyLong(out string keyExt); return options.DbSetAccessor(context) .SingleOrDefaultAsync(state => options.LongKeySelector(state) == key && options.KeyExtSelector(state) == keyExt); }); } if (typeof(IGrainWithStringKey).IsAssignableFrom(typeof(TGrain))) { if (options.KeyExtSelector == null) { throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } var compiledQuery = EF.CompileAsyncQuery((TContext context, string keyExt) => options.DbSetAccessor(context) .SingleOrDefault(state => options.KeyExtSelector(state) == keyExt)); return((TContext context, IAddressable grainRef) => { string keyExt = grainRef.GetPrimaryKeyString(); return options.DbSetAccessor(context) .SingleOrDefaultAsync(state => options.KeyExtSelector(state) == keyExt); }); } throw new InvalidOperationException($"Unexpected grain type \"{typeof(TGrain).FullName}\""); }
public virtual void SetDefaultKeySelectors <TContext, TGrain, TEntity>( GrainStorageOptions <TContext, TGrain, TEntity> options) where TContext : DbContext where TEntity : class { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (options.KeyPropertyName == null) { options.KeyPropertyName = _options.DefaultGrainKeyPropertyName; } if (options.KeyExtPropertyName == null) { options.KeyExtPropertyName = _options.DefaultGrainKeyExtPropertyName; } PropertyInfo idProperty = ReflectionHelper.GetPropertyInfo <TEntity>( options.KeyPropertyName ?? _options.DefaultGrainKeyPropertyName); Type idType = idProperty.PropertyType; if (typeof(IGrainWithGuidKey).IsAssignableFrom(typeof(TGrain))) { if (options.GuidKeySelector != null) { return; } if (idType != typeof(Guid)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a Guid key " + $"but the type {typeof(TEntity).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } options.GuidKeySelector = ReflectionHelper.GetAccessorDelegate <TEntity, Guid>(idProperty); return; } if (typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(typeof(TGrain))) { PropertyInfo keyExtProperty = ReflectionHelper.GetPropertyInfo <TEntity>( options.KeyExtPropertyName ?? _options.DefaultGrainKeyExtPropertyName); if (idType != typeof(Guid)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a Guid key " + $"but the type {typeof(TEntity).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } if (keyExtProperty.PropertyType != typeof(string)) { throw new GrainStorageConfigurationException($"Can not use property \"{keyExtProperty.Name}\" " + $"on grain state type \"{typeof(TEntity)}\". " + "KeyExt property must be of type string."); } if (options.GuidKeySelector == null) { options.GuidKeySelector = ReflectionHelper.GetAccessorDelegate <TEntity, Guid>(idProperty); } if (options.KeyExtSelector == null) { options.KeyExtSelector = ReflectionHelper.GetAccessorDelegate <TEntity, string>(keyExtProperty); } return; } if (typeof(IGrainWithIntegerKey).IsAssignableFrom(typeof(TGrain))) { if (options.LongKeySelector != null) { return; } if (idType != typeof(long)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a long key " + $"but the type {typeof(TEntity).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } options.LongKeySelector = ReflectionHelper.GetAccessorDelegate <TEntity, long>(idProperty); return; } if (typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(typeof(TGrain))) { PropertyInfo keyExtProperty = ReflectionHelper.GetPropertyInfo <TEntity>( options.KeyExtPropertyName ?? _options.DefaultGrainKeyExtPropertyName); if (keyExtProperty.PropertyType != typeof(string)) { throw new GrainStorageConfigurationException($"Can not use property \"{keyExtProperty.Name}\" " + $"on grain state type \"{typeof(TEntity)}\". " + "KeyExt property must be of type string."); } if (options.LongKeySelector == null) { options.LongKeySelector = ReflectionHelper.GetAccessorDelegate <TEntity, long>(idProperty); } if (options.KeyExtSelector == null) { options.KeyExtSelector = ReflectionHelper.GetAccessorDelegate <TEntity, string>(keyExtProperty); } return; } if (typeof(IGrainWithStringKey).IsAssignableFrom(typeof(TGrain))) { if (options.KeyExtSelector != null) { return; } if (idType != typeof(string)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a string key " + $"but the type {typeof(TEntity).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } options.KeyExtSelector = ReflectionHelper.GetAccessorDelegate <TEntity, string>(idProperty); return; } throw new InvalidOperationException($"Unexpected grain type \"{typeof(TGrain).FullName}\""); }
CreatePreCompiledDefaultReadStateFunc <TContext, TGrain, TEntity>( GrainStorageOptions <TContext, TGrain, TEntity> options) where TContext : DbContext where TEntity : class { if (typeof(IGrainWithGuidKey).IsAssignableFrom(typeof(TGrain))) { if (options.GuidKeySelector == null) { throw new GrainStorageConfigurationException($"GuidKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } Func <TContext, Guid, Task <TEntity> > compiledQuery = CreateCompiledQuery <TContext, TGrain, TEntity, Guid>(options); return((TContext context, IAddressable grainRef) => { Guid key = grainRef.GetPrimaryKey(); return compiledQuery(context, key); }); } if (typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(typeof(TGrain))) { if (options.GuidKeySelector == null) { throw new GrainStorageConfigurationException($"GuidKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } if (options.KeyExtSelector == null) { throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } Func <TContext, Guid, string, Task <TEntity> > compiledQuery = CreateCompiledCompoundQuery <TContext, TGrain, TEntity, Guid>(options); return((TContext context, IAddressable grainRef) => { Guid key = grainRef.GetPrimaryKey(out string keyExt); return compiledQuery(context, key, keyExt); }); } if (typeof(IGrainWithIntegerKey).IsAssignableFrom(typeof(TGrain))) { if (options.LongKeySelector == null) { throw new GrainStorageConfigurationException($"LongKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } Func <TContext, long, Task <TEntity> > compiledQuery = CreateCompiledQuery <TContext, TGrain, TEntity, long>(options); return((TContext context, IAddressable grainRef) => { long key = grainRef.GetPrimaryKeyLong(); return compiledQuery(context, key); }); } if (typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(typeof(TGrain))) { if (options.LongKeySelector == null) { throw new GrainStorageConfigurationException($"LongKeySelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } if (options.KeyExtSelector == null) { throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } Func <TContext, long, string, Task <TEntity> > compiledQuery = CreateCompiledCompoundQuery <TContext, TGrain, TEntity, long>(options); return((TContext context, IAddressable grainRef) => { long key = grainRef.GetPrimaryKeyLong(out string keyExt); return compiledQuery(context, key, keyExt); }); } if (typeof(IGrainWithStringKey).IsAssignableFrom(typeof(TGrain))) { if (options.KeyExtSelector == null) { throw new GrainStorageConfigurationException($"KeyExtSelector is not defined for " + $"{typeof(GrainStorageOptions<TContext, TGrain, TEntity>).FullName}"); } Func <TContext, string, Task <TEntity> > compiledQuery = CreateCompiledQuery <TContext, TGrain, TEntity, string>(options); return((TContext context, IAddressable grainRef) => { string keyExt = grainRef.GetPrimaryKeyString(); return compiledQuery(context, keyExt); }); } throw new InvalidOperationException($"Unexpected grain type \"{typeof(TGrain).FullName}\""); }
CreateDefaultGrainStateQueryExpressionGeneratorFunc <TGrain, TGrainState>( GrainStorageOptions options) where TGrain : Grain <TGrainState> where TGrainState : new() { if (options == null) { throw new ArgumentNullException(nameof(options)); } PropertyInfo idProperty = ReflectionHelper.GetPropertyInfo <TGrainState>( options.KeyPropertyName ?? _options.DefaultGrainKeyPropertyName); Type idType = idProperty.PropertyType; if (typeof(IGrainWithGuidKey).IsAssignableFrom(typeof(TGrain))) { if (idType != typeof(Guid)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a Guid key " + $"but the type {typeof(TGrainState).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } return(CreateGrainStateQueryExpressionGeneratorFunc <TGrainState>( grainRef => grainRef.GetPrimaryKey(), idProperty.Name)); } if (typeof(IGrainWithIntegerKey).IsAssignableFrom(typeof(TGrain))) { if (idType != typeof(long)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a long key " + $"but the type {typeof(TGrainState).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } return(CreateGrainStateQueryExpressionGeneratorFunc <TGrainState>( grainRef => grainRef.GetPrimaryKeyLong(), idProperty.Name)); } if (typeof(IGrainWithStringKey).IsAssignableFrom(typeof(TGrain))) { if (idType != typeof(string)) { throw new GrainStorageConfigurationException( $"Incompatible grain and state. \"{typeof(TGrain).FullName}\" expects a string key " + $"but the type {typeof(TGrainState).FullName}.{idProperty.Name} " + $"is of type {idType.FullName}."); } return(CreateGrainStateQueryExpressionGeneratorFunc <TGrainState>( grainRef => grainRef.GetPrimaryKeyString(), idProperty.Name)); } if (typeof(IGrainWithGuidCompoundKey).IsAssignableFrom(typeof(TGrain))) { PropertyInfo keyExtProperty = ReflectionHelper.GetPropertyInfo <TGrainState>( options.KeyExtPropertyName ?? _options.DefaultGrainKeyExtPropertyName); if (keyExtProperty.PropertyType != typeof(string)) { throw new GrainStorageConfigurationException($"Can not use property \"{keyExtProperty.Name}\" " + $"on grain state type \"{typeof(TGrainState)}\". " + "KeyExt property must be of type string."); } return(CreateGrainStateQueryExpressionGeneratorFunc <TGrainState>( (IAddressable grainRef, out string keyExt) => grainRef.GetPrimaryKey(out keyExt), idProperty.Name, keyExtProperty.Name)); } if (typeof(IGrainWithIntegerCompoundKey).IsAssignableFrom(typeof(TGrain))) { PropertyInfo keyExtProperty = ReflectionHelper.GetPropertyInfo <TGrainState>( options.KeyExtPropertyName ?? _options.DefaultGrainKeyExtPropertyName); if (keyExtProperty.PropertyType != typeof(string)) { throw new GrainStorageConfigurationException($"Can not use property \"{keyExtProperty.Name}\" " + $"on grain state type \"{typeof(TGrainState)}\". " + "KeyExt property must be of type string."); } return(CreateGrainStateQueryExpressionGeneratorFunc <TGrainState>( (IAddressable grainRef, out string keyExt) => grainRef.GetPrimaryKeyLong(out keyExt), idProperty.Name, keyExtProperty.Name)); } throw new GrainStorageConfigurationException($"Unexpected grain type \"{typeof(TGrain)}\"."); }