Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to #33678 - TimeOnly.FromDateTime() could not be translated in EF Core 8 #33689

Merged
merged 1 commit into from
May 8, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this sufficiently resilient? What if another overload is added with one parameter? Check for the parameter type as well to be sure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetRuntimeMethod already checks parameter type

{
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 @@ -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