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
16 changes: 13 additions & 3 deletions src/EFCore/Storage/ValueConversion/ValueConverter`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,19 @@ private static T Sanitize<T>(object value)
{
var unwrappedType = typeof(T).UnwrapNullableType();

return (T)(!unwrappedType.IsInstanceOfType(value)
? Convert.ChangeType(value, unwrappedType)
: value);
if (unwrappedType.IsInstanceOfType(value))
{
return (T)value;
}

// Convert.ChangeType cannot convert to enum types; use Enum.ToObject instead, which handles
// conversion from different enum types (with the same underlying type) or from integral types.
if (unwrappedType.IsEnum)
{
return (T)Enum.ToObject(unwrappedType, value);
}

return (T)Convert.ChangeType(value, unwrappedType);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,58 @@ public override async Task Project_inline_collection_with_Concat()
Assert.Equal(RelationalStrings.InsufficientInformationToIdentifyElementOfCollectionJoin, message);
}

// #38008
[ConditionalTheory, MemberData(nameof(ParameterTranslationModeValues))]
public virtual async Task Parameter_collection_of_enum_Cast_from_different_enum_type(ParameterTranslationMode mode)
{
var contextFactory = await InitializeNonSharedTest<Context38008>(
onConfiguring: b => SetParameterizedCollectionMode(b, mode),
seed: context =>
{
context.AddRange(
new Context38008.TestEntity38008 { Id = 1, Status = Context38008.EntityEnum.Clean },
new Context38008.TestEntity38008 { Id = 2, Status = Context38008.EntityEnum.Malware });
return context.SaveChangesAsync();
});

await using var context = contextFactory.CreateDbContext();

// Cast<EntityEnum>() returns a lazy IEnumerable whose boxed values retain the ViewModelEnum runtime type.
var filter = new[] { Context38008.ViewModelEnum.Malware }.Cast<Context38008.EntityEnum>();
var result = await context.Set<Context38008.TestEntity38008>()
.Where(a => filter.Any(f => f == a.Status))
.Select(a => a.Id)
.ToListAsync();

Assert.Equivalent(new[] { 2 }, result);
}

protected class Context38008(DbContextOptions options) : DbContext(options)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<TestEntity38008>().Property(e => e.Id).ValueGeneratedNever();

public class TestEntity38008
{
public int Id { get; set; }
public EntityEnum Status { get; set; }
}

[Flags]
public enum EntityEnum
{
Clean = 1,
Malware = 2
}

[Flags]
public enum ViewModelEnum
{
Clean = 1,
Malware = 2
}
}

protected class TestOwner
{
public int Id { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,54 @@ WHERE [t].[Id] IN (@ints1, @ints2, @ints3, @ints4, @ints5, @ints6, @ints7, @ints
""");
}

public override async Task Parameter_collection_of_enum_Cast_from_different_enum_type(ParameterTranslationMode mode)
{
switch (mode)
{
case ParameterTranslationMode.Constant:
{
await base.Parameter_collection_of_enum_Cast_from_different_enum_type(mode);
AssertSql(
"""
SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (CAST(2 AS int))) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.Parameter:
{
await AssertCompatibilityLevelTooLow(
() => base.Parameter_collection_Count_with_column_predicate_with_default_mode(mode));
break;
}

case ParameterTranslationMode.MultipleParameters:
{
await base.Parameter_collection_of_enum_Cast_from_different_enum_type(mode);
AssertSql(
"""
@filter1='2'

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (@filter1)) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

default:
throw new NotImplementedException();
}
}

public override async Task Static_readonly_collection_List_of_ints_Contains_int()
{
await base.Static_readonly_collection_List_of_ints_Contains_int();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,63 @@ WHERE [t].[Id] IN (@ints1, @ints2, @ints3, @ints4, @ints5, @ints6, @ints7, @ints
""");
}

public override async Task Parameter_collection_of_enum_Cast_from_different_enum_type(ParameterTranslationMode mode)
{
await base.Parameter_collection_of_enum_Cast_from_different_enum_type(mode);

switch (mode)
{
case ParameterTranslationMode.Constant:
{
AssertSql(
"""
SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (CAST(2 AS int))) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.Parameter:
{
AssertSql(
"""
@filter='[2]' (Size = 4000)

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM OPENJSON(@filter) WITH ([value] int '$') AS [f]
WHERE [f].[value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.MultipleParameters:
{
AssertSql(
"""
@filter1='2'

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (@filter1)) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

default:
throw new NotImplementedException();
}
}

public override async Task Static_readonly_collection_List_of_ints_Contains_int()
{
await base.Static_readonly_collection_List_of_ints_Contains_int();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,63 @@ WHERE [t].[Id] IN (@ints1, @ints2, @ints3, @ints4, @ints5, @ints6, @ints7, @ints
""");
}

public override async Task Parameter_collection_of_enum_Cast_from_different_enum_type(ParameterTranslationMode mode)
{
await base.Parameter_collection_of_enum_Cast_from_different_enum_type(mode);

switch (mode)
{
case ParameterTranslationMode.Constant:
{
AssertSql(
"""
SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (CAST(2 AS int))) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.Parameter:
{
AssertSql(
"""
@filter='[2]' (Size = 3)

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM OPENJSON(@filter) WITH ([value] int '$') AS [f]
WHERE [f].[value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.MultipleParameters:
{
AssertSql(
"""
@filter1='2'

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (@filter1)) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

default:
throw new NotImplementedException();
}
}

public override async Task Static_readonly_collection_List_of_ints_Contains_int()
{
await base.Static_readonly_collection_List_of_ints_Contains_int();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,63 @@ WHERE [t].[Id] IN (@ints1, @ints2, @ints3, @ints4, @ints5, @ints6, @ints7, @ints
""");
}

public override async Task Parameter_collection_of_enum_Cast_from_different_enum_type(ParameterTranslationMode mode)
{
await base.Parameter_collection_of_enum_Cast_from_different_enum_type(mode);

switch (mode)
{
case ParameterTranslationMode.Constant:
{
AssertSql(
"""
SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (CAST(2 AS int))) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.Parameter:
{
AssertSql(
"""
@filter='[2]' (Size = 4000)

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM OPENJSON(@filter) WITH ([value] int '$') AS [f]
WHERE [f].[value] = [t].[Status])
""");
break;
}

case ParameterTranslationMode.MultipleParameters:
{
AssertSql(
"""
@filter1='2'

SELECT [t].[Id]
FROM [TestEntity38008] AS [t]
WHERE EXISTS (
SELECT 1
FROM (VALUES (@filter1)) AS [f]([Value])
WHERE [f].[Value] = [t].[Status])
""");
break;
}

default:
throw new NotImplementedException();
}
}

public override async Task Static_readonly_collection_List_of_ints_Contains_int()
{
await base.Static_readonly_collection_List_of_ints_Contains_int();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,63 @@ public override async Task Parameter_collection_Contains_parameter_bucketization
""");
}

public override async Task Parameter_collection_of_enum_Cast_from_different_enum_type(ParameterTranslationMode mode)
{
await base.Parameter_collection_of_enum_Cast_from_different_enum_type(mode);

switch (mode)
{
case ParameterTranslationMode.Constant:
{
AssertSql(
"""
SELECT "t"."Id"
FROM "TestEntity38008" AS "t"
WHERE EXISTS (
SELECT 1
FROM (SELECT CAST(2 AS INTEGER) AS "Value") AS "f"
WHERE "f"."Value" = "t"."Status")
""");
break;
}

case ParameterTranslationMode.Parameter:
{
AssertSql(
"""
@filter='[2]' (Size = 3)

SELECT "t"."Id"
FROM "TestEntity38008" AS "t"
WHERE EXISTS (
SELECT 1
FROM json_each(@filter) AS "f"
WHERE "f"."value" = "t"."Status")
""");
break;
}

case ParameterTranslationMode.MultipleParameters:
{
AssertSql(
"""
@filter1='2'

SELECT "t"."Id"
FROM "TestEntity38008" AS "t"
WHERE EXISTS (
SELECT 1
FROM (SELECT @filter1 AS "Value") AS "f"
WHERE "f"."Value" = "t"."Status")
""");
break;
}

default:
throw new NotImplementedException();
}
}

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