protected override Expression VisitBinary(BinaryExpression binaryExpression) { if (binaryExpression.NodeType == ExpressionType.ArrayIndex) { if (TranslationFailed(binaryExpression.Left, Visit(TryRemoveImplicitConvert(binaryExpression.Left)), out var sqlLeft) || TranslationFailed(binaryExpression.Right, Visit(TryRemoveImplicitConvert(binaryExpression.Right)), out var sqlRight)) { return(null); } // Try translating ArrayIndex inside json column return(_jsonPocoTranslator.TranslateMemberAccess( sqlLeft, _sqlExpressionFactory.JsonArrayIndex(sqlRight), binaryExpression.Type)); } var visitedExpression = (SqlExpression)base.VisitBinary(binaryExpression); if (visitedExpression == null) { return(null); } if (visitedExpression is SqlBinaryExpression visitedBinaryExpression) { // Returning null forces client projection. // CHECK: Is this still true in .NET Core 3.0? switch (visitedBinaryExpression.OperatorType) { case ExpressionType.Add: case ExpressionType.Subtract: case ExpressionType.Multiply: case ExpressionType.Divide: case ExpressionType.Modulo: return(IsDateTimeBasedOperation(visitedBinaryExpression) ? null : visitedBinaryExpression); } // The following section might not be needed. Lets keep it for a bit, until we are sure. /* * // When comparing a JSON value to some string value or when assigning a string value * // to a JSON column, convert the string value to JSON first. * // Also explicitly convert parameter instances to JSON. * if (visitedBinaryExpression.Left.TypeMapping?.StoreType == "json" && * visitedBinaryExpression.Right.TypeMapping?.StoreType != "json" && * visitedBinaryExpression.Right.TypeMapping?.ClrType == typeof(string)) * { * visitedExpression = _sqlExpressionFactory.MakeBinary( * visitedBinaryExpression.OperatorType, * visitedBinaryExpression.Left, * _sqlExpressionFactory.Convert( * visitedBinaryExpression.Right, * visitedBinaryExpression.Right.Type, * _jsonTypeMapping), * _jsonTypeMapping); * } * else if (visitedBinaryExpression.Right.TypeMapping?.StoreType == "json" && * visitedBinaryExpression.Left.TypeMapping?.StoreType != "json" && * visitedBinaryExpression.Left.TypeMapping?.ClrType == typeof(string) && * visitedBinaryExpression.OperatorType != ExpressionType.Assign) * { * visitedExpression = _sqlExpressionFactory.MakeBinary( * visitedBinaryExpression.OperatorType, * _sqlExpressionFactory.Convert( * visitedBinaryExpression.Left, * visitedBinaryExpression.Left.Type, * _jsonTypeMapping), * visitedBinaryExpression.Right, * _jsonTypeMapping); * } */ } return(visitedExpression); }
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments, IDiagnosticsLogger <DbLoggerCategory.Query> logger) { if (instance != null && instance.Type.IsGenericList() && method.Name == "get_Item" && arguments.Count == 1) { // Try translating indexing inside json column return(_jsonPocoTranslator.TranslateMemberAccess(instance, arguments[0], method.ReturnType)); } // Predicate-less Any - translate to a simple length check. if (method.IsClosedFormOf(_enumerableAnyWithoutPredicate) && arguments.Count == 1 && arguments[0].Type.TryGetElementType(out _) && arguments[0].TypeMapping is MySqlJsonTypeMapping) { return(_sqlExpressionFactory.GreaterThan( _jsonPocoTranslator.TranslateArrayLength(arguments[0]), _sqlExpressionFactory.Constant(0))); } if (method.DeclaringType != typeof(JsonElement) || !(instance.TypeMapping is MySqlJsonTypeMapping mapping)) { return(null); } // The root of the JSON expression is a ColumnExpression. We wrap that with an empty traversal // expression (col->'$'); subsequent traversals will gradually append the path into that. // Note that it's possible to call methods such as GetString() directly on the root, and the // empty traversal is necessary to properly convert it to a text. instance = instance is ColumnExpression columnExpression ? _sqlExpressionFactory.JsonTraversal( columnExpression, returnsText : false, typeof(string), mapping) : instance; if (method == _getProperty) { return(instance is MySqlJsonTraversalExpression prevPathTraversal ? prevPathTraversal.Append( ApplyPathLocationTypeMapping(arguments[0]), typeof(JsonElement), _typeMappingSource.FindMapping(typeof(JsonElement))) : null); } if (method == _arrayIndexer) { return(instance is MySqlJsonTraversalExpression prevPathTraversal ? prevPathTraversal.Append( _sqlExpressionFactory.JsonArrayIndex(ApplyPathLocationTypeMapping(arguments[0])), typeof(JsonElement), _typeMappingSource.FindMapping(typeof(JsonElement))) : null); } if (_getMethods.Contains(method.Name) && arguments.Count == 0 && instance is MySqlJsonTraversalExpression traversal) { return(ConvertFromJsonExtract( traversal.Clone( method.Name == nameof(JsonElement.GetString), method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType) ), method.ReturnType)); } if (method == _getArrayLength) { // Could return NULL if the path is not found, but we would be alright to throw then. return(_sqlExpressionFactory.NullableFunction( "JSON_LENGTH", new[] { instance }, typeof(int), false)); } if (method.Name.StartsWith("TryGet") && arguments.Count == 0) { throw new InvalidOperationException($"The TryGet* methods on {nameof(JsonElement)} aren't translated yet, use Get* instead.'"); } return(null); }
public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments) { var traversal = GetTraversalExpression(instance, arguments); if (traversal == null) { return(null); } if (typeof(JToken).IsAssignableFrom(method.DeclaringType) && method.Name == "get_Item" && arguments.Count == 1) { var indexExpression = _sqlExpressionFactory.ApplyDefaultTypeMapping(arguments[0]); if (method.DeclaringType == typeof(JArray) || indexExpression.Type == typeof(int)) { // Try translating indexing inside json column. return(_jsonPocoTranslator.TranslateMemberAccess( traversal, _sqlExpressionFactory.JsonArrayIndex(indexExpression), method.ReturnType)); } return(traversal.Append( ApplyPathLocationTypeMapping(arguments[0]), method.DeclaringType, _typeMappingSource.FindMapping(method.DeclaringType))); } // Support for .Value<T>() and .Value<U, T>(): if (instance == null && method.Name == nameof(global::Newtonsoft.Json.Linq.Extensions.Value) && method.DeclaringType == typeof(global::Newtonsoft.Json.Linq.Extensions) && method.IsGenericMethod && method.GetParameters().Length == 1 && arguments.Count == 1) { return(ConvertFromJsonExtract( traversal.Clone( method.ReturnType == typeof(string), method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType) ), method.ReturnType)); } // Support for Count() if (instance == null && method.Name == nameof(Enumerable.Count) && method.DeclaringType == typeof(Enumerable) && method.IsGenericMethod && method.GetParameters().Length == 1 && arguments.Count == 1) { return(_jsonPocoTranslator.TranslateArrayLength(traversal)); } // Predicate-less Any - translate to a simple length check. if (method.IsClosedFormOf(_enumerableAnyWithoutPredicate) && arguments.Count == 1 && arguments[0].Type.TryGetElementType(out _) && arguments[0].TypeMapping is MySqlJsonTypeMapping) { return(_sqlExpressionFactory.GreaterThan( _jsonPocoTranslator.TranslateArrayLength(arguments[0]), _sqlExpressionFactory.Constant(0))); } return(null); }