diff --git a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs index cd4d7813860..1098802c9cf 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMapping.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMapping.cs @@ -434,6 +434,8 @@ public virtual DbParameter CreateParameter( parameter.Direction = ParameterDirection.Input; parameter.ParameterName = name; + value = ConvertUnderlyingEnumValueToEnum(value); + if (Converter != null) { value = Converter.ConvertToProvider(value); @@ -456,6 +458,15 @@ public virtual DbParameter CreateParameter( return parameter; } + // Enum when compared to constant will always have constant of integral type + // when enum would contain convert node. We remove the convert node but we also + // need to convert the integral value to enum value. + // This allows us to use converter on enum value or print enum value directly if supported by provider + private object ConvertUnderlyingEnumValueToEnum(object value) + => value?.GetType().IsInteger() == true && ClrType.UnwrapNullableType().IsEnum + ? Enum.ToObject(ClrType.UnwrapNullableType(), value) + : value; + /// /// Configures type information of a . /// @@ -473,15 +484,7 @@ protected virtual void ConfigureParameter([NotNull] DbParameter parameter) /// public virtual string GenerateSqlLiteral([CanBeNull] object value) { - // Enum when compared to constant will always have constant of integral type - // when enum would contain convert node. We remove the convert node but we also - // need to convert the integral value to enum value. - // This allows us to use converter on enum value or print enum value directly if supported by provider - if (value?.GetType().IsInteger() == true - && ClrType.UnwrapNullableType().IsEnum) - { - value = Enum.ToObject(ClrType.UnwrapNullableType(), value); - } + value = ConvertUnderlyingEnumValueToEnum(value); if (Converter != null) { diff --git a/src/EFCore/Storage/ValueConversion/ValueConverter`.cs b/src/EFCore/Storage/ValueConversion/ValueConverter`.cs index 9271af79072..4518aeb645a 100644 --- a/src/EFCore/Storage/ValueConversion/ValueConverter`.cs +++ b/src/EFCore/Storage/ValueConversion/ValueConverter`.cs @@ -39,8 +39,8 @@ private static Func SanitizeConverter(Expression v == null - ? (object)null - : compiled(Sanitize(v)); + ? (object)null + : compiled(Sanitize(v)); } private static T Sanitize(object value) diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 3494e10dfd2..e763cb39903 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -82,5 +82,11 @@ public override Task Group_by_on_StartsWith_with_null_parameter_as_argument(bool { return base.Group_by_on_StartsWith_with_null_parameter_as_argument(async); } + + [ConditionalTheory(Skip = "issue #18284")] + public override Task Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(bool async) + { + return base.Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(async); + } } } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index c436c8db3e7..5501db8f5df 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -7362,6 +7362,54 @@ public virtual Task Where_TimeSpan_Milliseconds(bool async) .Where(m => m.Duration.Milliseconds == 1)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(bool async) + { + var prm = (int)AmmunitionType.Cartridge; + + return AssertQuery( + async, + ss => ss.Set().Where(w => prm == (int)w.AmmunitionType), + ss => ss.Set().Where(w => w.AmmunitionType != null && prm == (int)w.AmmunitionType)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Enum_flags_closure_typed_as_underlying_type_generates_correct_parameter_type(bool async) + { + var prm = (int)MilitaryRank.Private + (int)MilitaryRank.Sergeant + (int)MilitaryRank.General; + + return AssertQuery( + async, + ss => ss.Set() + .Where(g => (prm & (int)g.Rank) == (int)g.Rank)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Enum_flags_closure_typed_as_different_type_generates_correct_parameter_type(bool async) + { + var prm = (byte)MilitaryRank.Private + (byte)MilitaryRank.Sergeant; + + return AssertQuery( + async, + ss => ss.Set() + .Where(g => (prm & (short)g.Rank) == (short)g.Rank)); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Constant_enum_with_same_underlying_value_as_previously_parameterized_int(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set() + .OrderBy(g => g.Nickname) + .Take(1) + .Select(g => g.Rank & MilitaryRank.Private)); + } + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); protected virtual void ClearLog() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 3bab0d87056..c48acc1e441 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -7568,6 +7568,51 @@ FROM [Missions] AS [m] WHERE DATEPART(millisecond, [m].[Duration]) = 1"); } + public override async Task Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(bool async) + { + await base.Enum_closure_typed_as_underlying_type_generates_correct_parameter_type(async); + + AssertSql( + @"@__prm_0='1' + +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +FROM [Weapons] AS [w] +WHERE @__prm_0 = [w].[AmmunitionType]"); + } + + public override async Task Enum_flags_closure_typed_as_underlying_type_generates_correct_parameter_type(bool async) + { + await base.Enum_flags_closure_typed_as_underlying_type_generates_correct_parameter_type(async); + + AssertSql( + @"@__prm_0='133' + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') AND ((@__prm_0 & [g].[Rank]) = [g].[Rank])"); + } + + public override async Task Enum_flags_closure_typed_as_different_type_generates_correct_parameter_type(bool async) + { + await base.Enum_flags_closure_typed_as_different_type_generates_correct_parameter_type(async); + + AssertSql( + @""); + } + + public override async Task Constant_enum_with_same_underlying_value_as_previously_parameterized_int(bool async) + { + await base.Constant_enum_with_same_underlying_value_as_previously_parameterized_int(async); + + AssertSql( + @"@__p_0='1' + +SELECT TOP(@__p_0) [g].[Rank] & @__p_0 +FROM [Gears] AS [g] +WHERE [g].[Discriminator] IN (N'Gear', N'Officer') +ORDER BY [g].[Nickname]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs index 52e770493d0..5c67c3eb3ff 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryBugsTest.cs @@ -7109,6 +7109,62 @@ private class CustomerView19708 #endregion + //[ConditionalFact] + + //public void Test19128() + //{ + // var db = new LeadsDbContext(); + // db.Database.EnsureDeleted(); + // db.Database.EnsureCreated(); + // var downloadLead = new Lead { LeadType = LeadType.Download }; + // var productLead = new Lead { LeadType = LeadType.Product }; + // var leads = new List { downloadLead, productLead }; + // db.AddRange(leads); + // db.SaveChanges(); + + // //fails + // var sumLeadTypes = (int)LeadType.Download + (int)LeadType.Product; + // var matchingLeads = db.Leads + // .Where(l => (sumLeadTypes & (int)l.LeadType) == (int)l.LeadType) + // .ToList(); + + // // works + // //var sumLeadTypes2 = LeadType.Download | LeadType.Product; + // //var matchingLeads2 = db.Leads + // // .Where(l => (sumLeadTypes2 & l.LeadType) == l.LeadType) + // // .ToList(); + //} + + //public class Lead + //{ + // [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + // public int Id { get; set; } + // public LeadType LeadType { get; set; } + //} + + //[Flags] + //public enum LeadType : short + //{ + // PremiumContent = 1, + // ActiveProjectLeads = 2, + // Product = 4, + // ContactRequest = 8, + // Shared = 16, + // Download = 32, + // //InternalHelpRequest = 64, + // //Article = 128 + //} + + //public class LeadsDbContext : DbContext + //{ + // public DbSet Leads { get; set; } + + // protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + // { + // optionsBuilder.UseSqlServer(@"Server=.;Database=Repro19128;Trusted_Connection=True;MultipleActiveResultSets=True"); + // } + //} + private DbContextOptions _options; private SqlServerTestStore CreateTestStore(