public SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList <SqlExpression> arguments) { // TODO: Fully support List<> if (arguments.Count == 0) { return(null); } var operand = arguments[0]; var operandElementType = operand.Type.IsArray ? operand.Type.GetElementType() : operand.Type.IsGenericType && operand.Type.GetGenericTypeDefinition() == typeof(List <>) ? operand.Type.GetGenericArguments()[0] : null; if (operandElementType == null) // Not an array/list { return(null); } // Even if the CLR type is an array/list, it may be mapped to a non-array database type (e.g. via value converters). if (operand.TypeMapping is RelationalTypeMapping typeMapping && !(typeMapping is NpgsqlArrayTypeMapping) && !(typeMapping is NpgsqlJsonTypeMapping)) { return(null); } if (method.IsClosedFormOf(SequenceEqual) && arguments[1].Type.IsArray) { return(_sqlExpressionFactory.Equal(operand, arguments[1])); } // Predicate-less Any - translate to a simple length check. if (method.IsClosedFormOf(EnumerableAnyWithoutPredicate)) { return(_sqlExpressionFactory.GreaterThan( _jsonPocoTranslator.TranslateArrayLength(operand) ?? _sqlExpressionFactory.Function("cardinality", arguments, typeof(int?)), _sqlExpressionFactory.Constant(0))); } // Note that .Where(e => new[] { "a", "b", "c" }.Any(p => e.SomeText == p))) // is pattern-matched in AllAnyToContainsRewritingExpressionVisitor, which transforms it to // new[] { "a", "b", "c" }.Contains(e.SomeText). // Here we go further, and translate that to the PostgreSQL-specific construct e.SomeText = ANY (@p) - // this is superior to the general solution which will expand parameters to constants, // since non-PG SQL does not support arrays. If the list is a constant we leave it for regular IN // (functionality the same but more familiar). // Note: we exclude constant array expressions from this PG-specific optimization since the general // EF Core mechanism is fine for that case. After https://github.com/aspnet/EntityFrameworkCore/issues/16375 // is done we may not need the check any more. // Note: we exclude arrays/lists over Nullable<T> since the ADO layer doesn't handle them (but will in 5.0) if (method.IsClosedFormOf(Contains) && _sqlExpressionFactory.FindMapping(operand.Type) != null && !(operand is SqlConstantExpression) && Nullable.GetUnderlyingType(operandElementType) == null) { var item = arguments[1]; // We require a null semantics check in case the item is null and the array contains a null. // Advanced parameter sniffing would help here: https://github.com/aspnet/EntityFrameworkCore/issues/17598 return(_sqlExpressionFactory.OrElse( // We need to coalesce to false since 'x' = ANY ({'y', NULL}) returns null, not false // (and so will be null when negated too) _sqlExpressionFactory.Coalesce( _sqlExpressionFactory.ArrayAnyAll(item, operand, ArrayComparisonType.Any, "="), _sqlExpressionFactory.Constant(false)), _sqlExpressionFactory.AndAlso( _sqlExpressionFactory.IsNull(item), _sqlExpressionFactory.IsNotNull( _sqlExpressionFactory.Function( "array_position", new[] { operand, _sqlExpressionFactory.Fragment("NULL") }, typeof(int)))))); } // Note: we also translate .Where(e => new[] { "a", "b", "c" }.Any(p => EF.Functions.Like(e.SomeText, p))) // to LIKE ANY (...). See NpgsqlSqlTranslatingExpressionVisitor.VisitMethodCall. return(null); }