Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

Expand All @@ -28,6 +29,9 @@ public class SqlNullabilityProcessor : ExpressionVisitor
/// </summary>
private readonly Dictionary<SqlParameterExpression, List<SqlParameterExpression>> _collectionParameterExpansionMap;

private static readonly bool UseOldBehavior37216 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37216", out var enabled) && enabled;

/// <summary>
/// Creates a new instance of the <see cref="SqlNullabilityProcessor" /> class.
/// </summary>
Expand Down Expand Up @@ -185,6 +189,33 @@ protected override Expression VisitExtension(Expression node)
throw new UnreachableException();
}

// We've inlined the user-provided collection from the values parameter into the SQL VALUES expression: (VALUES (1), (2)...).
// However, if the collection happens to be empty, this doesn't work as VALUES does not support empty sets. We convert it
// to a SELECT ... WHERE false to produce an empty result set instead.
if (!UseOldBehavior37216 && processedValues.Count == 0)
{
var select = new SelectExpression(
valuesExpression.Alias,
tables: [],
predicate: new SqlConstantExpression(false, Dependencies.TypeMappingSource.FindMapping(typeof(bool))),
groupBy: [],
having: null,
projections: valuesExpression.ColumnNames
.Select(n => new ProjectionExpression(
new SqlConstantExpression(value: null, elementTypeMapping.ClrType, elementTypeMapping), n))
.ToList(),
distinct: false,
orderings: [],
offset: null,
limit: null,
tags: ReadOnlySet<string>.Empty,
annotations: null,
sqlAliasManager: null!,
isMutable: false);

return select;
}

return valuesExpression.Update(processedValues);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,28 @@ WHERE ARRAY_CONTAINS(@ints, c["Int"])
""");
}

public override async Task Parameter_collection_empty_Contains()
{
await base.Parameter_collection_empty_Contains();

AssertSql(
"""
@ints='[]'

SELECT VALUE c
FROM root c
WHERE ARRAY_CONTAINS(@ints, c["Int"])
""");
}

public override async Task Parameter_collection_empty_Join()
{
// Cosmos join support. Issue #16920.
await AssertTranslationFailed(base.Parameter_collection_empty_Join);

AssertSql();
}

public override async Task Parameter_collection_Contains_with_EF_Constant()
{
// #34327
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,26 @@ await AssertQuery(
assertEmpty: true);
}

[ConditionalFact]
public virtual async Task Parameter_collection_empty_Contains()
{
int[] ints = [];

await AssertQuery(
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => ints.Contains(c.Int)),
assertEmpty: true);
}

[ConditionalFact] // #37216
public virtual async Task Parameter_collection_empty_Join()
{
int[] ints = [];

await AssertQuery(
ss => ss.Set<PrimitiveCollectionsEntity>().Join(ints, e => e.Id, i => i, (e, i) => e),
assertEmpty: true);
}

[ConditionalFact]
public virtual Task Parameter_collection_Contains_with_EF_Constant()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,33 @@ FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Parameter_collection_empty_Contains()
{
await base.Parameter_collection_empty_Contains();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE 0 = 1
""");
}

public override async Task Parameter_collection_empty_Join()
{
await base.Parameter_collection_empty_Join();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
INNER JOIN (
SELECT NULL AS [Value]
WHERE 0 = 1
) AS [p0] ON [p].[Id] = [p0].[Value]
""");
}

public override async Task Parameter_collection_Contains_with_EF_Constant()
{
await base.Parameter_collection_Contains_with_EF_Constant();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,33 @@ FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Parameter_collection_empty_Contains()
{
await base.Parameter_collection_empty_Contains();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE 0 = 1
""");
}

public override async Task Parameter_collection_empty_Join()
{
await base.Parameter_collection_empty_Join();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
INNER JOIN (
SELECT NULL AS [Value]
WHERE 0 = 1
) AS [p0] ON [p].[Id] = [p0].[Value]
""");
}

public override async Task Parameter_collection_Contains_with_EF_Constant()
{
await base.Parameter_collection_Contains_with_EF_Constant();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,33 @@ FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Parameter_collection_empty_Contains()
{
await base.Parameter_collection_empty_Contains();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE 0 = 1
""");
}

public override async Task Parameter_collection_empty_Join()
{
await base.Parameter_collection_empty_Join();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
INNER JOIN (
SELECT NULL AS [Value]
WHERE 0 = 1
) AS [p0] ON [p].[Id] = [p0].[Value]
""");
}

public override async Task Column_collection_of_ints_Contains()
{
await base.Column_collection_of_ints_Contains();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,33 @@ FROM [PrimitiveCollectionsEntity] AS [p]
""");
}

public override async Task Parameter_collection_empty_Contains()
{
await base.Parameter_collection_empty_Contains();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE 0 = 1
""");
}

public override async Task Parameter_collection_empty_Join()
{
await base.Parameter_collection_empty_Join();

AssertSql(
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
INNER JOIN (
SELECT NULL AS [Value]
WHERE 0 = 1
) AS [p0] ON [p].[Id] = [p0].[Value]
""");
}

public override async Task Parameter_collection_Contains_with_EF_Constant()
{
await base.Parameter_collection_Contains_with_EF_Constant();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,33 @@ WHERE 0
""");
}

public override async Task Parameter_collection_empty_Contains()
{
await base.Parameter_collection_empty_Contains();

AssertSql(
"""
SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."NullableWrappedId", "p"."NullableWrappedIdWithNullableComparer", "p"."String", "p"."Strings", "p"."WrappedId"
FROM "PrimitiveCollectionsEntity" AS "p"
WHERE 0
""");
}

public override async Task Parameter_collection_empty_Join()
{
await base.Parameter_collection_empty_Join();

AssertSql(
"""
SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."NullableWrappedId", "p"."NullableWrappedIdWithNullableComparer", "p"."String", "p"."Strings", "p"."WrappedId"
FROM "PrimitiveCollectionsEntity" AS "p"
INNER JOIN (
SELECT NULL AS "Value"
WHERE 0
) AS "p0" ON "p"."Id" = "p0"."Value"
""");
}

public override async Task Parameter_collection_Contains_with_EF_Constant()
{
await base.Parameter_collection_Contains_with_EF_Constant();
Expand Down