-
Notifications
You must be signed in to change notification settings - Fork 51
Fix default value initialization for nullable properties #345
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
Changes from all commits
dc113b9
1209aee
3b170ce
37cf0cf
a67e18d
791fcb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| // ------------------------------------------------------------------------------ | ||
| // <auto-generated> | ||
| // This code was generated by a tool. | ||
| // Runtime Version: 16.0.0.0 | ||
| // Runtime Version: 17.0.0.0 | ||
| // | ||
| // Changes to this file may cause incorrect behavior and will be lost if | ||
| // the code is regenerated. | ||
|
|
@@ -33,7 +33,7 @@ namespace Microsoft.OData.CodeGen.Templates | |
| /// <summary> | ||
| /// Class to produce the template output | ||
| /// </summary> | ||
| [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] | ||
| [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] | ||
| public partial class ODataT4CodeGenerator : ODataT4CodeGeneratorBase | ||
| { | ||
| /// <summary> | ||
|
|
@@ -1745,6 +1745,15 @@ public override int GetHashCode() | |
| protected abstract void WriteRequiredAttribute(string errorMessage); | ||
| #endregion Language specific write methods. | ||
|
|
||
| /// <summary> | ||
| /// Given a nullable type name, e.g. Nullable<T>, returns | ||
| /// the underlying type name T. If the type name is not | ||
| /// a Nullable<T>, then returns T. | ||
| /// </summary> | ||
| /// <param name="typeName">The type name.</param> | ||
| /// <returns>The underlying type name.</returns> | ||
| internal abstract string StripNullableFromTypeName(string typeName); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so the type name here will be in form of a string? Exa
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I do not understand but why can't we check if a type is nullable without the conversions to string?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because by the time we get to use this method, the input type name is already in string form. I do agree that it would be more robust to use actual reflection type metadata to detect nullability, but we don't have that information in the method where this method is used. To use types would require refactoring a lot more methods and a lot more work than what I "budgeted" to fix this bug. |
||
|
|
||
| internal HashSet<EdmPrimitiveTypeKind> ClrReferenceTypes { get { | ||
| if (clrReferenceTypes == null) | ||
| { | ||
|
|
@@ -3723,118 +3732,120 @@ internal static string GetPropertyInitializationValue(IEdmProperty property, boo | |
| if (edmCollectionTypeReference == null) | ||
| { | ||
| IEdmStructuralProperty structuredProperty = property as IEdmStructuralProperty; | ||
| if (structuredProperty != null) | ||
| if (structuredProperty == null) | ||
| { | ||
| if (!string.IsNullOrEmpty(structuredProperty.DefaultValueString)) | ||
| { | ||
| string valueClrType = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); | ||
| string defaultValue = structuredProperty.DefaultValueString; | ||
| bool isCSharpTemplate = clientTemplate is ODataClientCSharpTemplate; | ||
| if (edmTypeReference.Definition.TypeKind == EdmTypeKind.Enum) | ||
| { | ||
| var enumValues = defaultValue.Split(','); | ||
| string fullenumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); | ||
| string enumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context, false, false); | ||
| List<string> customizedEnumValues = new List<string>(); | ||
| foreach(var enumValue in enumValues) | ||
| { | ||
| string currentEnumValue = enumValue.Trim(); | ||
| int indexFirst = currentEnumValue.IndexOf('\'') + 1; | ||
| int indexLast = currentEnumValue.LastIndexOf('\''); | ||
| if (indexFirst > 0 && indexLast > indexFirst) | ||
| { | ||
| currentEnumValue = currentEnumValue.Substring(indexFirst, indexLast - indexFirst); | ||
| } | ||
| // only structured property has default value | ||
| return null; | ||
| } | ||
|
|
||
| var customizedEnumValue = context.EnableNamingAlias ? Customization.CustomizeNaming(currentEnumValue) : currentEnumValue; | ||
| if (isCSharpTemplate) | ||
| { | ||
| currentEnumValue = "(" + fullenumTypeName + ")" + clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + customizedEnumValue + "\")"; | ||
| } | ||
| else | ||
| { | ||
| currentEnumValue = clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + currentEnumValue + "\")"; | ||
| } | ||
| customizedEnumValues.Add(currentEnumValue); | ||
| } | ||
| if (isCSharpTemplate) | ||
| { | ||
| return string.Join(" | ", customizedEnumValues); | ||
| } | ||
| else | ||
| { | ||
| return string.Join(" Or ", customizedEnumValues); | ||
| } | ||
| } | ||
| if (string.IsNullOrEmpty(structuredProperty.DefaultValueString)) | ||
| { | ||
| // doesn't have a default value | ||
| return null; | ||
| } | ||
|
|
||
| if (valueClrType.Equals(clientTemplate.StringTypeName)) | ||
| { | ||
| defaultValue = "\"" + defaultValue + "\""; | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.BooleanTypeName)) | ||
| { | ||
| // EDMX specifies boolean defaults with capital letter, C# needs this string to be lower case. | ||
| if (isCSharpTemplate) | ||
| defaultValue = defaultValue.ToLower(); | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.BinaryTypeName)) | ||
| { | ||
| defaultValue = "System.Text.Encoding.UTF8.GetBytes(\"" + defaultValue + "\")"; | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.SingleTypeName)) | ||
| { | ||
| if (isCSharpTemplate) | ||
| { | ||
| defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "f"; | ||
| } | ||
| else | ||
| { | ||
| defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "F"; | ||
| } | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.DecimalTypeName)) | ||
| { | ||
| if (isCSharpTemplate) | ||
| { | ||
| // decimal in C# must be initialized with 'm' at the end, like Decimal dec = 3.00m | ||
| defaultValue = defaultValue.EndsWith("m", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "m"; | ||
| } | ||
| else | ||
| { | ||
| // decimal in VB must be initialized with 'D' at the end, like Decimal dec = 3.00D | ||
| defaultValue = defaultValue.ToLower(CultureInfo.InvariantCulture).Replace("m", "D"); | ||
| defaultValue = defaultValue.EndsWith("D", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "D"; | ||
| } | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.GuidTypeName) | ||
| | valueClrType.Equals(clientTemplate.DateTimeOffsetTypeName) | ||
| | valueClrType.Equals(clientTemplate.DateTypeName) | ||
| | valueClrType.Equals(clientTemplate.TimeOfDayTypeName)) | ||
| string valueClrType = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); | ||
|
habbes marked this conversation as resolved.
|
||
|
|
||
| // If it's a Nullable<T> type, then we need to use the underlying T to generate initialization code correctly | ||
| // The property initializers below do not expect that the specified default to be null. Since the default | ||
| // value of a Nullable<T> is null, it's not worth the effort to handle that edge case, Instead we | ||
| // assume the user will not specify "null" as the default value of a nullable property since it's redundant. | ||
| valueClrType = clientTemplate.StripNullableFromTypeName(valueClrType); | ||
| string defaultValue = structuredProperty.DefaultValueString; | ||
| bool isCSharpTemplate = clientTemplate is ODataClientCSharpTemplate; | ||
| if (edmTypeReference.Definition.TypeKind == EdmTypeKind.Enum) | ||
| { | ||
| var enumValues = defaultValue.Split(','); | ||
| string fullEnumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context); | ||
| string enumTypeName = GetClrTypeName(edmTypeReference, useDataServiceCollection, clientTemplate, context, false, false); | ||
| List<string> customizedEnumValues = new List<string>(); | ||
| foreach (var enumValue in enumValues) | ||
| { | ||
| string currentEnumValue = enumValue.Trim(); | ||
| int indexFirst = currentEnumValue.IndexOf('\'') + 1; | ||
| int indexLast = currentEnumValue.LastIndexOf('\''); | ||
| if (indexFirst > 0 && indexLast > indexFirst) | ||
| { | ||
| defaultValue = valueClrType + ".Parse(\"" + defaultValue + "\")"; | ||
| currentEnumValue = currentEnumValue.Substring(indexFirst, indexLast - indexFirst); | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.DurationTypeName)) | ||
|
|
||
| var customizedEnumValue = context.EnableNamingAlias ? Customization.CustomizeNaming(currentEnumValue) : currentEnumValue; | ||
| if (isCSharpTemplate) | ||
| { | ||
| defaultValue = clientTemplate.XmlConvertClassName + ".ToTimeSpan(\"" + defaultValue + "\")"; | ||
| currentEnumValue = "(" + fullEnumTypeName + ")" + clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + customizedEnumValue + "\")"; | ||
| } | ||
| else if (valueClrType.Contains("Microsoft.Spatial")) | ||
| else | ||
| { | ||
| defaultValue = string.Format(CultureInfo.InvariantCulture, clientTemplate.GeoTypeInitializePattern, valueClrType, defaultValue); | ||
| currentEnumValue = clientTemplate.EnumTypeName + ".Parse(" + clientTemplate.SystemTypeTypeName + ".GetType(\"" + enumTypeName + "\"), \"" + currentEnumValue + "\")"; | ||
| } | ||
| customizedEnumValues.Add(currentEnumValue); | ||
| } | ||
| if (isCSharpTemplate) | ||
| { | ||
| return string.Join(" | ", customizedEnumValues); | ||
| } | ||
| else | ||
| { | ||
| return string.Join(" Or ", customizedEnumValues); | ||
| } | ||
| } | ||
|
|
||
| return defaultValue; | ||
| if (valueClrType.Equals(clientTemplate.StringTypeName)) | ||
| { | ||
| defaultValue = "\"" + defaultValue + "\""; | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.BooleanTypeName)) | ||
| { | ||
| // EDMX specifies boolean defaults with capital letter, C# needs this string to be lower case. | ||
| if (isCSharpTemplate) | ||
| defaultValue = defaultValue.ToLower(); | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.BinaryTypeName)) | ||
| { | ||
| defaultValue = "System.Text.Encoding.UTF8.GetBytes(\"" + defaultValue + "\")"; | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.SingleTypeName)) | ||
| { | ||
| if (isCSharpTemplate) | ||
| { | ||
| defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "f"; | ||
| } | ||
| else | ||
| { | ||
| // doesn't have a default value | ||
| return null; | ||
| defaultValue = defaultValue.EndsWith("f", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "F"; | ||
| } | ||
| } | ||
| else | ||
| else if (valueClrType.Equals(clientTemplate.DecimalTypeName)) | ||
| { | ||
| // only structured property has default value | ||
| return null; | ||
| if (isCSharpTemplate) | ||
| { | ||
| // decimal in C# must be initialized with 'm' at the end, like Decimal dec = 3.00m | ||
| defaultValue = defaultValue.EndsWith("m", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "m"; | ||
| } | ||
| else | ||
| { | ||
| // decimal in VB must be initialized with 'D' at the end, like Decimal dec = 3.00D | ||
| defaultValue = defaultValue.ToLower(CultureInfo.InvariantCulture).Replace("m", "D"); | ||
| defaultValue = defaultValue.EndsWith("D", StringComparison.OrdinalIgnoreCase) ? defaultValue : defaultValue + "D"; | ||
| } | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.GuidTypeName) | ||
| | valueClrType.Equals(clientTemplate.DateTimeOffsetTypeName) | ||
| | valueClrType.Equals(clientTemplate.DateTypeName) | ||
| | valueClrType.Equals(clientTemplate.TimeOfDayTypeName)) | ||
| { | ||
| defaultValue = valueClrType + ".Parse(\"" + defaultValue + "\")"; | ||
| } | ||
| else if (valueClrType.Equals(clientTemplate.DurationTypeName)) | ||
| { | ||
| defaultValue = clientTemplate.XmlConvertClassName + ".ToTimeSpan(\"" + defaultValue + "\")"; | ||
| } | ||
| else if (valueClrType.Contains("Microsoft.Spatial")) | ||
| { | ||
| defaultValue = string.Format(CultureInfo.InvariantCulture, clientTemplate.GeoTypeInitializePattern, valueClrType, defaultValue); | ||
| } | ||
|
|
||
| return defaultValue; | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -4121,6 +4132,18 @@ internal override HashSet<string> LanguageKeywords { get { | |
| } } | ||
| private HashSet<string> CSharpKeywords; | ||
|
|
||
| internal override string StripNullableFromTypeName(string typeName) | ||
| { | ||
| const string nullablePrefix = "Nullable<"; | ||
| int indexOfNullablePrefix = typeName.IndexOf(nullablePrefix); | ||
| if (indexOfNullablePrefix == -1) | ||
| { | ||
| return typeName; | ||
| } | ||
|
|
||
| return typeName.Substring(indexOfNullablePrefix + nullablePrefix.Length).TrimEnd('>'); | ||
| } | ||
|
|
||
| internal override void WriteFileHeader() | ||
| { | ||
|
|
||
|
|
@@ -6243,6 +6266,18 @@ internal override HashSet<string> LanguageKeywords { get { | |
| } } | ||
| private HashSet<string> VBKeywords; | ||
|
|
||
| internal override string StripNullableFromTypeName(string typeName) | ||
| { | ||
| const string nullablePrefix = "Nullable(Of "; | ||
| int indexOfNullablePrefix = typeName.IndexOf(nullablePrefix); | ||
| if (indexOfNullablePrefix == -1) | ||
| { | ||
| return typeName; | ||
| } | ||
|
|
||
| return typeName.Substring(indexOfNullablePrefix + nullablePrefix.Length).TrimEnd(')'); | ||
| } | ||
|
|
||
| internal override void WriteFileHeader() | ||
| { | ||
|
|
||
|
|
@@ -8544,7 +8579,7 @@ internal VSManager(StringBuilder template) | |
| /// <summary> | ||
| /// Base class for this transformation | ||
| /// </summary> | ||
| [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "16.0.0.0")] | ||
| [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] | ||
| public class ODataT4CodeGeneratorBase | ||
| { | ||
| #region Fields | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.