Exception when trying to resolve 'Orleans.Storage.AdoNetGrainStorage'
Hey,
If I undestood correctly - this library should have support for any of the Orleans storage providers (correct me if I'm wrong), but I'm having this error with 'Orleans.Storage.AdoNetGrainStorage' (Posgresql):
Orleans.Runtime.OrleansException: Error from storage provider <redacted> during ReadState for grain Type=<redacted> Id=<redacted>|1 Error=
Exc level 0: System.InvalidOperationException: A suitable constructor for type 'Orleans.Storage.AdoNetGrainStorage' could not be located. Ensure the type is concrete and all parameters of a public constructor are either registered as services or passed as arguments. Also ensure no extraneous arguments are provided.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
After a bit of reseach I found that in TenantGrainStorageFactory in line below what being passed to ActivatorUtilities.CreateInstance is T options while AdoNet storage wants IOptions<T>:
return ActivatorUtilities.CreateInstance<TGrainStorage>(services, tenantProviderName, options);
To illustrate:
As of now I cloned the code and hijacked into storage grain creation process to continue my testing, but wondering if this can be addressed properly and pushed as nuget?
Thanks for reporting this @poliakhd , I plan to refresh Orleans.Multitenant for Orleans 8 and will look into this as well
@poliakhd I just saw this issue was not addressed when I intended to - but better late than never!
I reproduced the issue; to address it I added an optional getProviderParameters parameter. This is how you can use it for the ADO.NET grain storage provider:
.AddMultitenantGrainStorageAsDefault<AdoNetGrainStorage, AdoNetGrainStorageOptions, AdoNetGrainStorageOptionsValidator>(
(silo, name) => silo.AddAdoNetGrainStorage(name, options => options.ConnectionString = sqlConnectionString),
configureTenantOptions: (options, tenantId) => options.ConnectionString = sqlConnectionString,
getProviderParameters: (services, providerName, tenantProviderName, options) => [Options.Create(options)]
)
(you could ofc also make the connection string tenant-specific in configureTenantOptions, e.g. separate databases per tenant - that is not in above example)
getProviderParameters is a GrainStorageProviderParametersFactory, which is fully documented:
/// <summary>
/// Factory delegate, used to create parameters for a tenant grain storage provider constructor.<br />
/// Allows to e.g. transform the options if the provider expects a different type than <typeparamref name="TGrainStorageOptions"/>,<br />
/// or to retrieve an add extra parameters like <see cref="Configuration.ClusterOptions" />
/// </summary>
/// <typeparam name="TGrainStorageOptions">The provider-specific grain storage options type, e.g. Orleans.Storage.MemoryGrainStorageOptions or Orleans.Storage.AzureTableStorageOptions</typeparam>
/// <param name="services">The silo services</param>
/// <param name="providerName">The name - without the tenant id - of the provider; can be used to access named provider services that are not tenant specific</param>
/// <param name="tenantProviderName">The name - including the tenant id - of the tenant provider; can be used to access named provider services that are tenant specific</param>
/// <param name="options">The options to pass to the provider. Note that configureTenantOptions and options validation have already been executed on this</param>
/// <returns>The tenant storage provider construction parameters to pass to DI. Don't include <paramref name="tenantProviderName"/> in these; it is added automatically</returns>
public delegate object[] GrainStorageProviderParametersFactory<in TGrainStorageOptions>(
IServiceProvider services,
string providerName,
string tenantProviderName,
TGrainStorageOptions options
);
The new API will be included in the upcoming 2.2 release (ETA next week). Hth!