From 2402a278bf04fc7b3271618763305f68b2a34d94 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 10 Oct 2024 11:27:37 -0700 Subject: [PATCH] Add a config to swith between DateOnly TimeOnly and Date and TimeOfDay --- src/Microsoft.OData.Cli/GenerateCommand.cs | 9 +++++ src/Microsoft.OData.Cli/GenerateOptions.cs | 5 +++ .../CodeGeneration/V4CodeGenDescriptor.cs | 2 ++ .../Models/BaseUserSettings.cs | 16 +++++++++ .../Models/ServiceConfiguration.cs | 1 + .../Templates/ODataT4CodeGenerator.cs | 36 ++++++++++++++++--- .../Templates/ODataT4CodeGenerator.tt | 6 +++- .../Templates/ODataT4CodeGenerator.ttinclude | 30 +++++++++++++--- .../Views/AdvancedSettings.xaml | 4 ++- .../Templates/ODataT4CodeGeneratorTests.cs | 14 +++++++- 10 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OData.Cli/GenerateCommand.cs b/src/Microsoft.OData.Cli/GenerateCommand.cs index e043b2e7..4174a28e 100644 --- a/src/Microsoft.OData.Cli/GenerateCommand.cs +++ b/src/Microsoft.OData.Cli/GenerateCommand.cs @@ -132,6 +132,14 @@ public GenerateCommand() this.AddOption(multipleFiles); + Option useDateTimeOnly = new Option(new[] { "--date-time-only" }) + { + Name = "date-time-only", + Description = "Use C# DateOnly and TimeOnly for Edm.Date and Edm.TimeOfDay." + }; + + this.AddOption(useDateTimeOnly); + Option excludedOperationImports = new Option(new[] { "--excluded-operation-imports", "-eoi" }) { Name = "excluded-operation-imports", @@ -305,6 +313,7 @@ private TServiceConfig GetServiceConfiguration(GenerateOptions g UseDataServiceCollection = (generateOptions.EnableTracking == null) ? (configUserSettings?.UseDataServiceCollection ?? false) : generateOptions.EnableTracking.Value, MakeTypesInternal = (generateOptions.EnableInternal == null) ? (configUserSettings?.MakeTypesInternal ?? false) : generateOptions.EnableInternal.Value, GenerateMultipleFiles = (generateOptions.MultipleFiles == null) ? (configUserSettings?.GenerateMultipleFiles ?? false) : generateOptions.MultipleFiles.Value, + UseDateTimeOnly = (generateOptions.UseDateTimeOnly == null) ? (configUserSettings?.UseDateTimeOnly ?? false) : generateOptions.UseDateTimeOnly.Value, ExcludedSchemaTypes = excludedSchemaTypes, }; diff --git a/src/Microsoft.OData.Cli/GenerateOptions.cs b/src/Microsoft.OData.Cli/GenerateOptions.cs index cc71c522..137f48e8 100644 --- a/src/Microsoft.OData.Cli/GenerateOptions.cs +++ b/src/Microsoft.OData.Cli/GenerateOptions.cs @@ -66,6 +66,11 @@ public class GenerateOptions /// public bool? MultipleFiles { get; set; } + /// + /// Gets or sets a value that determines whether to use DateOnly and TimeOnly for Edm.Date and Edm.TimeOfDay. + /// + public bool? UseDateTimeOnly { get; set; } + /// /// Gets or sets the comma-separated list of the names of operation imports to exclude from the generated code. Example: ExcludedOperationImport1,ExcludedOperationImport2. /// diff --git a/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs b/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs index bc32ef2c..0204d7f8 100644 --- a/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs +++ b/src/Microsoft.OData.CodeGen/CodeGeneration/V4CodeGenDescriptor.cs @@ -122,6 +122,7 @@ private async Task AddT4FileAsync(string metadata, string outputDirectory, Langu text = Regex.Replace(text, "(public const bool IgnoreUnexpectedElementsAndAttributes = )true;", "$1" + serviceConfiguration.IgnoreUnexpectedElementsAndAttributes.ToString(CultureInfo.InvariantCulture).ToLower(CultureInfo.InvariantCulture) + ";"); text = Regex.Replace(text, "(public const bool MakeTypesInternal = )false;", "$1" + serviceConfiguration.MakeTypesInternal.ToString(CultureInfo.InvariantCulture).ToLower(CultureInfo.InvariantCulture) + ";"); text = Regex.Replace(text, "(public const bool GenerateMultipleFiles = )false;", "$1" + serviceConfiguration.GenerateMultipleFiles.ToString(CultureInfo.InvariantCulture).ToLower(CultureInfo.InvariantCulture) + ";"); + text = Regex.Replace(text, "(public const bool UseDateTimeOnly = )false;", "$1" + serviceConfiguration.UseDateTimeOnly.ToString(CultureInfo.InvariantCulture).ToLower(CultureInfo.InvariantCulture) + ";"); var customHeaders = serviceConfiguration.CustomHttpHeaders ?? ""; text = Regex.Replace(text, "(public const string CustomHttpHeaders = )\"\";", "$1@\"" + customHeaders + "\";"); text = Regex.Replace(text, "(public const string MetadataFilePath = )\"\";", "$1@\"" + metadataFile + "\";"); @@ -163,6 +164,7 @@ private async Task AddGeneratedCodeAsync(string metadata, string outputDirectory t4CodeGenerator.MakeTypesInternal = serviceConfiguration.MakeTypesInternal; t4CodeGenerator.OmitVersioningInfo = serviceConfiguration.OmitVersioningInfo; t4CodeGenerator.GenerateMultipleFiles = serviceConfiguration.GenerateMultipleFiles; + t4CodeGenerator.UseDateTimeOnly = serviceConfiguration.UseDateTimeOnly; t4CodeGenerator.ExcludedOperationImports = serviceConfiguration.ExcludedOperationImports; t4CodeGenerator.ExcludedBoundOperations = serviceConfiguration.ExcludedBoundOperations; t4CodeGenerator.ExcludedSchemaTypes = serviceConfiguration.ExcludedSchemaTypes; diff --git a/src/Microsoft.OData.CodeGen/Models/BaseUserSettings.cs b/src/Microsoft.OData.CodeGen/Models/BaseUserSettings.cs index 22f50731..f8cb8062 100644 --- a/src/Microsoft.OData.CodeGen/Models/BaseUserSettings.cs +++ b/src/Microsoft.OData.CodeGen/Models/BaseUserSettings.cs @@ -38,6 +38,8 @@ public class BaseUserSettings : INotifyPropertyChanged private bool generateMultipleFiles; + private bool useDateTimeOnly; + private string customHttpHeaders; private bool storeCustomHttpHeaders; @@ -204,6 +206,20 @@ public bool GenerateMultipleFiles } } + /// + /// Gets or sets a value that determines whether to use or for Edm.Date and Edm.TimeOfDay. + /// + [DataMember] + public bool UseDateTimeOnly + { + get { return useDateTimeOnly; } + set + { + useDateTimeOnly = value; + OnPropertyChanged(); + } + } + /// /// Gets or sets headers that will get sent along with the request when fetching the metadata document from the service. Format: Header1:HeaderValue, Header2:HeaderValue. /// diff --git a/src/Microsoft.OData.CodeGen/Models/ServiceConfiguration.cs b/src/Microsoft.OData.CodeGen/Models/ServiceConfiguration.cs index c199a1d7..85e55b80 100644 --- a/src/Microsoft.OData.CodeGen/Models/ServiceConfiguration.cs +++ b/src/Microsoft.OData.CodeGen/Models/ServiceConfiguration.cs @@ -22,6 +22,7 @@ public class ServiceConfiguration public bool MakeTypesInternal { get; set; } public bool OpenGeneratedFilesInIDE { get; set; } public bool GenerateMultipleFiles { get; set; } + public bool UseDateTimeOnly { get; set; } public string CustomHttpHeaders { get; set; } public bool IncludeWebProxy { get; set; } public string WebProxyHost { get; set; } diff --git a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs index 8f70adb8..fd7036f5 100644 --- a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs +++ b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.cs @@ -123,10 +123,14 @@ public static class Configuration // This flag indicates whether to omit runtime version and code generation timestamp from the generated files public const bool OmitVersioningInfo = false; - //This files indicates whether to generate the files into multiple files or single. + //This flag indicates whether to generate the files into multiple files or single. //If set to true then multiple files will be generated. Otherwise only a single file is generated. public const bool GenerateMultipleFiles = false; + //This flag indicates whether to use DateOnly or TimeOnly. + //If set to true then DateOnly or TimeOnly is used. Otherwise Date or TimeOfDay is used. + public const bool UseDateTimeOnly = false; + // (Optional) Custom http headers as a multiline string public const string CustomHttpHeaders = ""; @@ -206,6 +210,7 @@ public virtual async Task TransformTextAsync() MultipleFilesManager = new FilesManager(null), OmitVersioningInfo = this.OmitVersioningInfo, GenerateMultipleFiles = this.GenerateMultipleFiles, + UseDateTimeOnly = this.UseDateTimeOnly, ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, @@ -247,6 +252,7 @@ public virtual async Task TransformTextAsync() MultipleFilesManager = new FilesManager(null), OmitVersioningInfo = this.OmitVersioningInfo, GenerateMultipleFiles = this.GenerateMultipleFiles, + UseDateTimeOnly = this.UseDateTimeOnly, ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, @@ -514,6 +520,16 @@ public bool GenerateMultipleFiles get; set; } + +/// +/// true to use DateOnly or TimeOnly, false to use Date or TimeOfDay. +/// +public bool UseDateTimeOnly +{ + get; + set; +} + /// /// Boolean to show if we should include the web proxy /// @@ -764,6 +780,7 @@ private void ApplyParametersFromConfigurationClass() this.MetadataFilePath = Configuration.MetadataFilePath; this.MetadataFileRelativePath = Configuration.MetadataFileRelativePath; this.GenerateMultipleFiles = Configuration.GenerateMultipleFiles; + this.UseDateTimeOnly = Configuration.UseDateTimeOnly; this.SetCustomHttpHeadersFromString(Configuration.CustomHttpHeaders); this.ExcludedOperationImports = Configuration.ExcludedOperationImports.Split(',') .Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToList(); @@ -1281,6 +1298,15 @@ public bool IgnoreUnexpectedElementsAndAttributes /// true to generate multiple files, false generate a single file. /// public bool GenerateMultipleFiles + { + get; + set; + } + + /// + /// true to use DateOnly or TimeOnly, false to use Date or TimeOfDay. + /// + public bool UseDateTimeOnly { get; set; @@ -4298,10 +4324,10 @@ public ODataClientCSharpTemplate(CodeGenerationContext context) internal override string GeometryMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPolygon"; } } internal override string GeometryMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryMultiLineString"; } } internal override string GeometryMultiPointTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPoint"; } } - internal override string DateTypeName { get { return "global::Microsoft.OData.Edm.Date"; } } + internal override string DateTypeName { get { return context.UseDateTimeOnly ? "global::System.DateOnly" : "global::Microsoft.OData.Edm.Date"; } } internal override string DateTimeOffsetTypeName { get { return "global::System.DateTimeOffset"; } } internal override string DurationTypeName { get { return "global::System.TimeSpan"; } } - internal override string TimeOfDayTypeName { get { return "global::Microsoft.OData.Edm.TimeOfDay"; } } + internal override string TimeOfDayTypeName { get { return context.UseDateTimeOnly ? "global::System.TimeOnly" : "global::Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "global::System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "global::System.Enum"; } } internal override string DictionaryInterfaceName { get { return "global::System.Collections.Generic.IDictionary<{0}, {1}>"; } } @@ -6441,10 +6467,10 @@ public ODataClientVBTemplate(CodeGenerationContext context) internal override string GeometryMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPolygon"; } } internal override string GeometryMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiLineString"; } } internal override string GeometryMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPoint"; } } - internal override string DateTypeName { get { return "Global.Microsoft.OData.Edm.Date"; } } + internal override string DateTypeName { get { return context.UseDateTimeOnly ? "global::System.DateOnly" : "global::Microsoft.OData.Edm.Date"; } } internal override string DateTimeOffsetTypeName { get { return "Global.System.DateTimeOffset"; } } internal override string DurationTypeName { get { return "Global.System.TimeSpan"; } } - internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } + internal override string TimeOfDayTypeName { get { return context.UseDateTimeOnly ? "global::System.TimeOnly" : "global::Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "Global.System.Enum"; } } internal override string DictionaryInterfaceName { get { return "Global.System.Collections.Generic.IDictionary(Of {0}, {1})"; } } diff --git a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.tt b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.tt index 70c114cb..05b38ede 100644 --- a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.tt +++ b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.tt @@ -39,10 +39,14 @@ public static class Configuration // This flag indicates whether to omit runtime version and code generation timestamp from the generated files public const bool OmitVersioningInfo = false; - //This files indicates whether to generate the files into multiple files or single. + //This flag indicates whether to generate the files into multiple files or single. //If set to true then multiple files will be generated. Otherwise only a single file is generated. public const bool GenerateMultipleFiles = false; + //This flag indicates whether to use DateOnly or TimeOnly. + //If set to true then DateOnly or TimeOnly is used. Otherwise Date or TimeOfDay is used. + public const bool UseDateTimeOnly = false; + // (Optional) Custom http headers as a multiline string public const string CustomHttpHeaders = ""; diff --git a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude index f50826fe..ff5b1019 100644 --- a/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude +++ b/src/Microsoft.OData.CodeGen/Templates/ODataT4CodeGenerator.ttinclude @@ -63,6 +63,7 @@ public virtual async Task TransformTextAsync() MultipleFilesManager = new FilesManager(null), OmitVersioningInfo = this.OmitVersioningInfo, GenerateMultipleFiles = this.GenerateMultipleFiles, + UseDateTimeOnly = this.UseDateTimeOnly, ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, @@ -104,6 +105,7 @@ public virtual async Task TransformTextAsync() MultipleFilesManager = new FilesManager(null), OmitVersioningInfo = this.OmitVersioningInfo, GenerateMultipleFiles = this.GenerateMultipleFiles, + UseDateTimeOnly = this.UseDateTimeOnly, ExcludedOperationImports = this.ExcludedOperationImports, ExcludedBoundOperations = this.ExcludedBoundOperations, ExcludedSchemaTypes = this.ExcludedSchemaTypes, @@ -371,6 +373,16 @@ public bool GenerateMultipleFiles get; set; } + +/// +/// true to use DateOnly or TimeOnly, false to use Date or TimeOfDay. +/// +public bool UseDateTimeOnly +{ + get; + set; +} + /// /// Boolean to show if we should include the web proxy /// @@ -621,6 +633,7 @@ private void ApplyParametersFromConfigurationClass() this.MetadataFilePath = Configuration.MetadataFilePath; this.MetadataFileRelativePath = Configuration.MetadataFileRelativePath; this.GenerateMultipleFiles = Configuration.GenerateMultipleFiles; + this.UseDateTimeOnly = Configuration.UseDateTimeOnly; this.SetCustomHttpHeadersFromString(Configuration.CustomHttpHeaders); this.ExcludedOperationImports = Configuration.ExcludedOperationImports.Split(',') .Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToList(); @@ -1138,6 +1151,15 @@ public class CodeGenerationContext /// true to generate multiple files, false generate a single file. /// public bool GenerateMultipleFiles + { + get; + set; + } + + /// + /// true to use DateOnly or TimeOnly, false to use Date or TimeOfDay. + /// + public bool UseDateTimeOnly { get; set; @@ -4155,10 +4177,10 @@ public sealed class ODataClientCSharpTemplate : ODataClientTemplate internal override string GeometryMultiPolygonTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPolygon"; } } internal override string GeometryMultiLineStringTypeName { get { return "global::Microsoft.Spatial.GeometryMultiLineString"; } } internal override string GeometryMultiPointTypeName { get { return "global::Microsoft.Spatial.GeometryMultiPoint"; } } - internal override string DateTypeName { get { return "global::Microsoft.OData.Edm.Date"; } } + internal override string DateTypeName { get { return context.UseDateTimeOnly ? "global::System.DateOnly" : "global::Microsoft.OData.Edm.Date"; } } internal override string DateTimeOffsetTypeName { get { return "global::System.DateTimeOffset"; } } internal override string DurationTypeName { get { return "global::System.TimeSpan"; } } - internal override string TimeOfDayTypeName { get { return "global::Microsoft.OData.Edm.TimeOfDay"; } } + internal override string TimeOfDayTypeName { get { return context.UseDateTimeOnly ? "global::System.TimeOnly" : "global::Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "global::System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "global::System.Enum"; } } internal override string DictionaryInterfaceName { get { return "global::System.Collections.Generic.IDictionary<{0}, {1}>"; } } @@ -5355,10 +5377,10 @@ public sealed class ODataClientVBTemplate : ODataClientTemplate internal override string GeometryMultiPolygonTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPolygon"; } } internal override string GeometryMultiLineStringTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiLineString"; } } internal override string GeometryMultiPointTypeName { get { return "Global.Microsoft.Spatial.GeometryMultiPoint"; } } - internal override string DateTypeName { get { return "Global.Microsoft.OData.Edm.Date"; } } + internal override string DateTypeName { get { return context.UseDateTimeOnly ? "global::System.DateOnly" : "global::Microsoft.OData.Edm.Date"; } } internal override string DateTimeOffsetTypeName { get { return "Global.System.DateTimeOffset"; } } internal override string DurationTypeName { get { return "Global.System.TimeSpan"; } } - internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } + internal override string TimeOfDayTypeName { get { return context.UseDateTimeOnly ? "global::System.TimeOnly" : "global::Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "Global.System.Enum"; } } internal override string DictionaryInterfaceName { get { return "Global.System.Collections.Generic.IDictionary(Of {0}, {1})"; } } diff --git a/src/ODataConnectedService.Shared/Views/AdvancedSettings.xaml b/src/ODataConnectedService.Shared/Views/AdvancedSettings.xaml index 6bb6d8d1..915387d4 100644 --- a/src/ODataConnectedService.Shared/Views/AdvancedSettings.xaml +++ b/src/ODataConnectedService.Shared/Views/AdvancedSettings.xaml @@ -55,11 +55,13 @@ Omit runtime version and code generation timestamp from the generated files + HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0, 15" FontWeight="Medium"/> + diff --git a/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs b/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs index fa7a745a..eb5a07bb 100644 --- a/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs +++ b/test/ODataConnectedService.Tests/Templates/ODataT4CodeGeneratorTests.cs @@ -339,6 +339,17 @@ public void MergedFunctionalTest() ODataT4CodeGeneratorTestDescriptors.MergedFunctionalTest.Verify(code, false/*isCSharp*/, true/*useDSC*/); } + [TestMethod] + public void MergedFunctionalTest_UseDateTimeOnly() + { + string code = CodeGenWithT4Template(ODataT4CodeGeneratorTestDescriptors.MergedFunctionalTest.Metadata, null, true, false, useDateTimeOnly: true); + + code.Should().Contain("global::System.DateOnly nonNullableDateProp"); + code.Should().Contain("global::System.TimeOnly nonNullableTimeOfDayProp"); + code.Should().Contain("public virtual global::System.DateOnly nonNullableDateProp"); + code.Should().Contain("public virtual global::System.TimeOnly NonNullableTimeOfDayProp"); + } + [TestMethod] public void CodeGenWithKeywordsAsNames() { @@ -666,7 +677,7 @@ private static string CodeGenWithT4Template(string edmx, string namespacePrefix, bool ignoreUnexpectedElementsAndAttributes = false, Func, XmlReader> getReferencedModelReaderFunc = null, bool appendDSCSuffix = false, string MetadataFilePath = null, bool generateMultipleFiles = false, bool omitVersioningInfo = false, - string metadataDocumentUri = null, IEnumerable excludedSchemaTypes = default(List)) + string metadataDocumentUri = null, IEnumerable excludedSchemaTypes = default(List), bool useDateTimeOnly = false) { if (useDataServiceCollection && appendDSCSuffix) // hack now @@ -691,6 +702,7 @@ private static string CodeGenWithT4Template(string edmx, string namespacePrefix, EnableNamingAlias = enableNamingAlias, IgnoreUnexpectedElementsAndAttributes = ignoreUnexpectedElementsAndAttributes, GenerateMultipleFiles = generateMultipleFiles, + UseDateTimeOnly = useDateTimeOnly, OmitVersioningInfo = omitVersioningInfo, ExcludedSchemaTypes = excludedSchemaTypes };