From 3a4d4ddb8427d442ca11d6eb24411c6e01016922 Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Fri, 6 Feb 2026 14:16:08 -0800 Subject: [PATCH 1/2] Fix inline object modification --- .../Converters/DatabaseObjectConverter.cs | 133 ++++++++++++++++-- .../SerializationDeserializationTests.cs | 16 ++- 2 files changed, 131 insertions(+), 18 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index 6d625c7f9d..4146f7d30c 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -77,7 +77,8 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri // Only escape columns for properties whose type(derived type) is SourceDefinition. if (IsSourceDefinitionOrDerivedClassProperty(prop) && propVal is SourceDefinition sourceDef) { - EscapeDollaredColumns(sourceDef); + // Check if we need to escape any column names + propVal = GetSourceDefinitionWithEscapedColumns(sourceDef); } JsonSerializer.Serialize(writer, propVal, options); @@ -93,25 +94,131 @@ private static bool IsSourceDefinitionOrDerivedClassProperty(PropertyInfo prop) } /// - /// Escapes column keys that start with '$' to '_$' for serialization. + /// Returns the SourceDefinition as-is if no columns start with '$', + /// otherwise returns a copy with escaped column names. /// - private static void EscapeDollaredColumns(SourceDefinition sourceDef) + private static SourceDefinition GetSourceDefinitionWithEscapedColumns(SourceDefinition sourceDef) { - if (sourceDef.Columns is null || sourceDef.Columns.Count == 0) + if (sourceDef == null) { - return; + throw new ArgumentNullException(nameof(sourceDef)); } - List keysToEscape = sourceDef.Columns.Keys - .Where(k => k.StartsWith(DOLLAR_CHAR, StringComparison.Ordinal)) - .ToList(); + // If no columns or no columns starting with '$', return original + if (sourceDef.Columns is null || sourceDef.Columns.Count == 0 || + !sourceDef.Columns.Keys.Any(k => k.StartsWith(DOLLAR_CHAR, StringComparison.Ordinal))) + { + return sourceDef; + } - foreach (string key in keysToEscape) + // Create escaped columns dictionary + Dictionary escapedColumns = CreateEscapedColumnsDictionary(sourceDef.Columns); + + // Create new instance based on the actual type + return sourceDef switch { - ColumnDefinition col = sourceDef.Columns[key]; - sourceDef.Columns.Remove(key); - string newKey = ESCAPED_DOLLARCHAR + key[1..]; - sourceDef.Columns[newKey] = col; + StoredProcedureDefinition spDef => CreateStoredProcedureDefinition(spDef, escapedColumns), + _ => CreateSourceDefinition(sourceDef, escapedColumns) + }; + } + + /// + /// Creates a dictionary with escaped column keys for serialization. + /// + private static Dictionary CreateEscapedColumnsDictionary(IDictionary originalColumns) + { + Dictionary escapedColumns = new(); + foreach (KeyValuePair kvp in originalColumns) + { + if (kvp.Key.StartsWith(DOLLAR_CHAR, StringComparison.Ordinal)) + { + string escapedKey = ESCAPED_DOLLARCHAR + kvp.Key[1..]; + escapedColumns[escapedKey] = kvp.Value; + } + else + { + escapedColumns[kvp.Key] = kvp.Value; + } + } + + return escapedColumns; + } + + /// + /// Creates a new StoredProcedureDefinition with escaped columns and copied properties. + /// + private static StoredProcedureDefinition CreateStoredProcedureDefinition(StoredProcedureDefinition original, Dictionary escapedColumns) + { + StoredProcedureDefinition newSpDef = new() + { + IsInsertDMLTriggerEnabled = original.IsInsertDMLTriggerEnabled, + IsUpdateDMLTriggerEnabled = original.IsUpdateDMLTriggerEnabled, + PrimaryKey = original.PrimaryKey + }; + + // Add escaped columns + AddColumnsToDictionary(newSpDef.Columns, escapedColumns); + + // Copy parameters + AddParametersToDictionary(newSpDef.Parameters, original.Parameters); + + // Copy relationship map if it exists + CopyRelationshipMap(newSpDef.SourceEntityRelationshipMap, original.SourceEntityRelationshipMap); + + return newSpDef; + } + + /// + /// Creates a new SourceDefinition with escaped columns and copied properties. + /// + private static SourceDefinition CreateSourceDefinition(SourceDefinition original, Dictionary escapedColumns) + { + SourceDefinition newSourceDef = new() + { + IsInsertDMLTriggerEnabled = original.IsInsertDMLTriggerEnabled, + IsUpdateDMLTriggerEnabled = original.IsUpdateDMLTriggerEnabled, + PrimaryKey = original.PrimaryKey + }; + + // Add escaped columns + AddColumnsToDictionary(newSourceDef.Columns, escapedColumns); + + // Copy relationship map if it exists + CopyRelationshipMap(newSourceDef.SourceEntityRelationshipMap, original.SourceEntityRelationshipMap); + + return newSourceDef; + } + + /// + /// Helper method to add columns to a dictionary. + /// + private static void AddColumnsToDictionary(IDictionary target, Dictionary source) + { + foreach (KeyValuePair kvp in source) + { + target.Add(kvp.Key, kvp.Value); + } + } + + /// + /// Helper method to add parameters to a dictionary. + /// + private static void AddParametersToDictionary(IDictionary target, IDictionary source) + { + foreach (KeyValuePair kvp in source) + { + target.Add(kvp.Key, kvp.Value); + } + } + + /// + /// Helper method to copy relationship map between SourceDefinition instances. + /// + private static void CopyRelationshipMap(IDictionary target, IDictionary source) + { + foreach (KeyValuePair kvp in source) + { + target.Add(kvp.Key, kvp.Value); } } diff --git a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs index 12ae3ee993..440a5e1c13 100644 --- a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs @@ -308,7 +308,7 @@ public void TestDictionaryDatabaseObjectSerializationDeserialization_WithDollarC Assert.AreEqual(deserializedDatabaseTable.SourceType, _databaseTable.SourceType); Assert.AreEqual(deserializedDatabaseTable.FullName, _databaseTable.FullName); - deserializedDatabaseTable.Equals(_databaseTable); + Assert.IsTrue(deserializedDatabaseTable.Equals(_databaseTable)); VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseTable.SourceDefinition, _databaseTable.SourceDefinition, "$FirstName"); VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseTable.TableDefinition, _databaseTable.TableDefinition, "$FirstName"); } @@ -343,7 +343,7 @@ public void TestDatabaseViewSerializationDeserialization_WithDollarColumn() DatabaseView deserializedDatabaseView = (DatabaseView)deserializedDict["person"]; Assert.AreEqual(deserializedDatabaseView.SourceType, _databaseView.SourceType); - deserializedDatabaseView.Equals(_databaseView); + Assert.IsTrue(deserializedDatabaseView.Equals(_databaseView)); VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseView.SourceDefinition, _databaseView.SourceDefinition, "$FirstName"); VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseView.ViewDefinition, _databaseView.ViewDefinition, "$FirstName"); } @@ -377,7 +377,7 @@ public void TestDatabaseStoredProcedureSerializationDeserialization_WithDollarCo DatabaseStoredProcedure deserializedDatabaseSP = (DatabaseStoredProcedure)deserializedDict["person"]; Assert.AreEqual(deserializedDatabaseSP.SourceType, _databaseStoredProcedure.SourceType); - deserializedDatabaseSP.Equals(_databaseStoredProcedure); + Assert.IsTrue(deserializedDatabaseSP.Equals(_databaseStoredProcedure)); VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseSP.SourceDefinition, _databaseStoredProcedure.SourceDefinition, "$FirstName", true); VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseSP.StoredProcedureDefinition, _databaseStoredProcedure.StoredProcedureDefinition, "$FirstName", true); } @@ -529,7 +529,10 @@ private static void VerifyColumnDefinitionSerializationDeserialization(ColumnDef Assert.AreEqual(fields, 8); // test values - expectedColumnDefinition.Equals(deserializedColumnDefinition); + Assert.AreEqual(expectedColumnDefinition.DbType, deserializedColumnDefinition.DbType); + Assert.AreEqual(expectedColumnDefinition.SqlDbType, deserializedColumnDefinition.SqlDbType); + Assert.AreEqual(expectedColumnDefinition.IsAutoGenerated, deserializedColumnDefinition.IsAutoGenerated); + Assert.AreEqual(expectedColumnDefinition.DefaultValue.ToString(), deserializedColumnDefinition.DefaultValue.ToString()); } private static void VerifyParameterDefinitionSerializationDeserialization(ParameterDefinition expectedParameterDefinition, ParameterDefinition deserializedParameterDefinition) @@ -538,7 +541,10 @@ private static void VerifyParameterDefinitionSerializationDeserialization(Parame int fields = typeof(ParameterDefinition).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length; Assert.AreEqual(fields, 9); // test values - expectedParameterDefinition.Equals(deserializedParameterDefinition); + Assert.AreEqual(expectedParameterDefinition.DbType, deserializedParameterDefinition.DbType); + Assert.AreEqual(expectedParameterDefinition.SqlDbType, deserializedParameterDefinition.SqlDbType); + Assert.AreEqual(expectedParameterDefinition.SystemType, deserializedParameterDefinition.SystemType); + Assert.AreEqual(expectedParameterDefinition.ConfigDefaultValue.ToString(), deserializedParameterDefinition.ConfigDefaultValue.ToString()); } private static void VerifyRelationShipPair(RelationShipPair expectedRelationShipPair, RelationShipPair deserializedRelationShipPair) From a262c2d77d580a28aac3c8cbfd1709e6ed1d571b Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Fri, 6 Feb 2026 14:49:41 -0800 Subject: [PATCH 2/2] Fix inline object modification --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index 4146f7d30c..7262043d14 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -99,11 +99,6 @@ private static bool IsSourceDefinitionOrDerivedClassProperty(PropertyInfo prop) /// private static SourceDefinition GetSourceDefinitionWithEscapedColumns(SourceDefinition sourceDef) { - if (sourceDef == null) - { - throw new ArgumentNullException(nameof(sourceDef)); - } - // If no columns or no columns starting with '$', return original if (sourceDef.Columns is null || sourceDef.Columns.Count == 0 || !sourceDef.Columns.Keys.Any(k => k.StartsWith(DOLLAR_CHAR, StringComparison.Ordinal)))