Skip to content

Commit

Permalink
Fix to #33678 - TimeOnly.FromDateTime() could not be translated in EF…
Browse files Browse the repository at this point in the history
… Core 8

Adding translation for TimeOnly.FromDateTime and TimeOnly.FromTimeSpan. We already had support for DateOnly.FromDateTime.

Fixes #33678
  • Loading branch information
maumar committed May 8, 2024
1 parent 4c4edf7 commit 9c66372
Show file tree
Hide file tree
Showing 10 changed files with 760 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public class SqlServerTimeOnlyMethodTranslator : IMethodCallTranslator
private static readonly MethodInfo IsBetweenMethod = typeof(TimeOnly).GetRuntimeMethod(
nameof(TimeOnly.IsBetween), [typeof(TimeOnly), typeof(TimeOnly)])!;

private static readonly MethodInfo FromDateTime = typeof(TimeOnly).GetRuntimeMethod(
nameof(TimeOnly.FromDateTime), [typeof(DateTime)])!;

private static readonly MethodInfo FromTimeSpan = typeof(TimeOnly).GetRuntimeMethod(
nameof(TimeOnly.FromTimeSpan), [typeof(TimeSpan)])!;

private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
Expand All @@ -48,7 +54,19 @@ public SqlServerTimeOnlyMethodTranslator(ISqlExpressionFactory sqlExpressionFact
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method.DeclaringType != typeof(TimeOnly) || instance is null)
if (method.DeclaringType != typeof(TimeOnly))
{
return null;
}

if ((method == FromDateTime || method == FromTimeSpan)
&& instance is null
&& arguments.Count == 1)
{
return _sqlExpressionFactory.Convert(arguments[0], typeof(TimeOnly));
}

if (instance is null)
{
return null;
}
Expand Down
77 changes: 77 additions & 0 deletions test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7911,6 +7911,83 @@ public virtual Task Where_TimeOnly_subtract_TimeOnly(bool async)
async,
ss => ss.Set<Mission>().Where(m => m.Time - new TimeOnly(10, 0, 0) == new TimeSpan(0, 0, 15, 50, 500)).AsTracking());
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeOnly_FromDateTime_compared_to_property(bool async)
=> AssertQuery(
async,
ss => from t in ss.Set<CogTag>()
from m in ss.Set<Mission>()
where TimeOnly.FromDateTime(t.IssueDate) == m.Time
select new { TagId = t.Id, MissionId = m.Id },
elementSorter: e => (e.TagId, e.MissionId));
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeOnly_FromDateTime_compared_to_parameter(bool async)
{
var time = new TimeOnly(2, 0, 0);
return AssertQuery(
async,
ss => ss.Set<CogTag>().Where(x => x.Gear != null && TimeOnly.FromDateTime(x.IssueDate.AddHours(x.Gear.SquadId)) == time));
}
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeOnly_FromDateTime_compared_to_constant(bool async)
=> AssertQuery(
async,
ss => ss.Set<CogTag>().Where(x => TimeOnly.FromDateTime(x.IssueDate.AddHours(x.Note.Length)) > new TimeOnly(9, 0, 0)));
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeOnly_FromTimeSpan_compared_to_property(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().Where(x => TimeOnly.FromTimeSpan(x.Duration) < x.Time));
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_TimeOnly_FromTimeSpan_compared_to_parameter(bool async)
{
var time = new TimeOnly(1, 2, 3);
return AssertQuery(
async,
ss => ss.Set<Mission>().Where(x => TimeOnly.FromTimeSpan(x.Duration) == time));
}
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Order_by_TimeOnly_FromTimeSpan(bool async)
=> AssertQuery(
async,
ss => ss.Set<Mission>().OrderBy(x => TimeOnly.FromTimeSpan(x.Duration)),
assertOrder: true);
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_DateOnly_FromDateTime_compared_to_property(bool async)
=> AssertQuery(
async,
ss => from t in ss.Set<CogTag>()
from m in ss.Set<Mission>()
where DateOnly.FromDateTime(t.IssueDate) > m.Date
select new { TagId = t.Id, MissionId = m.Id },
elementSorter: e => (e.TagId, e.MissionId));
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Where_DateOnly_FromDateTime_compared_to_constant_and_parameter(bool async)
{
var prm = new DateOnly(2, 10, 11);
return AssertQuery(
async,
ss => ss.Set<CogTag>().Where(x => new[] { prm, new DateOnly(15, 3, 7) }.Contains(DateOnly.FromDateTime(x.IssueDate))));
}
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Basic_query_gears(bool async)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2405,4 +2405,47 @@ ELSE NULL
END = N'COUNTRY'
""");
}



[ConditionalFact]
public async Task MyRepro()
{

using var context = new MyContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

var myTime = new TimeOnly(12, 0);
var list = await context.Persons.Where(p => TimeOnly.FromDateTime(p.Time) >= myTime).ToListAsync();
}


public class MyContext : DbContext
{
public DbSet<Person> Persons { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Repro;Trusted_Connection=True;MultipleActiveResultSets=true");
}
}


public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Time { get; set; }
}






}
Original file line number Diff line number Diff line change
Expand Up @@ -9260,6 +9260,111 @@ public override async Task Where_TimeOnly_subtract_TimeOnly(bool async)
AssertSql();
}

public override async Task Where_TimeOnly_FromDateTime_compared_to_property(bool async)
{
await base.Where_TimeOnly_FromDateTime_compared_to_property(async);

AssertSql(
"""
SELECT [t].[Id] AS [TagId], [m].[Id] AS [MissionId]
FROM [Tags] AS [t]
CROSS JOIN [Missions] AS [m]
WHERE CAST([t].[IssueDate] AS time) = [m].[Time]
""");
}

public override async Task Where_TimeOnly_FromDateTime_compared_to_parameter(bool async)
{
await base.Where_TimeOnly_FromDateTime_compared_to_parameter(async);

AssertSql(
"""
@__time_0='02:00' (DbType = Time)

SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
FROM [Tags] AS [t]
LEFT JOIN [Gears] AS [g] ON [t].[GearNickName] = [g].[Nickname] AND [t].[GearSquadId] = [g].[SquadId]
WHERE [g].[Nickname] IS NOT NULL AND [g].[SquadId] IS NOT NULL AND CAST(DATEADD(hour, CAST(CAST([g].[SquadId] AS float) AS int), [t].[IssueDate]) AS time) = @__time_0
""");
}

public override async Task Where_TimeOnly_FromDateTime_compared_to_constant(bool async)
{
await base.Where_TimeOnly_FromDateTime_compared_to_constant(async);

AssertSql(
"""
SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
FROM [Tags] AS [t]
WHERE CAST(DATEADD(hour, CAST(CAST(CAST(LEN([t].[Note]) AS int) AS float) AS int), [t].[IssueDate]) AS time) > '09:00:00'
""");
}

public override async Task Where_TimeOnly_FromTimeSpan_compared_to_property(bool async)
{
await base.Where_TimeOnly_FromTimeSpan_compared_to_property(async);

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE CAST([m].[Duration] AS time) < [m].[Time]
""");
}

public override async Task Where_TimeOnly_FromTimeSpan_compared_to_parameter(bool async)
{
await base.Where_TimeOnly_FromTimeSpan_compared_to_parameter(async);

AssertSql(
"""
@__time_0='01:02' (DbType = Time)

SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE CAST([m].[Duration] AS time) = @__time_0
""");
}

public override async Task Order_by_TimeOnly_FromTimeSpan(bool async)
{
await base.Order_by_TimeOnly_FromTimeSpan(async);

AssertSql(
"""
SELECT [m].[Id], [m].[BriefingDocument], [m].[BriefingDocumentFileExtension], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
ORDER BY CAST([m].[Duration] AS time)
""");
}

public override async Task Where_DateOnly_FromDateTime_compared_to_property(bool async)
{
await base.Where_DateOnly_FromDateTime_compared_to_property(async);

AssertSql(
"""
SELECT [t].[Id] AS [TagId], [m].[Id] AS [MissionId]
FROM [Tags] AS [t]
CROSS JOIN [Missions] AS [m]
WHERE CAST([t].[IssueDate] AS date) > [m].[Date]
""");
}

public override async Task Where_DateOnly_FromDateTime_compared_to_constant_and_parameter(bool async)
{
await base.Where_DateOnly_FromDateTime_compared_to_constant_and_parameter(async);

AssertSql(
"""
@__prm_0='10/11/0002' (DbType = Date)

SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
FROM [Tags] AS [t]
WHERE CAST([t].[IssueDate] AS date) IN (@__prm_0, '0015-03-07')
""");
}

public override async Task Include_on_entity_that_is_not_present_in_final_projection_but_uses_TypeIs_instead(bool async)
{
await base.Include_on_entity_that_is_not_present_in_final_projection_but_uses_TypeIs_instead(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11861,6 +11861,117 @@ public override async Task Where_TimeOnly_subtract_TimeOnly(bool async)
AssertSql();
}

public override async Task Where_TimeOnly_FromDateTime_compared_to_property(bool async)
{
await base.Where_TimeOnly_FromDateTime_compared_to_property(async);

AssertSql(
"""
SELECT [t].[Id] AS [TagId], [m].[Id] AS [MissionId]
FROM [Tags] AS [t]
CROSS JOIN [Missions] AS [m]
WHERE CAST([t].[IssueDate] AS time) = [m].[Time]
""");
}

public override async Task Where_TimeOnly_FromDateTime_compared_to_parameter(bool async)
{
await base.Where_TimeOnly_FromDateTime_compared_to_parameter(async);

AssertSql(
"""
@__time_0='02:00' (DbType = Time)

SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
FROM [Tags] AS [t]
LEFT JOIN (
SELECT [g].[Nickname], [g].[SquadId]
FROM [Gears] AS [g]
UNION ALL
SELECT [o].[Nickname], [o].[SquadId]
FROM [Officers] AS [o]
) AS [u] ON [t].[GearNickName] = [u].[Nickname] AND [t].[GearSquadId] = [u].[SquadId]
WHERE [u].[Nickname] IS NOT NULL AND [u].[SquadId] IS NOT NULL AND CAST(DATEADD(hour, CAST(CAST([u].[SquadId] AS float) AS int), [t].[IssueDate]) AS time) = @__time_0
""");
}

public override async Task Where_TimeOnly_FromDateTime_compared_to_constant(bool async)
{
await base.Where_TimeOnly_FromDateTime_compared_to_constant(async);

AssertSql(
"""
SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
FROM [Tags] AS [t]
WHERE CAST(DATEADD(hour, CAST(CAST(CAST(LEN([t].[Note]) AS int) AS float) AS int), [t].[IssueDate]) AS time) > '09:00:00'
""");
}

public override async Task Where_TimeOnly_FromTimeSpan_compared_to_property(bool async)
{
await base.Where_TimeOnly_FromTimeSpan_compared_to_property(async);

AssertSql(
"""
SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE CAST([m].[Duration] AS time) < [m].[Time]
""");
}

public override async Task Where_TimeOnly_FromTimeSpan_compared_to_parameter(bool async)
{
await base.Where_TimeOnly_FromTimeSpan_compared_to_parameter(async);

AssertSql(
"""
@__time_0='01:02' (DbType = Time)

SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
WHERE CAST([m].[Duration] AS time) = @__time_0
""");
}

public override async Task Order_by_TimeOnly_FromTimeSpan(bool async)
{
await base.Order_by_TimeOnly_FromTimeSpan(async);

AssertSql(
"""
SELECT [m].[Id], [m].[CodeName], [m].[Date], [m].[Duration], [m].[Rating], [m].[Time], [m].[Timeline]
FROM [Missions] AS [m]
ORDER BY CAST([m].[Duration] AS time)
""");
}

public override async Task Where_DateOnly_FromDateTime_compared_to_property(bool async)
{
await base.Where_DateOnly_FromDateTime_compared_to_property(async);

AssertSql(
"""
SELECT [t].[Id] AS [TagId], [m].[Id] AS [MissionId]
FROM [Tags] AS [t]
CROSS JOIN [Missions] AS [m]
WHERE CAST([t].[IssueDate] AS date) > [m].[Date]
""");
}

public override async Task Where_DateOnly_FromDateTime_compared_to_constant_and_parameter(bool async)
{
await base.Where_DateOnly_FromDateTime_compared_to_constant_and_parameter(async);

AssertSql(
"""
@__prm_0='10/11/0002' (DbType = Date)

SELECT [t].[Id], [t].[GearNickName], [t].[GearSquadId], [t].[IssueDate], [t].[Note]
FROM [Tags] AS [t]
WHERE CAST([t].[IssueDate] AS date) IN (@__prm_0, '0015-03-07')
""");
}

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

0 comments on commit 9c66372

Please sign in to comment.