Skip to content

Commit 0aadb22

Browse files
committed
Fixes dotnet#33635
1 parent 4900726 commit 0aadb22

File tree

13 files changed

+553
-205
lines changed

13 files changed

+553
-205
lines changed

src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -884,10 +884,11 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
884884
}
885885

886886
// if object is nullable, add null safeguard before calling the function
887-
// we special-case Nullable<>.GetValueOrDefault, which doesn't need the safeguard
887+
// we special-case Nullable<>.GetValueOrDefault and Nullable<>.ToString, which don't need the safeguard
888888
if (methodCallExpression.Object != null
889889
&& @object!.Type.IsNullableType()
890-
&& methodCallExpression.Method.Name != nameof(Nullable<int>.GetValueOrDefault))
890+
&& methodCallExpression.Method.Name != nameof(Nullable<int>.GetValueOrDefault)
891+
&& methodCallExpression.Method.Name != nameof(Nullable<int>.ToString))
891892
{
892893
var result = (Expression)methodCallExpression.Update(
893894
Expression.Convert(@object, methodCallExpression.Object.Type),

src/EFCore.SqlServer/Query/Internal/SqlServerObjectToStringTranslator.cs

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Data;
45
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
56

67
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
@@ -101,6 +102,26 @@ public SqlServerObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFact
101102
_sqlExpressionFactory.Constant(true.ToString()));
102103
}
103104

105+
if (instance.Type.IsEnum)
106+
{
107+
if (instance.TypeMapping?.DbType == DbType.String
108+
|| instance.TypeMapping?.Converter?.ProviderClrType == typeof(string))
109+
{
110+
return _sqlExpressionFactory.MakeUnary(ExpressionType.Convert, instance, typeof(string));
111+
}
112+
else
113+
{
114+
var cases = Enum.GetValues(instance.Type)
115+
.Cast<object>()
116+
.Select(value => new CaseWhenClause(
117+
_sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(value)),
118+
_sqlExpressionFactory.Constant(value.ToString(), typeof(string))))
119+
.ToArray();
120+
121+
return _sqlExpressionFactory.Case(cases, _sqlExpressionFactory.Constant(string.Empty, typeof(string)));
122+
}
123+
}
124+
104125
return TypeMapping.TryGetValue(instance.Type, out var storeType)
105126
? _sqlExpressionFactory.Function(
106127
"CONVERT",

src/EFCore.Sqlite.Core/Query/Internal/SqliteObjectToStringTranslator.cs

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Data;
45
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
56

67
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal;
@@ -98,6 +99,26 @@ public SqliteObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFactory
9899
_sqlExpressionFactory.Constant(true.ToString()));
99100
}
100101

102+
if (instance.Type.IsEnum)
103+
{
104+
if (instance.TypeMapping?.DbType == DbType.String
105+
|| instance.TypeMapping?.Converter?.ProviderClrType == typeof(string))
106+
{
107+
return _sqlExpressionFactory.MakeUnary(ExpressionType.Convert, instance, typeof(string));
108+
}
109+
else
110+
{
111+
var cases = Enum.GetValues(instance.Type)
112+
.Cast<object>()
113+
.Select(value => new CaseWhenClause(
114+
_sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(value)),
115+
_sqlExpressionFactory.Constant(value?.ToString(), typeof(string))))
116+
.ToArray();
117+
118+
return _sqlExpressionFactory.Case(cases, _sqlExpressionFactory.Constant(string.Empty, typeof(string)));
119+
}
120+
}
121+
101122
return TypeMapping.Contains(instance.Type)
102123
? _sqlExpressionFactory.Convert(instance, typeof(string))
103124
: null;

test/EFCore.Specification.Tests/Query/GearsOfWarQueryFixtureBase.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
337337
b.HasOne(w => w.Owner).WithMany(g => g.Weapons).HasForeignKey(w => w.OwnerFullName).HasPrincipalKey(g => g.FullName);
338338
});
339339

340-
modelBuilder.Entity<Mission>().Property(m => m.Id).ValueGeneratedNever();
340+
modelBuilder.Entity<Mission>(
341+
b =>
342+
{
343+
b.Property(m => m.Id).ValueGeneratedNever();
344+
b.Property(m => m.Difficulty).HasConversion<string>();
345+
});
341346

342347
modelBuilder.Entity<SquadMission>(
343348
b =>

test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs

+28-10
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,34 @@ public virtual Task ToString_boolean_property_nullable(bool async)
8989
async,
9090
ss => ss.Set<LocustHorde>().Select(lh => lh.Eradicated.ToString()));
9191

92+
[ConditionalTheory]
93+
[MemberData(nameof(IsAsyncData))]
94+
public virtual Task ToString_enum_property_projection(bool async)
95+
=> AssertQuery(
96+
async,
97+
ss => ss.Set<Gear>().Select(g => g.Rank.ToString()));
98+
99+
[ConditionalTheory]
100+
[MemberData(nameof(IsAsyncData))]
101+
public virtual Task ToString_nullable_enum_property_projection(bool async)
102+
=> AssertQuery(
103+
async,
104+
ss => ss.Set<Weapon>().Select(w => w.AmmunitionType.ToString()));
105+
106+
[ConditionalTheory]
107+
[MemberData(nameof(IsAsyncData))]
108+
public virtual Task ToString_enum_contains(bool async)
109+
=> AssertQuery(
110+
async,
111+
ss => ss.Set<Mission>().Where(g => g.Difficulty.ToString().Contains("Med")).Select(g => g.CodeName));
112+
113+
[ConditionalTheory]
114+
[MemberData(nameof(IsAsyncData))]
115+
public virtual Task ToString_nullable_enum_contains(bool async)
116+
=> AssertQuery(
117+
async,
118+
ss => ss.Set<Weapon>().Where(w => w.AmmunitionType.ToString().Contains("Cart")).Select(g => g.Name));
119+
92120
[ConditionalTheory]
93121
[MemberData(nameof(IsAsyncData))]
94122
public virtual Task Include_multiple_one_to_one_and_one_to_many_self_reference(bool async)
@@ -3118,16 +3146,6 @@ public virtual Task Projecting_nullable_bool_in_conditional_works(bool async)
31183146
new { Prop = cg.Gear != null ? cg.Gear.HasSoulPatch : false }),
31193147
e => e.Prop);
31203148

3121-
[ConditionalTheory]
3122-
[MemberData(nameof(IsAsyncData))]
3123-
public virtual Task Enum_ToString_is_client_eval(bool async)
3124-
=> AssertQuery(
3125-
async,
3126-
ss =>
3127-
ss.Set<Gear>().OrderBy(g => g.SquadId)
3128-
.ThenBy(g => g.Nickname)
3129-
.Select(g => g.Rank.ToString()));
3130-
31313149
[ConditionalTheory]
31323150
[MemberData(nameof(IsAsyncData))]
31333151
public virtual Task Correlated_collections_naked_navigation_with_ToList(bool async)

test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/GearsOfWarData.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ public static IReadOnlyList<Mission> CreateMissions()
133133
Timeline = new DateTimeOffset(599898024001234567, new TimeSpan(1, 30, 0)),
134134
Duration = new TimeSpan(1, 2, 3),
135135
Date = new DateOnly(2020, 1, 1),
136-
Time = new TimeOnly(15, 30, 10)
136+
Time = new TimeOnly(15, 30, 10),
137+
Difficulty = MissionDifficulty.Low
137138
},
138139
new()
139140
{
@@ -143,7 +144,8 @@ public static IReadOnlyList<Mission> CreateMissions()
143144
Timeline = new DateTimeOffset(2, 3, 1, 8, 0, 0, new TimeSpan(-5, 0, 0)),
144145
Duration = new TimeSpan(0, 1, 2, 3, 456),
145146
Date = new DateOnly(1990, 11, 10),
146-
Time = new TimeOnly(10, 15, 50, 500)
147+
Time = new TimeOnly(10, 15, 50, 500),
148+
Difficulty = MissionDifficulty.Medium
147149
},
148150
new()
149151
{
@@ -153,7 +155,8 @@ public static IReadOnlyList<Mission> CreateMissions()
153155
Timeline = new DateTimeOffset(10, 5, 3, 12, 0, 0, new TimeSpan()),
154156
Duration = new TimeSpan(0, 1, 0, 15, 456),
155157
Date = new DateOnly(1, 1, 1),
156-
Time = new TimeOnly(0, 0, 0)
158+
Time = new TimeOnly(0, 0, 0),
159+
Difficulty = MissionDifficulty.Unknown
157160
}
158161
};
159162

test/EFCore.Specification.Tests/TestModels/GearsOfWarModel/Mission.cs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class Mission
1313
public TimeSpan Duration { get; set; }
1414
public DateOnly Date { get; set; }
1515
public TimeOnly Time { get; set; }
16+
public MissionDifficulty Difficulty { get; set; }
1617

1718
public virtual ICollection<SquadMission> ParticipatingSquads { get; set; }
1819
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel;
5+
6+
public enum MissionDifficulty
7+
{
8+
Unknown = 0,
9+
Low = 1,
10+
Medium = 2,
11+
High = 3,
12+
Extreme = 4
13+
}

0 commit comments

Comments
 (0)