-
Notifications
You must be signed in to change notification settings - Fork 256
Upgrade from .NET 5 to .NET 6 - Using a custom IMethodCallTranslator no longer works #2186
Copy link
Copy link
Closed
Description
I've injected my own IMethodCallTranslatorProvider to effectively monkey patch in PostGIS methods in that aren't currently available via Npgsql. This was working as expected in .NET 5, but after upgrading to .NET 6 I get the following exception thrown:
System.InvalidCastException: Unable to cast object of type 'Imagery.Warehouse.Metadata.Data.EFExtensions.ExtraNpgsqlMethodCallTranslatorProvider' to type 'Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal.NpgsqlMethodCallTranslatorProvider'.
at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitor..ctor(RelationalSqlTranslatingExpressionVisitorDependencies dependencies, QueryCompilationContext queryCompilationContext, QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor)
at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitorFactory.Create(QueryCompilationContext queryCompilationContext, QueryableMethodTranslatingExpressionVisitor queryableMethodTranslatingExpressionVisitor)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor..ctor(QueryableMethodTranslatingExpressionVisitorDependencies dependencies, RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext)
at Microsoft.EntityFrameworkCore.Query.Internal.RelationalQueryableMethodTranslatingExpressionVisitorFactory.Create(QueryCompilationContext queryCompilationContext)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable`1 source)
at Imagery.Warehouse.Metadata.Data.Stores.ReadStore.GetImageset(String imagesetUrn) in /home/paul.french/src/imagery-warehouse-metadata/lib/Imagery.Warehouse.Metadata.Data/Stores/ReadStore.cs:line 57
Here's a brief overview of the implementing code:
public static class NpgsqlDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder UseExtraFunctions(this DbContextOptionsBuilder optionsBuilder)
{
var extension = GetOrCreateExtension(optionsBuilder);
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension);
return optionsBuilder;
}
private static NpgsqlDbContextOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.Options.FindExtension<NpgsqlDbContextOptionsExtension>()
?? new NpgsqlDbContextOptionsExtension();
}
public class NpgsqlDbContextOptionsExtension : IDbContextOptionsExtension
{
private DbContextOptionsExtensionInfo _info;
public void Validate(IDbContextOptions options)
{
// Nothing to do
}
public DbContextOptionsExtensionInfo Info {
get
{
return _info ??= new MyDbContextOptionsExtensionInfo(this);
}
}
public void ApplyServices(IServiceCollection services)
{
// services.AddSingleton<IMethodCallTranslatorProvider, ExtraNpgsqlMethodCallTranslatorProvider>();
services.AddScoped<IMethodCallTranslatorProvider, ExtraNpgsqlMethodCallTranslatorProvider>();
}
private sealed class MyDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo
{
public MyDbContextOptionsExtensionInfo(IDbContextOptionsExtension instance) : base(instance) { }
public override bool IsDatabaseProvider => false;
public override string LogFragment => "";
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
}
public override int GetServiceProviderHashCode() => 0;
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) => false;
}
}
public sealed class ExtraNpgsqlMethodCallTranslatorProvider : RelationalMethodCallTranslatorProvider
{
public ExtraNpgsqlMethodCallTranslatorProvider(RelationalMethodCallTranslatorProviderDependencies dependencies) : base(dependencies)
{
var expressionFactory = dependencies.SqlExpressionFactory;
AddTranslators(new List<IMethodCallTranslator>
{
new ExtraNpgsqlMethodCallTranslator(expressionFactory)
});
}
}
public static class ExtraNpgsqlDbFunctionsExtensions
{
public static Geometry Force3D(this DbFunctions _, Geometry geometry) => throw new NotSupportedException();
public static Geometry Union(this DbFunctions _, Geometry geometry) => throw new NotSupportedException();
public static Geometry Multi(this DbFunctions _, Geometry geometry) => throw new NotSupportedException();
}
public class ExtraNpgsqlMethodCallTranslator : IMethodCallTranslator
{
private readonly ISqlExpressionFactory _expressionFactory;
private static readonly MethodInfo _force3DMethod
= typeof(ExtraNpgsqlDbFunctionsExtensions).GetMethod(
nameof(ExtraNpgsqlDbFunctionsExtensions.Force3D),
new [] { typeof(DbFunctions), typeof(Geometry)});
private static readonly MethodInfo _unionMethod
= typeof(ExtraNpgsqlDbFunctionsExtensions).GetMethod(
nameof(ExtraNpgsqlDbFunctionsExtensions.Union),
new [] { typeof(DbFunctions), typeof(Geometry)});
private static readonly MethodInfo _multiMethod
= typeof(ExtraNpgsqlDbFunctionsExtensions).GetMethod(
nameof(ExtraNpgsqlDbFunctionsExtensions.Multi),
new [] { typeof(DbFunctions), typeof(Geometry)});
public ExtraNpgsqlMethodCallTranslator(ISqlExpressionFactory expressionFactory)
{
_expressionFactory = expressionFactory;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments, IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method == _force3DMethod)
{
return _expressionFactory.Function(
"ST_Force3D",
new[] {arguments[1]},
nullable: true,
new[] {true},
method.ReturnType,
arguments[1].TypeMapping);
}
if (method == _unionMethod)
{
return _expressionFactory.Function(
"ST_Union",
new[] { arguments[1] },
true,
new[] { true },
method.ReturnType,
arguments[1].TypeMapping);
}
if (method == _multiMethod)
{
return _expressionFactory.Function(
"ST_Multi",
new[] { arguments[1] },
true,
new[] { true },
method.ReturnType,
arguments[1].TypeMapping);
}
return null;
}
}
Which is then registered/called in Startup.cs:
services.AddDbContext<ReadOnlyMetadataDbContext>((provider, builder) =>
{
builder.UseNpgsql(_configuration.GetConnectionString(Data.Connection.Constants.ConnectionStringNames.Reader), o =>
{
o.UseNetTopologySuite();
var databasePasswordManager = provider.GetRequiredService<IDatabasePasswordManager>();
o.ProvidePasswordCallback(databasePasswordManager.GetReaderPassword);
});
builder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
builder.UseExtraFunctions();
if (isSecretManagerEnabled)
{
builder.AddInterceptors(provider.GetRequiredService<ReaderConnectionInterceptor>());
}
});
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels