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));
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 3
0
        /// <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);
        }
Ejemplo n.º 4
0
        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);
        }
Ejemplo n.º 5
0
        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());
        }
Ejemplo n.º 7
0
        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);
        }
Ejemplo n.º 8
0
        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));
        }
Ejemplo n.º 9
0
        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));
        }
Ejemplo n.º 10
0
        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);
        }
Ejemplo n.º 11
0
        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);
        }
Ejemplo n.º 12
0
        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)}\".");
        }