Skip to content

Commit

Permalink
Skips normalization of array[index].Property to array.Select(e => e.P…
Browse files Browse the repository at this point in the history
…roperty).ElementAt(index), because it messes-up our JSON-Array handling in `MySqlSqlTranslatingExpressionVisitor`. See dotnet/efcore#30386.
  • Loading branch information
lauxjpn committed Sep 29, 2023
1 parent 1550b88 commit fc67ee0
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public static IServiceCollection AddEntityFrameworkMySql([NotNull] this IService
//.TryAdd<IValueConverterSelector, MySqlValueConverterSelector>()
.TryAdd<IQueryCompilationContextFactory, MySqlQueryCompilationContextFactory>()
.TryAdd<IQueryTranslationPostprocessorFactory, MySqlQueryTranslationPostprocessorFactory>()
.TryAdd<IQueryTranslationPreprocessorFactory, MySqlQueryTranslationPreprocessorFactory>()
.TryAdd<IMigrationsModelDiffer, MySqlMigrationsModelDiffer>()
.TryAdd<IMigrator, MySqlMigrator>()
.TryAddProviderSpecificServices(m => m
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Pomelo.EntityFrameworkCore.MySql.Query.ExpressionVisitors.Internal;

public class MySqlQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
private readonly RelationalQueryCompilationContext _relationalQueryCompilationContext;

public MySqlQueryTranslationPreprocessor(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
_relationalQueryCompilationContext = (RelationalQueryCompilationContext)queryCompilationContext;
}

/// <summary>
/// Workaround https://github.com/dotnet/efcore/issues/30386.
/// </summary>
public override Expression NormalizeQueryableMethod(Expression expression)
{
// Implementation of base (RelationalQueryTranslationPreprocessor).
expression = new RelationalQueryMetadataExtractingExpressionVisitor(_relationalQueryCompilationContext).Visit(expression);

// Implementation of base.base (QueryTranslationPreprocessor), using `MySqlQueryableMethodNormalizingExpressionVisitor` instead of
// `QueryableMethodNormalizingExpressionVisitor` directly.
expression = new MySqlQueryableMethodNormalizingExpressionVisitor(QueryCompilationContext).Normalize(expression);
expression = ProcessQueryRoots(expression);

return expression;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.

using Microsoft.EntityFrameworkCore.Query;

namespace Pomelo.EntityFrameworkCore.MySql.Query.ExpressionVisitors.Internal;

public class MySqlQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;

public MySqlQueryTranslationPreprocessorFactory(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}

public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new MySqlQueryTranslationPreprocessor(
_dependencies,
_relationalDependencies,
queryCompilationContext);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Pomelo Foundation. All rights reserved.
// Licensed under the MIT. See LICENSE in the project root for license information.

using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;

namespace Pomelo.EntityFrameworkCore.MySql.Query.ExpressionVisitors.Internal;

/// <summary>
/// Skips normalization of array[index].Property to array.Select(e => e.Property).ElementAt(index),
/// because it messes-up our JSON-Array handling in `MySqlSqlTranslatingExpressionVisitor`.
/// See https://github.com/dotnet/efcore/issues/30386.
/// </summary>
public class MySqlQueryableMethodNormalizingExpressionVisitor : QueryableMethodNormalizingExpressionVisitor
{
public MySqlQueryableMethodNormalizingExpressionVisitor(QueryCompilationContext queryCompilationContext)
: base(queryCompilationContext)
{
}

protected override Expression VisitBinary(BinaryExpression binaryExpression)
{
// Convert array[x] to array.ElementAt(x)
if (binaryExpression is
{
NodeType: ExpressionType.ArrayIndex,
Left: var source,
Right: var index
})
{
return binaryExpression;

// Original (base) implementation:
//
// return VisitMethodCall(
// Expression.Call(
// ElementAtMethodInfo.MakeGenericMethod(source.Type.GetSequenceType()), source, index));
}

return base.VisitBinary(binaryExpression);
}

protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
// Normalize list[x] to list.ElementAt(x)
if (methodCallExpression is
{
Method:
{
Name: "get_Item",
IsStatic: false,
DeclaringType: Type declaringType
},
Object: Expression indexerSource,
Arguments: [var index]
}
&& declaringType.GetInterface("IReadOnlyList`1") is not null)
{
return methodCallExpression;

// Original (base) implementation:
//
// return VisitMethodCall(
// Expression.Call(
// ElementAtMethodInfo.MakeGenericMethod(indexerSource.Type.GetSequenceType()),
// indexerSource,
// index));
}

return base.VisitMethodCall(methodCallExpression);
}
}

0 comments on commit fc67ee0

Please sign in to comment.