Skip to content

Support DateOnly and TimeOnly - #450

Merged
xuzhg merged 3 commits into
mainfrom
DateTimeOnlyNet6
Jan 21, 2022
Merged

Support DateOnly and TimeOnly#450
xuzhg merged 3 commits into
mainfrom
DateTimeOnlyNet6

Conversation

@xuzhg

@xuzhg xuzhg commented Jan 20, 2022

Copy link
Copy Markdown
Member

#413

Support DateOnly & TimeOnly new types defined in .NET 6.

  1. Add the .NET 6.0 target framework to introduce new types (DateOnly and TimeOnly)
  2. Support serialize of DateOnly & TimeOnly as "Date" and "TimeOfDay"
  3. Support deserialize of DateOnly & TimeOnly from "Date" and "TimeOfDay".
  4. Support the query option on DateOnly & TimeOnly, for example $filter.

To be clear: EF core 6.0 doesn't support DateOnly and TimeOnly. See dotnet/efcore#24507

return Type.GetTypeCode(underlyingTypeOrSelf) == TypeCode.DateTime;
}

#if NET6_0

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have you considered moving NET6-specific members into a partial file? That might make it easier to manage in the future. Something like TypeHelper.Net6.cs for example. #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Since there's only two new methods added, i'd like to create a new file until we have more .NET 6 specify methods added. Thanks for your suggestion.

Comment on lines +94 to +100
internal static bool IsDateOnly(Type clrType)
{
Type underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(clrType);
return underlyingTypeOrSelf == typeof(DateOnly);
}

internal static bool IsTimeOnly(Type clrType)

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the whole TypeHelper class is already internal, I'd recommend leaving these methods as public:
#Resolved

Comment on lines +94 to +100
internal static bool IsDateOnly(Type clrType)
{
Type underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(clrType);
return underlyingTypeOrSelf == typeof(DateOnly);
}

internal static bool IsTimeOnly(Type clrType)

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I realize there are already similar methods here (for example, IsDateTime). However, have you considered making these extension methods instead? That would make it slightly more readable for callers to use them.

TypeHelper is already static so it would be fairly easy to convert them. #Resolved

Type underlyingTypeOrSelf = GetUnderlyingTypeOrSelf(clrType);
return underlyingTypeOrSelf == typeof(TimeOnly);
}
#endif

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You might want to leave an extra blank line after this. #Resolved


#if NET6_0
BuildTypeMapping<DateOnly>(EdmPrimitiveTypeKind.Date, isStandard: false);
BuildTypeMapping<DateOnly?>(EdmPrimitiveTypeKind.Date, isStandard: false);

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I assume there is no behavior difference in the order we add these mappings, but I'd still suggest adding the nullable versions prior to the non-nullable ones for consistency: all other types in this method are following that pattern.

FAKE EDIT: Actually, scratch that: the order does seem to matter, as per the comment at the top of the method:

// Do not change the order for the nullable or non-nullable. Put nullable ahead of non-nullable.
// By design: non-nullable will overwrite the item1.

I'd even go as far as to suggest a small method to encapsulate this, something like:

        public void BuildStructTypeMapping<T>(EdmPrimitiveTypeKind edmPrimitiveTypeKind, bool isStandard)
            where T : struct
        {
            BuildTypeMapping<T?>(edmPrimitiveTypeKind, isStandard);
            BuildTypeMapping<T>(edmPrimitiveTypeKind, isStandard);
        }

Using this for all structs in the method would simplify it quite a bit and centralize the order concern (the comment could be even moved over into this method). #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Would you mind sharing with me a contribution for this? I'd love to take and merge it for you.

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For sure. I'll wait until you merge this and then refactor the whole block so as to avoid you having to rebase it.

For now, just make sure the order is as per the comment and we should be good.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, you are right for the order. I forget what i said. OMG

property = ClrCanonicalFunctions.DateProperties[node.Name];
}
#if NET6_0
else if (ExpressionBinderHelper.IsType<DateOnly>(parameter.Type))

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have you considered adding a IsDateOnly and IsTimeOnly methods for consistency with the way other checks are made?

I understand they are trivial 1 liners, but it makes the code more consistent when the checks use the same approaches.

Otherwise, when reading this code, it immediately raises questions to me like "wait, but why are we not using IsType<DateTime> as well?", you know? #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

:) I have the same concern.


Expression[] arguments = BindArguments(node.Parameters, context);
Contract.Assert(arguments.Length == 1 && (ExpressionBinderHelper.IsTimeRelated(arguments[0].Type)));

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unintended line break? #Resolved

property = ClrCanonicalFunctions.TimeOfDayProperties[node.Name];
}
#if NET6_0
else if (ExpressionBinderHelper.IsType<TimeOnly>(parameter.Type))

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above regarding IsType<T> vs IsTimeOnly. #Resolved

return result;
}

[SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "<Pending>")]

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also missing justification (and also apparently unrelated to the changes). #Resolved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

.NET 6 introduces a new overload for Append and raises a compile waring to use the old overload.

So, before .NET 6, it's ok to build without a warning. however, there's a build warning after add .NET 6 target framework.

Since I have to add .NET 6 target framework to introduce DateOnly and TimeOnly type, so, a build warning raises.

Originally, i use
#if NET6_0
// call new overload
#else
// call old overload
#endif

But, i think it's easy to suppress the message since that's only for debugging purposes. Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.NET 6 introduces a new overload for Append and raises a compile waring to use the old overload.

Ah I see, so that's why it is only raised now. Gotcha.

Originally, i use
#if NET6_0
// call new overload
#else
// call old overload
#endif

But, i think it's easy to suppress the message since that's only for debugging purposes. Thoughts?

I'd agree. The docs for this rule state:

It is safe to suppress a warning from this rule when it is certain that the default format is the correct choice, and where code maintainability is not an important development priority.

I LOL'd at the second part ("nah, this doesn't need to be maintainable" 🤣 ) but the first part says that if you are ok with the defaults it is a safe suppression.

Just make sure to replace the "<Pending>" there with a simple explanation, maybe like "The default format provider is fine here." or something.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Took your suggestion. Thanks.

Comment on lines +41 to +46
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="6.0.0" />
</ItemGroup>

@julealgon julealgon Jan 20, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Considering none of the changes related to TimeOnly and DateOnly have any relationship to EF (I'm assuming this), do we really need to do this? #WontFix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, I originally thought There were many requirements for DateOnly and TimeOnly because EF Core supports them.

So, when I first draft the e2e, I assume to use EF Core 6.0. However, it seems EF Core doesn't support them until EF Core 7.

So, I forgot to remove them. Thanks.

Of course, there's a workaround to map the DateOnly and TimeOnly to "date" and "time". I'd like to try that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also, i think it's ok to add them since I added .NET 5 target framework, and make sure the test running under .NET 6 runtime using the 6.0 EF core nuget.

@julealgon

Copy link
Copy Markdown
Contributor

You might also want to add a comment to the description to hard link this PR with #413.

@xuzhg xuzhg linked an issue Jan 20, 2022 that may be closed by this pull request
@xuzhg

xuzhg commented Jan 20, 2022

Copy link
Copy Markdown
Member Author

I already added the link between issue and pr. did i miss something?


In reply to: 1017831541

@xuzhg
xuzhg merged commit dad7c4c into main Jan 21, 2022
@xuzhg
xuzhg deleted the DateTimeOnlyNet6 branch January 21, 2022 07:37
kakone pushed a commit to kakone/AspNetCoreOData that referenced this pull request Apr 3, 2022
* Support DateOnly and TimeOnly

* Update the nuget nuspec to include .NET 6 target framework

* Resolve review comment. Thanks @juliano Leal Goncalves
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for DateOnly and TimeOnly types

2 participants