From 4034ebb784e5a86947b63f47364312459f480510 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:22:58 +0000 Subject: [PATCH 01/21] Initial plan From 5df419dc55a6500f31481343782a508f24af2058 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:35:19 +0000 Subject: [PATCH 02/21] Sanitize lifted constant variable names in precompiled query generation + test Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Internal/PrecompiledQueryCodeGenerator.cs | 48 +++++++++++++++++-- ...AdHocPrecompiledQueryRelationalTestBase.cs | 29 +++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs b/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs index fa488ff6f90..e04490d4daa 100644 --- a/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs +++ b/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs @@ -975,12 +975,52 @@ private void GenerateQueryExecutor( var queryExecutorAfterLiftingExpression = _liftableConstantProcessor.LiftConstants(queryExecutor, materializerLiftableConstantContext, variableNames); - foreach (var liftedConstant in _liftableConstantProcessor.LiftedConstants) + // Lifted constant variable names are partly derived from model metadata (e.g. property names), which may not be valid C# + // identifiers (shadow properties can be given arbitrary names). Sanitize any such names and replace the corresponding + // parameters across all the lifted constant expressions and the query executor. + var liftedConstants = _liftableConstantProcessor.LiftedConstants; + var sanitizedNames = new string[liftedConstants.Count]; + var liftedConstantExpressions = new Expression[liftedConstants.Count]; + List? originalParameters = null; + List? sanitizedParameters = null; + for (var i = 0; i < liftedConstants.Count; i++) + { + var (parameter, expression) = liftedConstants[i]; + liftedConstantExpressions[i] = expression; + + var sanitizedName = SanitizeIdentifierName(parameter.Name ?? "unknown"); + if (sanitizedName != parameter.Name) + { + var baseName = sanitizedName; + for (var j = 0; variableNames.Contains(sanitizedName); j++) + { + sanitizedName = baseName + j; + } + + (originalParameters ??= []).Add(parameter); + (sanitizedParameters ??= []).Add(Expression.Parameter(parameter.Type, sanitizedName)); + } + + variableNames.Add(sanitizedName); + sanitizedNames[i] = sanitizedName; + } + + if (originalParameters is not null) + { + var replacer = new ReplacingExpressionVisitor(originalParameters, sanitizedParameters!); + queryExecutorAfterLiftingExpression = replacer.Visit(queryExecutorAfterLiftingExpression); + + for (var i = 0; i < liftedConstantExpressions.Length; i++) + { + liftedConstantExpressions[i] = replacer.Visit(liftedConstantExpressions[i]); + } + } + + for (var i = 0; i < liftedConstants.Count; i++) { var variableValueSyntax = _linqToCSharpTranslator.TranslateExpression( - liftedConstant.Expression, _constantReplacements, _memberAccessReplacements, namespaces, unsafeAccessors); - // code.AppendLine($"{liftedConstant.Parameter.Type.Name} {liftedConstant.Parameter.Name} = {variableValueSyntax.NormalizeWhitespace().ToFullString()};"); - code.AppendLine($"var {liftedConstant.Parameter.Name} = {variableValueSyntax.NormalizeWhitespace().ToFullString()};"); + liftedConstantExpressions[i], _constantReplacements, _memberAccessReplacements, namespaces, unsafeAccessors); + code.AppendLine($"var {sanitizedNames[i]} = {variableValueSyntax.NormalizeWhitespace().ToFullString()};"); } var queryExecutorSyntaxTree = diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs index 865ab6f8b86..79fba24c6cf 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs @@ -387,6 +387,35 @@ public class InvalidNameNestedEntity public string Name2 { get; set; } = ""; } + [Fact] + public virtual async Task Invalid_identifier_shadow_property_name() + { + var contextFactory = await InitializeNonSharedTest(); + var options = contextFactory.GetOptions(); + + await Test( + """ +await using var context = new AdHocPrecompiledQueryRelationalTestBase.InvalidShadowNameContext(dbContextOptions); +var entities = await context.Entities.ToListAsync(); +""", + typeof(InvalidShadowNameContext), + options); + } + + public class InvalidShadowNameContext(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.Entity() + .Property("NOT VALID !!!1").HasConversion(x => 0, x => ""); + } + + public class InvalidShadowNameEntity + { + public Guid Id { get; set; } + } + #endregion protected TestSqlLoggerFactory TestSqlLoggerFactory From 975fab3f0632abf5f00a1daf1c2d20038f8003ca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:50:22 +0000 Subject: [PATCH 03/21] Add warning for shadow property names that are not valid C# identifiers Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/Diagnostics/CoreEventId.cs | 15 +++++ .../Diagnostics/CoreLoggerExtensions.cs | 34 ++++++++++ src/EFCore/Diagnostics/LoggingDefinitions.cs | 9 +++ src/EFCore/EFCore.baseline.json | 6 ++ src/EFCore/Infrastructure/ModelValidator.cs | 65 +++++++++++++++++++ src/EFCore/Properties/CoreStrings.Designer.cs | 25 +++++++ src/EFCore/Properties/CoreStrings.resx | 4 ++ ...AdHocPrecompiledQueryRelationalTestBase.cs | 3 +- .../Infrastructure/ModelValidatorTest.cs | 34 ++++++++++ 9 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index e68acbf39c9..0a4e8f45bf9 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -129,6 +129,7 @@ private enum Id NoEntityTypeConfigurationsWarning = CoreBaseId + 632, AccidentalEntityType = CoreBaseId + 633, AccidentalComplexPropertyCollection = CoreBaseId + 634, + ShadowPropertyNameNotValidIdentifierWarning = CoreBaseId + 635, // ChangeTracking events DetectChangesStarting = CoreBaseId + 800, @@ -750,6 +751,20 @@ private static EventId MakeModelValidationId(Id id) /// public static readonly EventId AccidentalComplexPropertyCollection = MakeModelValidationId(Id.AccidentalComplexPropertyCollection); + /// + /// A shadow property has a name that is not a valid C# identifier, which prevents it from being used in precompiled queries. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ShadowPropertyNameNotValidIdentifierWarning = + MakeModelValidationId(Id.ShadowPropertyNameNotValidIdentifierWarning); + /// /// The on the collection navigation property was ignored. /// diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index 3b76077e276..79e201f5fe1 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -1432,6 +1432,40 @@ private static string ShadowPropertyCreated(EventDefinitionBase definition, Even return d.GenerateMessage(p.Property.DeclaringType.DisplayName(), p.Property.Name); } + /// + /// Logs for the event. + /// + /// The diagnostics logger to use. + /// The property. + public static void ShadowPropertyNameNotValidIdentifierWarning( + this IDiagnosticsLogger diagnostics, + IProperty property) + { + var definition = CoreResources.LogShadowPropertyNameNotValidIdentifier(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, property.DeclaringType.DisplayName(), property.Name); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new PropertyEventData( + definition, + ShadowPropertyNameNotValidIdentifier, + property); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ShadowPropertyNameNotValidIdentifier(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (PropertyEventData)payload; + return d.GenerateMessage(p.Property.DeclaringType.DisplayName(), p.Property.Name); + } + /// /// Logs for the event. /// diff --git a/src/EFCore/Diagnostics/LoggingDefinitions.cs b/src/EFCore/Diagnostics/LoggingDefinitions.cs index 55133e004e3..d0498c902df 100644 --- a/src/EFCore/Diagnostics/LoggingDefinitions.cs +++ b/src/EFCore/Diagnostics/LoggingDefinitions.cs @@ -825,4 +825,13 @@ public abstract class LoggingDefinitions /// [EntityFrameworkInternal] public EventDefinitionBase? LogQueryCompilationStarting; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogShadowPropertyNameNotValidIdentifier; } diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 7c9812b71e2..23cc7464300 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -3092,6 +3092,9 @@ { "Member": "static readonly Microsoft.Extensions.Logging.EventId ShadowPropertyCreated" }, + { + "Member": "static readonly Microsoft.Extensions.Logging.EventId ShadowPropertyNameNotValidIdentifierWarning" + }, { "Member": "static readonly Microsoft.Extensions.Logging.EventId SkipCollectionChangeDetected" }, @@ -3355,6 +3358,9 @@ { "Member": "static void ShadowPropertyCreated(this Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger diagnostics, Microsoft.EntityFrameworkCore.Metadata.IProperty property);" }, + { + "Member": "static void ShadowPropertyNameNotValidIdentifierWarning(this Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger diagnostics, Microsoft.EntityFrameworkCore.Metadata.IProperty property);" + }, { "Member": "static void SkipCollectionChangeDetected(this Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger diagnostics, Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry internalEntityEntry, Microsoft.EntityFrameworkCore.Metadata.ISkipNavigation navigation, System.Collections.Generic.ISet added, System.Collections.Generic.ISet removed);" }, diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index e0a7e8f743c..eb30c7968b0 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.ObjectModel; +using System.Globalization; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -174,6 +175,70 @@ protected virtual void ValidateProperty( ValidateTypeMapping(property, logger); ValidatePrimitiveCollection(property, logger); ValidateAutoLoaded(property, structuralType, logger); + + if (property.IsShadowProperty() + && !IsValidCSharpIdentifier(property.Name)) + { + logger.ShadowPropertyNameNotValidIdentifierWarning(property); + } + } + + private static bool IsValidCSharpIdentifier(string name) + { + if (string.IsNullOrEmpty(name) + || !IsIdentifierStartCharacter(name[0])) + { + return false; + } + + for (var i = 1; i < name.Length; i++) + { + if (!IsIdentifierPartCharacter(name[i])) + { + return false; + } + } + + return true; + + static bool IsIdentifierStartCharacter(char ch) + => ch < 'a' + ? ch is >= 'A' and (<= 'Z' or '_') + : ch <= 'z' || (ch > '\u007F' && IsLetterChar(CharUnicodeInfo.GetUnicodeCategory(ch))); + + static bool IsIdentifierPartCharacter(char ch) + { + if (ch < 'a') + { + return (ch < 'A' ? ch is >= '0' and <= '9' : ch <= 'Z') || ch == '_'; + } + + if (ch <= 'z') + { + return true; + } + + if (ch <= '\u007F') + { + return false; + } + + var cat = CharUnicodeInfo.GetUnicodeCategory(ch); + return IsLetterChar(cat) + || cat is UnicodeCategory.DecimalDigitNumber + or UnicodeCategory.ConnectorPunctuation + or UnicodeCategory.NonSpacingMark + or UnicodeCategory.SpacingCombiningMark + or UnicodeCategory.Format; + } + + static bool IsLetterChar(UnicodeCategory cat) + => cat is UnicodeCategory.UppercaseLetter + or UnicodeCategory.LowercaseLetter + or UnicodeCategory.TitlecaseLetter + or UnicodeCategory.ModifierLetter + or UnicodeCategory.OtherLetter + or UnicodeCategory.LetterNumber; } /// diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 186af2df532..d9368ed5897 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -5557,6 +5557,31 @@ public static EventDefinition LogShadowPropertyCreated(IDiagnost return (EventDefinition)definition; } + /// + /// The shadow property '{entityType}.{property}' has a name that is not a valid C# identifier. Precompiled queries that use this property will fail to compile. Consider renaming the property to a valid C# identifier. + /// + public static EventDefinition LogShadowPropertyNameNotValidIdentifier(IDiagnosticsLogger logger) + { + var definition = ((LoggingDefinitions)logger.Definitions).LogShadowPropertyNameNotValidIdentifier; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((LoggingDefinitions)logger.Definitions).LogShadowPropertyNameNotValidIdentifier, + logger, + static logger => new EventDefinition( + logger.Options, + CoreEventId.ShadowPropertyNameNotValidIdentifierWarning, + LogLevel.Warning, + "CoreEventId.ShadowPropertyNameNotValidIdentifierWarning", + level => LoggerMessage.Define( + level, + CoreEventId.ShadowPropertyNameNotValidIdentifierWarning, + _resourceManager.GetString("LogShadowPropertyNameNotValidIdentifier")!))); + } + + return (EventDefinition)definition; + } + /// /// {addedCount} entities were added and {removedCount} entities were removed from skip navigation '{entityType}.{property}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 09ee563d2d1..13f459c928c 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1230,6 +1230,10 @@ The property '{entityType}.{property}' was created in shadow state because there are no eligible CLR members with a matching name. Debug CoreEventId.ShadowPropertyCreated string string + + The shadow property '{entityType}.{property}' has a name that is not a valid C# identifier. Precompiled queries that use this property will fail to compile. Consider renaming the property to a valid C# identifier. + Warning CoreEventId.ShadowPropertyNameNotValidIdentifierWarning string string + {addedCount} entities were added and {removedCount} entities were removed from skip navigation '{entityType}.{property}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values. Debug CoreEventId.SkipCollectionChangeDetected int int string string diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs index 79fba24c6cf..a36e2612871 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocPrecompiledQueryRelationalTestBase.cs @@ -390,7 +390,8 @@ public class InvalidNameNestedEntity [Fact] public virtual async Task Invalid_identifier_shadow_property_name() { - var contextFactory = await InitializeNonSharedTest(); + var contextFactory = await InitializeNonSharedTest( + onConfiguring: o => o.ConfigureWarnings(w => w.Ignore(CoreEventId.ShadowPropertyNameNotValidIdentifierWarning))); var options = contextFactory.GetOptions(); await Test( diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 275a48afaea..a0c3b9bb75f 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -462,6 +462,40 @@ public virtual void Passes_on_shadow_primary_key_created_by_convention_in_depend .GenerateMessage("A", "Key"), modelBuilder, LogLevel.Debug); } + [Fact] + public virtual void Warns_on_shadow_property_name_that_is_not_a_valid_identifier() + { + var modelBuilder = CreateConventionlessModelBuilder(); + var model = modelBuilder.Model; + + var entityType = model.AddEntityType(typeof(A)); + SetPrimaryKey(entityType); + AddProperties(entityType); + + entityType.AddProperty("NOT VALID !!!1", typeof(string)); + + VerifyWarning( + CoreResources.LogShadowPropertyNameNotValidIdentifier(new TestLogger()) + .GenerateMessage("A", "NOT VALID !!!1"), modelBuilder, LogLevel.Warning); + } + + [Fact] + public virtual void Does_not_warn_on_shadow_property_with_valid_identifier_name() + { + var modelBuilder = CreateConventionlessModelBuilder(); + var model = modelBuilder.Model; + + var entityType = model.AddEntityType(typeof(A)); + SetPrimaryKey(entityType); + AddProperties(entityType); + + entityType.AddProperty("ValidName", typeof(string)); + + VerifyLogDoesNotContain( + CoreResources.LogShadowPropertyNameNotValidIdentifier(new TestLogger()) + .GenerateMessage("A", "ValidName"), modelBuilder); + } + [Fact] // Issue #33484 public virtual void Does_not_log_for_shadow_property_when_creating_indexer_property() { From d83e0d597326d61d7de4113c44881d20e13c6122 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:52:02 +0000 Subject: [PATCH 04/21] Document why C# identifier validation is replicated in runtime assembly Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/Infrastructure/ModelValidator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index eb30c7968b0..303994f2ce8 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -185,6 +185,8 @@ protected virtual void ValidateProperty( private static bool IsValidCSharpIdentifier(string name) { + // This mirrors the Unicode rules used by Roslyn (and CSharpHelper in the Design assembly), replicated here because the + // runtime assembly does not reference Roslyn. if (string.IsNullOrEmpty(name) || !IsIdentifierStartCharacter(name[0])) { From a928c0d389421a3856fc525b42406588d5a76b2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 02:29:55 +0000 Subject: [PATCH 05/21] Move sanitization into LiftConstants; simplify identifier check; update warning message Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Internal/PrecompiledQueryCodeGenerator.cs | 48 ++-------------- src/EFCore/Infrastructure/ModelValidator.cs | 55 +++---------------- src/EFCore/Properties/CoreStrings.Designer.cs | 2 +- src/EFCore/Properties/CoreStrings.resx | 2 +- src/EFCore/Query/LiftableConstantProcessor.cs | 6 +- 5 files changed, 20 insertions(+), 93 deletions(-) diff --git a/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs b/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs index e04490d4daa..fa488ff6f90 100644 --- a/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs +++ b/src/EFCore.Design/Query/Internal/PrecompiledQueryCodeGenerator.cs @@ -975,52 +975,12 @@ private void GenerateQueryExecutor( var queryExecutorAfterLiftingExpression = _liftableConstantProcessor.LiftConstants(queryExecutor, materializerLiftableConstantContext, variableNames); - // Lifted constant variable names are partly derived from model metadata (e.g. property names), which may not be valid C# - // identifiers (shadow properties can be given arbitrary names). Sanitize any such names and replace the corresponding - // parameters across all the lifted constant expressions and the query executor. - var liftedConstants = _liftableConstantProcessor.LiftedConstants; - var sanitizedNames = new string[liftedConstants.Count]; - var liftedConstantExpressions = new Expression[liftedConstants.Count]; - List? originalParameters = null; - List? sanitizedParameters = null; - for (var i = 0; i < liftedConstants.Count; i++) - { - var (parameter, expression) = liftedConstants[i]; - liftedConstantExpressions[i] = expression; - - var sanitizedName = SanitizeIdentifierName(parameter.Name ?? "unknown"); - if (sanitizedName != parameter.Name) - { - var baseName = sanitizedName; - for (var j = 0; variableNames.Contains(sanitizedName); j++) - { - sanitizedName = baseName + j; - } - - (originalParameters ??= []).Add(parameter); - (sanitizedParameters ??= []).Add(Expression.Parameter(parameter.Type, sanitizedName)); - } - - variableNames.Add(sanitizedName); - sanitizedNames[i] = sanitizedName; - } - - if (originalParameters is not null) - { - var replacer = new ReplacingExpressionVisitor(originalParameters, sanitizedParameters!); - queryExecutorAfterLiftingExpression = replacer.Visit(queryExecutorAfterLiftingExpression); - - for (var i = 0; i < liftedConstantExpressions.Length; i++) - { - liftedConstantExpressions[i] = replacer.Visit(liftedConstantExpressions[i]); - } - } - - for (var i = 0; i < liftedConstants.Count; i++) + foreach (var liftedConstant in _liftableConstantProcessor.LiftedConstants) { var variableValueSyntax = _linqToCSharpTranslator.TranslateExpression( - liftedConstantExpressions[i], _constantReplacements, _memberAccessReplacements, namespaces, unsafeAccessors); - code.AppendLine($"var {sanitizedNames[i]} = {variableValueSyntax.NormalizeWhitespace().ToFullString()};"); + liftedConstant.Expression, _constantReplacements, _memberAccessReplacements, namespaces, unsafeAccessors); + // code.AppendLine($"{liftedConstant.Parameter.Type.Name} {liftedConstant.Parameter.Name} = {variableValueSyntax.NormalizeWhitespace().ToFullString()};"); + code.AppendLine($"var {liftedConstant.Parameter.Name} = {variableValueSyntax.NormalizeWhitespace().ToFullString()};"); } var queryExecutorSyntaxTree = diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 303994f2ce8..e70bd4c8a54 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.ObjectModel; -using System.Globalization; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -177,70 +176,34 @@ protected virtual void ValidateProperty( ValidateAutoLoaded(property, structuralType, logger); if (property.IsShadowProperty() - && !IsValidCSharpIdentifier(property.Name)) + && !IsValidIdentifier(property.Name)) { logger.ShadowPropertyNameNotValidIdentifierWarning(property); } } - private static bool IsValidCSharpIdentifier(string name) + /// + /// Returns if the given name only uses letters, digits and underscores and does not start with a digit; + /// that is, if it can be used as-is as a C# identifier in generated code. + /// + internal static bool IsValidIdentifier(string? name) { - // This mirrors the Unicode rules used by Roslyn (and CSharpHelper in the Design assembly), replicated here because the - // runtime assembly does not reference Roslyn. if (string.IsNullOrEmpty(name) - || !IsIdentifierStartCharacter(name[0])) + || (!char.IsLetter(name[0]) && name[0] != '_')) { return false; } for (var i = 1; i < name.Length; i++) { - if (!IsIdentifierPartCharacter(name[i])) + var ch = name[i]; + if (!char.IsLetter(ch) && !char.IsAsciiDigit(ch) && ch != '_') { return false; } } return true; - - static bool IsIdentifierStartCharacter(char ch) - => ch < 'a' - ? ch is >= 'A' and (<= 'Z' or '_') - : ch <= 'z' || (ch > '\u007F' && IsLetterChar(CharUnicodeInfo.GetUnicodeCategory(ch))); - - static bool IsIdentifierPartCharacter(char ch) - { - if (ch < 'a') - { - return (ch < 'A' ? ch is >= '0' and <= '9' : ch <= 'Z') || ch == '_'; - } - - if (ch <= 'z') - { - return true; - } - - if (ch <= '\u007F') - { - return false; - } - - var cat = CharUnicodeInfo.GetUnicodeCategory(ch); - return IsLetterChar(cat) - || cat is UnicodeCategory.DecimalDigitNumber - or UnicodeCategory.ConnectorPunctuation - or UnicodeCategory.NonSpacingMark - or UnicodeCategory.SpacingCombiningMark - or UnicodeCategory.Format; - } - - static bool IsLetterChar(UnicodeCategory cat) - => cat is UnicodeCategory.UppercaseLetter - or UnicodeCategory.LowercaseLetter - or UnicodeCategory.TitlecaseLetter - or UnicodeCategory.ModifierLetter - or UnicodeCategory.OtherLetter - or UnicodeCategory.LetterNumber; } /// diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index d9368ed5897..8c72415e1ec 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -5558,7 +5558,7 @@ public static EventDefinition LogShadowPropertyCreated(IDiagnost } /// - /// The shadow property '{entityType}.{property}' has a name that is not a valid C# identifier. Precompiled queries that use this property will fail to compile. Consider renaming the property to a valid C# identifier. + /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property using only numbers, letters and underscore. /// public static EventDefinition LogShadowPropertyNameNotValidIdentifier(IDiagnosticsLogger logger) { diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 13f459c928c..8ef2bef77eb 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1231,7 +1231,7 @@ Debug CoreEventId.ShadowPropertyCreated string string - The shadow property '{entityType}.{property}' has a name that is not a valid C# identifier. Precompiled queries that use this property will fail to compile. Consider renaming the property to a valid C# identifier. + The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property using only numbers, letters and underscore. Warning CoreEventId.ShadowPropertyNameNotValidIdentifierWarning string string diff --git a/src/EFCore/Query/LiftableConstantProcessor.cs b/src/EFCore/Query/LiftableConstantProcessor.cs index 525d460bb1c..80dd5a61aa8 100644 --- a/src/EFCore/Query/LiftableConstantProcessor.cs +++ b/src/EFCore/Query/LiftableConstantProcessor.cs @@ -126,7 +126,11 @@ public virtual Expression LiftConstants(Expression expression, ParameterExpressi continue; } - var name = liftedConstant.Parameter.Name ?? "unknown"; + // Lifted constant variable names are partly derived from model metadata (e.g. property names), which may not be valid C# + // identifiers (shadow properties can be given arbitrary names). Fall back to a generic name in that case. + var name = ModelValidator.IsValidIdentifier(liftedConstant.Parameter.Name) + ? liftedConstant.Parameter.Name! + : "unknown"; var baseName = name; for (var j = 0; variableNames.Contains(name); j++) { From 0e95a844b9b7d4ffb032a4b4a53d7573c54602ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 04:42:52 +0000 Subject: [PATCH 06/21] Use strict IsValidIdentifier logic in CSharpHelper identifier char checks Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Design/Internal/CSharpHelper.cs | 68 +------------------ 1 file changed, 2 insertions(+), 66 deletions(-) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index c62d8194854..953469b0133 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -1633,72 +1633,8 @@ public virtual string Expression( } private static bool IsIdentifierStartCharacter(char ch) - { - if (ch < 'a') - { - return ch is >= 'A' and (<= 'Z' or '_'); - } - - if (ch <= 'z') - { - return true; - } - - return ch > '\u007F' && IsLetterChar(CharUnicodeInfo.GetUnicodeCategory(ch)); - } + => char.IsLetter(ch) || ch == '_'; private static bool IsIdentifierPartCharacter(char ch) - { - if (ch < 'a') - { - return (ch < 'A' - ? ch is >= '0' and <= '9' - : ch <= 'Z') - || ch == '_'; - } - - if (ch <= 'z') - { - return true; - } - - if (ch <= '\u007F') - { - return false; - } - - var cat = CharUnicodeInfo.GetUnicodeCategory(ch); - if (IsLetterChar(cat)) - { - return true; - } - - switch (cat) - { - case UnicodeCategory.DecimalDigitNumber: - case UnicodeCategory.ConnectorPunctuation: - case UnicodeCategory.NonSpacingMark: - case UnicodeCategory.SpacingCombiningMark: - case UnicodeCategory.Format: - return true; - } - - return false; - } - - private static bool IsLetterChar(UnicodeCategory cat) - { - switch (cat) - { - case UnicodeCategory.UppercaseLetter: - case UnicodeCategory.LowercaseLetter: - case UnicodeCategory.TitlecaseLetter: - case UnicodeCategory.ModifierLetter: - case UnicodeCategory.OtherLetter: - case UnicodeCategory.LetterNumber: - return true; - } - - return false; - } + => char.IsLetter(ch) || char.IsAsciiDigit(ch) || ch == '_'; } From eeb2c519fb76afa6f8fd03ea45527f1f8071a15f Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 18 Jun 2026 23:27:46 -0700 Subject: [PATCH 07/21] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/EFCore/Diagnostics/CoreEventId.cs | 2 +- src/EFCore/Infrastructure/ModelValidator.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index 0a4e8f45bf9..8ec0fc0f7fb 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -752,7 +752,7 @@ private static EventId MakeModelValidationId(Id id) public static readonly EventId AccidentalComplexPropertyCollection = MakeModelValidationId(Id.AccidentalComplexPropertyCollection); /// - /// A shadow property has a name that is not a valid C# identifier, which prevents it from being used in precompiled queries. + /// A shadow property has a name that is not a valid identifier, which can cause issues in generated code. /// /// /// diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index e70bd4c8a54..c3974a1cfc3 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -183,8 +183,8 @@ protected virtual void ValidateProperty( } /// - /// Returns if the given name only uses letters, digits and underscores and does not start with a digit; - /// that is, if it can be used as-is as a C# identifier in generated code. + /// Returns if the given name only uses letters, ASCII digits and underscores and does not start with a digit; + /// that is, if it can be used as-is as an identifier in generated code. /// internal static bool IsValidIdentifier(string? name) { From 1d734842afd181bb8501cf5df5d76c9655648af1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 06:34:02 +0000 Subject: [PATCH 08/21] Adjust invalid shadow property warning message Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/Properties/CoreStrings.Designer.cs | 2 +- src/EFCore/Properties/CoreStrings.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 8c72415e1ec..c2c0a1c764e 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -5558,7 +5558,7 @@ public static EventDefinition LogShadowPropertyCreated(IDiagnost } /// - /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property using only numbers, letters and underscore. + /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits and underscore. /// public static EventDefinition LogShadowPropertyNameNotValidIdentifier(IDiagnosticsLogger logger) { diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 8ef2bef77eb..e50f1964c7e 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1231,7 +1231,7 @@ Debug CoreEventId.ShadowPropertyCreated string string - The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property using only numbers, letters and underscore. + The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits and underscore. Warning CoreEventId.ShadowPropertyNameNotValidIdentifierWarning string string From 3a006aaae43673eeccd5a15d0698ad485c622378 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 06:34:29 +0000 Subject: [PATCH 09/21] Polish invalid shadow property warning text Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/Properties/CoreStrings.Designer.cs | 2 +- src/EFCore/Properties/CoreStrings.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index c2c0a1c764e..1539ef0796a 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -5558,7 +5558,7 @@ public static EventDefinition LogShadowPropertyCreated(IDiagnost } /// - /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits and underscore. + /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits, and underscore. /// public static EventDefinition LogShadowPropertyNameNotValidIdentifier(IDiagnosticsLogger logger) { diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index e50f1964c7e..bc24b148df1 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1231,7 +1231,7 @@ Debug CoreEventId.ShadowPropertyCreated string string - The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits and underscore. + The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits, and underscore. Warning CoreEventId.ShadowPropertyNameNotValidIdentifierWarning string string From 9573291693555102960e8aaf70b6d517c4e395c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 19:21:28 +0000 Subject: [PATCH 10/21] Adjust invalid shadow property warning wording Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/Properties/CoreStrings.Designer.cs | 2 +- src/EFCore/Properties/CoreStrings.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 1539ef0796a..b7c29e725f2 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -5558,7 +5558,7 @@ public static EventDefinition LogShadowPropertyCreated(IDiagnost } /// - /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits, and underscore. + /// The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to use only letters, digits (except for the first character), and underscore. /// public static EventDefinition LogShadowPropertyNameNotValidIdentifier(IDiagnosticsLogger logger) { diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index bc24b148df1..0166459de44 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1231,7 +1231,7 @@ Debug CoreEventId.ShadowPropertyCreated string string - The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to start with a letter or underscore, and use only letters, digits, and underscore. + The shadow property '{entityType}.{property}' has a name that is not a valid identifier. This can cause issues in generated code. Consider renaming the property to use only letters, digits (except for the first character), and underscore. Warning CoreEventId.ShadowPropertyNameNotValidIdentifierWarning string string From ee5e1f17c9bdfef9a4a4d901d7f1ded58586c657 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Jun 2026 03:09:18 +0000 Subject: [PATCH 11/21] Update Cosmos discriminator shadow property name Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../CosmosDiscriminatorConvention.cs | 14 +++++++---- .../Query/NorthwindQueryCosmosFixture.cs | 23 +++++++++++-------- .../Extensions/CosmosBuilderExtensionsTest.cs | 20 ++++++++++++++-- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index 2990718e5c0..df9507ee2ef 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; +using Microsoft.EntityFrameworkCore; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -21,6 +22,8 @@ public class CosmosDiscriminatorConvention : IEntityTypeAnnotationChangedConvention, IModelEmbeddedDiscriminatorNameConvention { + private const string DiscriminatorPropertyName = "_type"; + /// /// Creates a new instance of . /// @@ -86,8 +89,9 @@ private static void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBui if (entityType.IsDocumentRoot()) { - entityTypeBuilder.HasDiscriminator(entityType.Model.GetEmbeddedDiscriminatorName(), typeof(string)) - ?.HasValue(entityType, entityType.ShortName()); + var discriminator = entityTypeBuilder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); + discriminator?.EntityType.FindDiscriminatorProperty()?.Builder.ToJsonProperty(entityType.Model.GetEmbeddedDiscriminatorName()); + discriminator?.HasValue(entityType, entityType.ShortName()); } else { @@ -125,7 +129,8 @@ public override void ProcessEntityTypeBaseTypeChanged( { if (entityType.IsDocumentRoot()) { - entityTypeBuilder.HasDiscriminator(entityType.Model.GetEmbeddedDiscriminatorName(), typeof(string)); + var discriminator = entityTypeBuilder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); + discriminator?.EntityType.FindDiscriminatorProperty()?.Builder.ToJsonProperty(entityType.Model.GetEmbeddedDiscriminatorName()); } } else @@ -137,9 +142,10 @@ public override void ProcessEntityTypeBaseTypeChanged( return; } - var discriminator = rootType.Builder.HasDiscriminator(entityType.Model.GetEmbeddedDiscriminatorName(), typeof(string)); + var discriminator = rootType.Builder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); if (discriminator != null) { + discriminator.EntityType.FindDiscriminatorProperty()?.Builder.ToJsonProperty(entityType.Model.GetEmbeddedDiscriminatorName()); SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs index fd75389851b..1e8f5bb69b8 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs @@ -52,32 +52,35 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasRootDiscriminatorInJsonId() .ToContainer("ProductsAndOrders"); - modelBuilder.Entity() - .ToContainer("ProductsAndOrders") - .HasRootDiscriminatorInJsonId() - .HasDiscriminator("$type").HasValue("Order"); + modelBuilder.Entity().ToContainer("ProductsAndOrders").HasRootDiscriminatorInJsonId(); + modelBuilder.Entity().HasDiscriminator("_type").HasValue("Order"); + modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); modelBuilder .Entity() .ToContainer("ProductsAndOrders") - .HasRootDiscriminatorInJsonId() - .HasDiscriminator("$type").HasValue("Product"); + .HasRootDiscriminatorInJsonId(); + modelBuilder.Entity().HasDiscriminator("_type").HasValue("Product"); + modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); modelBuilder .Entity() .ToContainer("ProductsAndOrders") - .HasRootDiscriminatorInJsonId() - .HasDiscriminator("$type").HasValue("ProductView"); + .HasRootDiscriminatorInJsonId(); + modelBuilder.Entity().HasDiscriminator("_type").HasValue("ProductView"); + modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); modelBuilder .Entity() .ToContainer("Customers") - .HasDiscriminator("$type").HasValue("Customer"); + .HasDiscriminator("_type").HasValue("Customer"); + modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); modelBuilder .Entity() .ToContainer("Customers") - .HasDiscriminator("$type").HasValue("Customer"); + .HasDiscriminator("_type").HasValue("Customer"); + modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); modelBuilder.Entity().Metadata.RemoveIndex( modelBuilder.Entity().Property(e => e.City).Metadata.GetContainingIndexes().Single()); diff --git a/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs b/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs index 193fe2084af..49945ead2d9 100644 --- a/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs +++ b/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs @@ -135,7 +135,8 @@ public void Default_discriminator_can_be_removed() var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; - Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.Name); + Assert.Equal("_type", entityType.FindDiscriminatorProperty()!.Name); + Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); Assert.Equal(nameof(Customer), entityType.GetDiscriminatorValue()); modelBuilder.Entity().HasNoDiscriminator(); @@ -145,7 +146,8 @@ public void Default_discriminator_can_be_removed() modelBuilder.Entity().HasBaseType(); - Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.Name); + Assert.Equal("_type", entityType.FindDiscriminatorProperty()!.Name); + Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); Assert.Equal(nameof(Customer), entityType.GetDiscriminatorValue()); modelBuilder.Entity().HasBaseType((string)null); @@ -166,6 +168,20 @@ public void Can_set_etag_concurrency_entity() Assert.True(etagProperty.IsConcurrencyToken); } + [Fact] + public void Default_discriminator_property_uses_embedded_discriminator_json_name() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.HasEmbeddedDiscriminatorName("Terminator"); + modelBuilder.Entity(); + + var discriminatorProperty = modelBuilder.Model.FindEntityType(typeof(Customer))!.FindDiscriminatorProperty()!; + + Assert.Equal("_type", discriminatorProperty.Name); + Assert.Equal("Terminator", discriminatorProperty.GetJsonPropertyName()); + } + [Fact] public void Can_set_etag_concurrency_property() { From 955413398d2ad875d4895d7cfd4e7fc0b145ae81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Jun 2026 03:15:04 +0000 Subject: [PATCH 12/21] Polish Cosmos discriminator configuration updates Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../CosmosDiscriminatorConvention.cs | 25 +++++++++----- .../Query/NorthwindQueryCosmosFixture.cs | 34 +++++++++---------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index df9507ee2ef..00126127dc4 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; -using Microsoft.EntityFrameworkCore; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -86,11 +85,9 @@ private static void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBui { return; } - if (entityType.IsDocumentRoot()) { - var discriminator = entityTypeBuilder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); - discriminator?.EntityType.FindDiscriminatorProperty()?.Builder.ToJsonProperty(entityType.Model.GetEmbeddedDiscriminatorName()); + var discriminator = HasDiscriminator(entityTypeBuilder); discriminator?.HasValue(entityType, entityType.ShortName()); } else @@ -129,8 +126,7 @@ public override void ProcessEntityTypeBaseTypeChanged( { if (entityType.IsDocumentRoot()) { - var discriminator = entityTypeBuilder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); - discriminator?.EntityType.FindDiscriminatorProperty()?.Builder.ToJsonProperty(entityType.Model.GetEmbeddedDiscriminatorName()); + HasDiscriminator(entityTypeBuilder); } } else @@ -142,15 +138,28 @@ public override void ProcessEntityTypeBaseTypeChanged( return; } - var discriminator = rootType.Builder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); + var discriminator = HasDiscriminator(rootType.Builder); if (discriminator != null) { - discriminator.EntityType.FindDiscriminatorProperty()?.Builder.ToJsonProperty(entityType.Model.GetEmbeddedDiscriminatorName()); SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator); } } } + private static IConventionDiscriminatorBuilder? HasDiscriminator(IConventionEntityTypeBuilder entityTypeBuilder) + { + var discriminator = entityTypeBuilder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); + var discriminatorProperty = discriminator?.EntityType.FindDiscriminatorProperty(); + if (discriminatorProperty != null) + { + CosmosPropertyBuilderExtensions.ToJsonProperty( + discriminatorProperty.Builder, + entityTypeBuilder.Metadata.Model.GetEmbeddedDiscriminatorName()); + } + + return discriminator; + } + /// protected override void SetDefaultDiscriminatorValues( IEnumerable entityTypes, diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs index 1e8f5bb69b8..1d4f647ed78 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs @@ -52,35 +52,35 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .HasRootDiscriminatorInJsonId() .ToContainer("ProductsAndOrders"); - modelBuilder.Entity().ToContainer("ProductsAndOrders").HasRootDiscriminatorInJsonId(); - modelBuilder.Entity().HasDiscriminator("_type").HasValue("Order"); - modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); + var orderQuery = modelBuilder.Entity().ToContainer("ProductsAndOrders").HasRootDiscriminatorInJsonId(); + orderQuery.HasDiscriminator("_type").HasValue("Order"); + orderQuery.Property("_type").ToJsonProperty("$type"); - modelBuilder + var productQuery = modelBuilder .Entity() .ToContainer("ProductsAndOrders") .HasRootDiscriminatorInJsonId(); - modelBuilder.Entity().HasDiscriminator("_type").HasValue("Product"); - modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); + productQuery.HasDiscriminator("_type").HasValue("Product"); + productQuery.Property("_type").ToJsonProperty("$type"); - modelBuilder + var productView = modelBuilder .Entity() .ToContainer("ProductsAndOrders") .HasRootDiscriminatorInJsonId(); - modelBuilder.Entity().HasDiscriminator("_type").HasValue("ProductView"); - modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); + productView.HasDiscriminator("_type").HasValue("ProductView"); + productView.Property("_type").ToJsonProperty("$type"); - modelBuilder + var customerQueryWithQueryFilter = modelBuilder .Entity() - .ToContainer("Customers") - .HasDiscriminator("_type").HasValue("Customer"); - modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); + .ToContainer("Customers"); + customerQueryWithQueryFilter.HasDiscriminator("_type").HasValue("Customer"); + customerQueryWithQueryFilter.Property("_type").ToJsonProperty("$type"); - modelBuilder + var customerQuery = modelBuilder .Entity() - .ToContainer("Customers") - .HasDiscriminator("_type").HasValue("Customer"); - modelBuilder.Entity().Property("_type").ToJsonProperty("$type"); + .ToContainer("Customers"); + customerQuery.HasDiscriminator("_type").HasValue("Customer"); + customerQuery.Property("_type").ToJsonProperty("$type"); modelBuilder.Entity().Metadata.RemoveIndex( modelBuilder.Entity().Property(e => e.City).Metadata.GetContainingIndexes().Single()); From c6bb2875e46272a6fa1849053fd3de764f8b7485 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Jun 2026 04:46:15 +0000 Subject: [PATCH 13/21] Use default Cosmos discriminator property name Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../CosmosDiscriminatorConvention.cs | 4 +--- .../Query/NorthwindQueryCosmosFixture.cs | 20 +++++++++---------- .../Extensions/CosmosBuilderExtensionsTest.cs | 6 +++--- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index 00126127dc4..1e007b7aaf5 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -21,8 +21,6 @@ public class CosmosDiscriminatorConvention : IEntityTypeAnnotationChangedConvention, IModelEmbeddedDiscriminatorNameConvention { - private const string DiscriminatorPropertyName = "_type"; - /// /// Creates a new instance of . /// @@ -148,7 +146,7 @@ public override void ProcessEntityTypeBaseTypeChanged( private static IConventionDiscriminatorBuilder? HasDiscriminator(IConventionEntityTypeBuilder entityTypeBuilder) { - var discriminator = entityTypeBuilder.HasDiscriminator(DiscriminatorPropertyName, typeof(string)); + var discriminator = entityTypeBuilder.HasDiscriminator(typeof(string)); var discriminatorProperty = discriminator?.EntityType.FindDiscriminatorProperty(); if (discriminatorProperty != null) { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs index 1d4f647ed78..dae02c001a8 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindQueryCosmosFixture.cs @@ -53,34 +53,34 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .ToContainer("ProductsAndOrders"); var orderQuery = modelBuilder.Entity().ToContainer("ProductsAndOrders").HasRootDiscriminatorInJsonId(); - orderQuery.HasDiscriminator("_type").HasValue("Order"); - orderQuery.Property("_type").ToJsonProperty("$type"); + orderQuery.HasDiscriminator().HasValue("Order"); + orderQuery.Property("Discriminator").ToJsonProperty("$type"); var productQuery = modelBuilder .Entity() .ToContainer("ProductsAndOrders") .HasRootDiscriminatorInJsonId(); - productQuery.HasDiscriminator("_type").HasValue("Product"); - productQuery.Property("_type").ToJsonProperty("$type"); + productQuery.HasDiscriminator().HasValue("Product"); + productQuery.Property("Discriminator").ToJsonProperty("$type"); var productView = modelBuilder .Entity() .ToContainer("ProductsAndOrders") .HasRootDiscriminatorInJsonId(); - productView.HasDiscriminator("_type").HasValue("ProductView"); - productView.Property("_type").ToJsonProperty("$type"); + productView.HasDiscriminator().HasValue("ProductView"); + productView.Property("Discriminator").ToJsonProperty("$type"); var customerQueryWithQueryFilter = modelBuilder .Entity() .ToContainer("Customers"); - customerQueryWithQueryFilter.HasDiscriminator("_type").HasValue("Customer"); - customerQueryWithQueryFilter.Property("_type").ToJsonProperty("$type"); + customerQueryWithQueryFilter.HasDiscriminator().HasValue("Customer"); + customerQueryWithQueryFilter.Property("Discriminator").ToJsonProperty("$type"); var customerQuery = modelBuilder .Entity() .ToContainer("Customers"); - customerQuery.HasDiscriminator("_type").HasValue("Customer"); - customerQuery.Property("_type").ToJsonProperty("$type"); + customerQuery.HasDiscriminator().HasValue("Customer"); + customerQuery.Property("Discriminator").ToJsonProperty("$type"); modelBuilder.Entity().Metadata.RemoveIndex( modelBuilder.Entity().Property(e => e.City).Metadata.GetContainingIndexes().Single()); diff --git a/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs b/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs index 49945ead2d9..91a680b2538 100644 --- a/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs +++ b/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs @@ -135,7 +135,7 @@ public void Default_discriminator_can_be_removed() var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; - Assert.Equal("_type", entityType.FindDiscriminatorProperty()!.Name); + Assert.Equal("Discriminator", entityType.FindDiscriminatorProperty()!.Name); Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); Assert.Equal(nameof(Customer), entityType.GetDiscriminatorValue()); @@ -146,7 +146,7 @@ public void Default_discriminator_can_be_removed() modelBuilder.Entity().HasBaseType(); - Assert.Equal("_type", entityType.FindDiscriminatorProperty()!.Name); + Assert.Equal("Discriminator", entityType.FindDiscriminatorProperty()!.Name); Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); Assert.Equal(nameof(Customer), entityType.GetDiscriminatorValue()); @@ -178,7 +178,7 @@ public void Default_discriminator_property_uses_embedded_discriminator_json_name var discriminatorProperty = modelBuilder.Model.FindEntityType(typeof(Customer))!.FindDiscriminatorProperty()!; - Assert.Equal("_type", discriminatorProperty.Name); + Assert.Equal("Discriminator", discriminatorProperty.Name); Assert.Equal("Terminator", discriminatorProperty.GetJsonPropertyName()); } From 482bd555aad5a05530631b986e4788155ba38267 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 18:23:29 +0000 Subject: [PATCH 14/21] Use embedded discriminator name in relational JSON snapshots Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Design/AnnotationCodeGenerator.cs | 8 ++- .../RelationalPropertyExtensions.cs | 2 +- ...rpMigrationsGeneratorTest.ModelSnapshot.cs | 18 ++++++- .../RelationalBuilderExtensionsTest.cs | 49 +++++++++++++++++++ 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 3a09933958a..ca63c50d4af 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -52,7 +52,8 @@ public AnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) /// public virtual IEnumerable FilterIgnoredAnnotations(IEnumerable annotations) - => annotations.Where(a => !(CoreAnnotationNames.AllNames.Contains(a.Name) + => annotations.Where(a => !((CoreAnnotationNames.AllNames.Contains(a.Name) + && a.Name != CoreAnnotationNames.EmbeddedDiscriminatorName) || IgnoredRelationalAnnotations.Contains(a.Name))); /// @@ -180,6 +181,11 @@ public virtual IReadOnlyList GenerateFluentApiCalls( { var methodCallCodeFragments = new List(); + GenerateSimpleFluentApiCall( + annotations, + CoreAnnotationNames.EmbeddedDiscriminatorName, "HasEmbeddedDiscriminatorName", + methodCallCodeFragments); + GenerateSimpleFluentApiCall( annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index caa17d220d1..3e1e3e866e9 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -2071,7 +2071,7 @@ private static TValue ThrowReadValueException( ?? (property.IsKey() || !property.DeclaringType.IsMappedToJson() ? null : property == property.DeclaringType.FindDiscriminatorProperty() - ? "$type" + ? property.DeclaringType.Model.GetEmbeddedDiscriminatorName() : property.Name); /// diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index 89d1a99e71b..7387d9ce459 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -6489,6 +6489,7 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() => Test( builder => { + builder.HasEmbeddedDiscriminatorName("Terminator"); builder.Entity(b => { b.HasKey(x => x.Id).HasName("PK_Custom"); @@ -6501,6 +6502,7 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() bb.ComplexProperty( x => x.EntityWithStringKey, bbb => { + bbb.HasDiscriminator("Discriminator"); bbb.ComplexCollection(x => x.Properties, bbbb => bbbb.HasJsonPropertyName("JsonProps")); }); bb.ComplexProperty( @@ -6513,8 +6515,14 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() }); }, AddBoilerPlate( - GetHeading() - + """ + """ + modelBuilder + .HasEmbeddedDiscriminatorName("Terminator") + .HasDefaultSchema("DefaultSchema") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b => { var id = b.Property("Id") @@ -6543,6 +6551,9 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() b1.ComplexProperty(typeof(Dictionary), "EntityWithStringKey", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", b2 => { + b2.Property("Discriminator") + .IsRequired(); + b2.Property("Id"); b2.ComplexCollection(typeof(List>), "Properties", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey.Properties#EntityWithStringProperty", b3 => @@ -6553,6 +6564,8 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() b3.HasJsonPropertyName("JsonProps"); }); + + b2.HasDiscriminator("Discriminator").HasValue("EntityWithStringKey"); }); b1 @@ -6595,6 +6608,7 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() Assert.False(entityWithStringKeyComplexProperty.IsCollection); Assert.True(entityWithStringKeyComplexProperty.IsNullable); var entityWithStringKeyComplexType = entityWithStringKeyComplexProperty.ComplexType; + Assert.Equal("Terminator", entityWithStringKeyComplexType.FindDiscriminatorProperty()!.GetJsonPropertyName()); var propertiesComplexCollection = entityWithStringKeyComplexType.FindComplexProperty(nameof(EntityWithStringKey.Properties)); diff --git a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs index 8ce3403ad5a..3ca98c7b8a9 100644 --- a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs @@ -895,6 +895,38 @@ public void Can_set_default_discriminator_value_separately() Assert.Equal("2", modelBuilder.Model.FindEntityType(typeof(SpecialCustomer)).GetDiscriminatorValue()); } + [Fact] + public void Embedded_discriminator_name_is_used_for_json_entity_and_complex_discriminators() + { + var modelBuilder = CreateConventionModelBuilder(); + + modelBuilder.HasEmbeddedDiscriminatorName("Terminator"); + modelBuilder.Entity( + b => + { + b.OwnsOne( + e => e.JsonOwned, bb => + { + bb.ToJson(); + }); + b.ComplexProperty( + e => e.JsonComplex, bb => + { + bb.ToJson(); + bb.HasDiscriminator("ComplexDiscriminator"); + }); + }); + + var entityType = modelBuilder.Model.FindEntityType(typeof(JsonContainer))!; + var ownedEntityType = entityType.FindNavigation(nameof(JsonContainer.JsonOwned))!.TargetEntityType; + ((EntityType)ownedEntityType).Builder.HasDiscriminator("EntityDiscriminator", typeof(string), ConfigurationSource.Explicit)!.HasValue( + typeof(JsonOwned), "Owned"); + var complexType = entityType.FindComplexProperty(nameof(JsonContainer.JsonComplex))!.ComplexType; + + Assert.Equal("Terminator", ownedEntityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); + Assert.Equal("Terminator", complexType.FindDiscriminatorProperty()!.GetJsonPropertyName()); + } + [Fact] public void Can_set_schema_on_model() { @@ -1522,6 +1554,23 @@ private class OrderDetails public Order Order { get; } } + private class JsonContainer + { + public int Id { get; set; } + public JsonOwned JsonOwned { get; set; } + public JsonComplex JsonComplex { get; set; } + } + + private class JsonOwned + { + public string Name { get; set; } + } + + private class JsonComplex + { + public string Name { get; set; } + } + private class Splot { public static readonly PropertyInfo SplowedProperty = typeof(Splot).GetProperty("Splowed"); From ef171203219629a7572163180f5ff63140449d30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 18:27:32 +0000 Subject: [PATCH 15/21] Polish embedded discriminator snapshot coverage Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore.Relational/Design/AnnotationCodeGenerator.cs | 7 ++++--- .../Extensions/RelationalBuilderExtensionsTest.cs | 9 +++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index ca63c50d4af..65cd05470cc 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -52,9 +52,10 @@ public AnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) /// public virtual IEnumerable FilterIgnoredAnnotations(IEnumerable annotations) - => annotations.Where(a => !((CoreAnnotationNames.AllNames.Contains(a.Name) - && a.Name != CoreAnnotationNames.EmbeddedDiscriminatorName) - || IgnoredRelationalAnnotations.Contains(a.Name))); + => annotations.Where( + a => (!CoreAnnotationNames.AllNames.Contains(a.Name) + || a.Name == CoreAnnotationNames.EmbeddedDiscriminatorName) + && !IgnoredRelationalAnnotations.Contains(a.Name)); /// public virtual void RemoveAnnotationsHandledByConventions(IModel model, IDictionary annotations) diff --git a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs index 3ca98c7b8a9..54e51ca07fd 100644 --- a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs @@ -923,8 +923,13 @@ public void Embedded_discriminator_name_is_used_for_json_entity_and_complex_disc typeof(JsonOwned), "Owned"); var complexType = entityType.FindComplexProperty(nameof(JsonContainer.JsonComplex))!.ComplexType; - Assert.Equal("Terminator", ownedEntityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); - Assert.Equal("Terminator", complexType.FindDiscriminatorProperty()!.GetJsonPropertyName()); + var ownedDiscriminatorProperty = ownedEntityType.FindDiscriminatorProperty()!; + var complexDiscriminatorProperty = complexType.FindDiscriminatorProperty()!; + + Assert.Equal("EntityDiscriminator", ownedDiscriminatorProperty.Name); + Assert.Equal("Terminator", ownedDiscriminatorProperty.GetJsonPropertyName()); + Assert.Equal("ComplexDiscriminator", complexDiscriminatorProperty.Name); + Assert.Equal("Terminator", complexDiscriminatorProperty.GetJsonPropertyName()); } [Fact] From 4ce5c26d964962551c416ce2291317919e84c9c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 18:30:41 +0000 Subject: [PATCH 16/21] Fix nullability in relational discriminator test Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Extensions/RelationalBuilderExtensionsTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs index 54e51ca07fd..d3d88f8417d 100644 --- a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs @@ -1562,18 +1562,18 @@ private class OrderDetails private class JsonContainer { public int Id { get; set; } - public JsonOwned JsonOwned { get; set; } - public JsonComplex JsonComplex { get; set; } + public JsonOwned JsonOwned { get; set; } = null!; + public JsonComplex JsonComplex { get; set; } = null!; } private class JsonOwned { - public string Name { get; set; } + public string Name { get; set; } = null!; } private class JsonComplex { - public string Name { get; set; } + public string Name { get; set; } = null!; } private class Splot From 667b21e8fb1d6aa80b5c2fbc72816daa6ba82ebd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 18:34:22 +0000 Subject: [PATCH 17/21] Refine embedded discriminator readability Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore.Relational/Design/AnnotationCodeGenerator.cs | 5 ++++- .../Extensions/RelationalBuilderExtensionsTest.cs | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 65cd05470cc..b3b505189a6 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -54,9 +54,12 @@ public AnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) public virtual IEnumerable FilterIgnoredAnnotations(IEnumerable annotations) => annotations.Where( a => (!CoreAnnotationNames.AllNames.Contains(a.Name) - || a.Name == CoreAnnotationNames.EmbeddedDiscriminatorName) + || ShouldGenerateCoreAnnotation(a.Name)) && !IgnoredRelationalAnnotations.Contains(a.Name)); + private static bool ShouldGenerateCoreAnnotation(string annotationName) + => annotationName == CoreAnnotationNames.EmbeddedDiscriminatorName; + /// public virtual void RemoveAnnotationsHandledByConventions(IModel model, IDictionary annotations) => RemoveConventionalAnnotationsHelper(model, annotations, IsHandledByConvention); diff --git a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs index d3d88f8417d..4036d2daab3 100644 --- a/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Extensions/RelationalBuilderExtensionsTest.cs @@ -919,8 +919,9 @@ public void Embedded_discriminator_name_is_used_for_json_entity_and_complex_disc var entityType = modelBuilder.Model.FindEntityType(typeof(JsonContainer))!; var ownedEntityType = entityType.FindNavigation(nameof(JsonContainer.JsonOwned))!.TargetEntityType; - ((EntityType)ownedEntityType).Builder.HasDiscriminator("EntityDiscriminator", typeof(string), ConfigurationSource.Explicit)!.HasValue( - typeof(JsonOwned), "Owned"); + var discriminatorBuilder = ((EntityType)ownedEntityType).Builder.HasDiscriminator( + "EntityDiscriminator", typeof(string), ConfigurationSource.Explicit)!; + discriminatorBuilder.HasValue(typeof(JsonOwned), "Owned"); var complexType = entityType.FindComplexProperty(nameof(JsonContainer.JsonComplex))!.ComplexType; var ownedDiscriminatorProperty = ownedEntityType.FindDiscriminatorProperty()!; From 01eb71151d012cdf78b5c9c4823c093caa036064 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 19:11:09 +0000 Subject: [PATCH 18/21] WIP: update Cosmos discriminator expectations Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Conventions/CosmosDiscriminatorConvention.cs | 2 +- src/EFCore.Design/Design/Internal/CSharpHelper.cs | 7 +++---- .../Migrations/Design/CSharpSnapshotGenerator.cs | 9 +++++++++ .../Design/AnnotationCodeGenerator.cs | 14 ++------------ .../Extensions/RelationalPropertyExtensions.cs | 2 +- src/EFCore/Infrastructure/ModelValidator.cs | 3 ++- .../Inheritance/InheritanceQueryCosmosFixture.cs | 1 - .../Inheritance/InheritanceQueryCosmosTest.cs | 6 +++--- .../Baselines/BigModel/ManyTypesEntityType.cs | 2 +- .../Baselines/BigModel/PrincipalBaseEntityType.cs | 2 +- ...ePrincipalDerivedDependentBasebyteEntityType.cs | 2 +- .../BigModel/PrincipalDerivedEntityType.cs | 2 +- .../ComplexTypes/PrincipalBaseEntityType.cs | 2 +- .../ComplexTypes/PrincipalDerivedEntityType.cs | 2 +- .../IndexedDataEntityType.cs | 2 +- .../Baselines/No_NativeAOT/ManyTypesEntityType.cs | 2 +- .../No_NativeAOT/PrincipalBaseEntityType.cs | 2 +- ...ePrincipalDerivedDependentBasebyteEntityType.cs | 2 +- .../No_NativeAOT/PrincipalDerivedEntityType.cs | 2 +- .../SimpleModel/DependentDerivedEntityType.cs | 2 +- .../Extensions/CosmosBuilderExtensionsTest.cs | 4 ++-- .../CSharpMigrationsGeneratorTest.ModelSnapshot.cs | 4 ++-- 22 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs index 1e007b7aaf5..22bb65d622e 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs @@ -152,7 +152,7 @@ public override void ProcessEntityTypeBaseTypeChanged( { CosmosPropertyBuilderExtensions.ToJsonProperty( discriminatorProperty.Builder, - entityTypeBuilder.Metadata.Model.GetEmbeddedDiscriminatorName()); + (string?)entityTypeBuilder.Metadata.Model.FindAnnotation("EmbeddedDiscriminatorName")?.Value); } return discriminator; diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 953469b0133..100d32ccbab 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -297,8 +297,7 @@ private static string Identifier(string name, bool? capitalize) builder.Append(name[partStart..]); } - if (builder.Length == 0 - || !IsIdentifierStartCharacter(builder[0])) + if (!ModelValidator.IsValidIdentifier(builder.ToString())) { builder.Insert(0, '_'); } @@ -1633,8 +1632,8 @@ public virtual string Expression( } private static bool IsIdentifierStartCharacter(char ch) - => char.IsLetter(ch) || ch == '_'; + => ModelValidator.IsValidIdentifier(ch.ToString()); private static bool IsIdentifierPartCharacter(char ch) - => char.IsLetter(ch) || char.IsAsciiDigit(ch) || ch == '_'; + => ModelValidator.IsValidIdentifier($"_{ch}"); } diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 5aaa65db93b..f151fdb9bd9 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -579,6 +579,15 @@ private Dictionary GetAnnotations(IProperty property) property.GetColumnName()); } + if (!annotations.ContainsKey(RelationalAnnotationNames.JsonPropertyName) + && property.GetJsonPropertyName() is { } jsonPropertyName + && property.Name != jsonPropertyName) + { + annotations[RelationalAnnotationNames.JsonPropertyName] = new Annotation( + RelationalAnnotationNames.JsonPropertyName, + jsonPropertyName); + } + return Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(annotations.Values) .ToDictionary(a => a.Name, a => a); diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index b3b505189a6..3a09933958a 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -52,13 +52,8 @@ public AnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) /// public virtual IEnumerable FilterIgnoredAnnotations(IEnumerable annotations) - => annotations.Where( - a => (!CoreAnnotationNames.AllNames.Contains(a.Name) - || ShouldGenerateCoreAnnotation(a.Name)) - && !IgnoredRelationalAnnotations.Contains(a.Name)); - - private static bool ShouldGenerateCoreAnnotation(string annotationName) - => annotationName == CoreAnnotationNames.EmbeddedDiscriminatorName; + => annotations.Where(a => !(CoreAnnotationNames.AllNames.Contains(a.Name) + || IgnoredRelationalAnnotations.Contains(a.Name))); /// public virtual void RemoveAnnotationsHandledByConventions(IModel model, IDictionary annotations) @@ -185,11 +180,6 @@ public virtual IReadOnlyList GenerateFluentApiCalls( { var methodCallCodeFragments = new List(); - GenerateSimpleFluentApiCall( - annotations, - CoreAnnotationNames.EmbeddedDiscriminatorName, "HasEmbeddedDiscriminatorName", - methodCallCodeFragments); - GenerateSimpleFluentApiCall( annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 3e1e3e866e9..9514b9bf52c 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -2071,7 +2071,7 @@ private static TValue ThrowReadValueException( ?? (property.IsKey() || !property.DeclaringType.IsMappedToJson() ? null : property == property.DeclaringType.FindDiscriminatorProperty() - ? property.DeclaringType.Model.GetEmbeddedDiscriminatorName() + ? (string?)property.DeclaringType.Model.FindAnnotation("EmbeddedDiscriminatorName")?.Value : property.Name); /// diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index c3974a1cfc3..f06d6d37048 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -186,7 +186,8 @@ protected virtual void ValidateProperty( /// Returns if the given name only uses letters, ASCII digits and underscores and does not start with a digit; /// that is, if it can be used as-is as an identifier in generated code. /// - internal static bool IsValidIdentifier(string? name) + [EntityFrameworkInternal] + public static bool IsValidIdentifier(string? name) { if (string.IsNullOrEmpty(name) || (!char.IsLetter(name[0]) && name[0] != '_')) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosFixture.cs index 43862b4fde2..83521a667c0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosFixture.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosFixture.cs @@ -34,7 +34,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().ToContainer("Animals"); modelBuilder.Entity().ToContainer("Plants"); - modelBuilder.Entity().Property("Discriminator").ToJsonProperty("_type"); modelBuilder.Entity().ToContainer("Countries"); modelBuilder.Entity().ToContainer("Drinks"); modelBuilder.Entity().ToContainer("Animals"); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosTest.cs index 64780ac44dc..31dc9368881 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/Inheritance/InheritanceQueryCosmosTest.cs @@ -253,7 +253,7 @@ public override Task Can_use_of_type_rose(bool async) """ SELECT VALUE c FROM root c -WHERE (c["$type"] IN ("Daisy", "Rose") AND (c["$type"] = "Rose")) +WHERE (c["Discriminator"] IN ("Daisy", "Rose") AND (c["Discriminator"] = "Rose")) """); }); @@ -290,7 +290,7 @@ public override Task Can_query_all_plants(bool async) """ SELECT VALUE c FROM root c -WHERE c["$type"] IN ("Daisy", "Rose") +WHERE c["Discriminator"] IN ("Daisy", "Rose") ORDER BY c["id"] """); }); @@ -350,7 +350,7 @@ public override Task Can_query_just_roses(bool async) """ SELECT VALUE c FROM root c -WHERE (c["$type"] = "Rose") +WHERE (c["Discriminator"] = "Rose") OFFSET 0 LIMIT 2 """); }); diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs index 10f0b26b8a5..9088f2b2e27 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs @@ -36,7 +36,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+ManyTypes", typeof(CompiledModelTestBase.ManyTypes), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "ManyTypes", propertyCount: 170, keyCount: 1); diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBaseEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBaseEntityType.cs index 06643bad1a0..074783fedc5 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBaseEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBaseEntityType.cs @@ -32,7 +32,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+PrincipalBase", typeof(CompiledModelTestBase.PrincipalBase), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalBase", derivedTypesCount: 1, propertyCount: 15, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index d9d7d3e8a99..d5dfb096b62 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -34,7 +34,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), propertyBag: true, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalBasePrincipalDerived>", propertyCount: 8, foreignKeyCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalDerivedEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalDerivedEntityType.cs index edcac7de4b9..5712ed3bf4a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalDerivedEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/BigModel/PrincipalDerivedEntityType.cs @@ -24,7 +24,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+PrincipalDerived>", typeof(CompiledModelTestBase.PrincipalDerived>), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalDerived", propertyCount: 0, navigationCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs index 1ab863968a2..2b6ee66c55a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs @@ -32,7 +32,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+PrincipalBase", typeof(CompiledModelTestBase.PrincipalBase), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalBase", derivedTypesCount: 1, propertyCount: 15, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs index cbe7e7aac5b..8645018f1ea 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs @@ -31,7 +31,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+PrincipalDerived>", typeof(CompiledModelTestBase.PrincipalDerived>), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalDerived", propertyCount: 0, complexPropertyCount: 2); diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs index 11be89f211d..9a549751d78 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs @@ -32,7 +32,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelCosmosTest+IndexedData", typeof(CompiledModelCosmosTest.IndexedData), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "IndexedData", propertyCount: 10, unnamedIndexCount: 3, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs index a4efb56e17a..f72adba1643 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs @@ -30,7 +30,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+ManyTypes", typeof(CompiledModelTestBase.ManyTypes), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "ManyTypes", propertyCount: 170, keyCount: 1); diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs index 269b6688b8e..c6796a07ca0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs @@ -25,7 +25,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+PrincipalBase", typeof(CompiledModelTestBase.PrincipalBase), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalBase", derivedTypesCount: 1, propertyCount: 15, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index 2db1a4e4cee..385bed9b0c9 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -26,7 +26,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas sharedClrType: true, indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(typeof(Dictionary)), propertyBag: true, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalBasePrincipalDerived>", propertyCount: 8, foreignKeyCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalDerivedEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalDerivedEntityType.cs index 09d7c707205..a41a3ec75a5 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalDerivedEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalDerivedEntityType.cs @@ -20,7 +20,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+PrincipalDerived>", typeof(CompiledModelTestBase.PrincipalDerived>), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "PrincipalDerived", propertyCount: 0, navigationCount: 2, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs index 84ccae895e1..4450549e976 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs @@ -28,7 +28,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas "Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+DependentDerived", typeof(CompiledModelTestBase.DependentDerived), baseEntityType, - discriminatorProperty: "$type", + discriminatorProperty: "Discriminator", discriminatorValue: "DependentDerived", propertyCount: 5, keyCount: 1); diff --git a/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs b/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs index 91a680b2538..0a7b249397b 100644 --- a/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs +++ b/test/EFCore.Cosmos.Tests/Extensions/CosmosBuilderExtensionsTest.cs @@ -136,7 +136,7 @@ public void Default_discriminator_can_be_removed() var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; Assert.Equal("Discriminator", entityType.FindDiscriminatorProperty()!.Name); - Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); + Assert.Equal("Discriminator", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); Assert.Equal(nameof(Customer), entityType.GetDiscriminatorValue()); modelBuilder.Entity().HasNoDiscriminator(); @@ -147,7 +147,7 @@ public void Default_discriminator_can_be_removed() modelBuilder.Entity().HasBaseType(); Assert.Equal("Discriminator", entityType.FindDiscriminatorProperty()!.Name); - Assert.Equal("$type", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); + Assert.Equal("Discriminator", entityType.FindDiscriminatorProperty()!.GetJsonPropertyName()); Assert.Equal(nameof(Customer), entityType.GetDiscriminatorValue()); modelBuilder.Entity().HasBaseType((string)null); diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index 7387d9ce459..3726e535176 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -6517,7 +6517,6 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() AddBoilerPlate( """ modelBuilder - .HasEmbeddedDiscriminatorName("Terminator") .HasDefaultSchema("DefaultSchema") .HasAnnotation("Relational:MaxIdentifierLength", 128); @@ -6552,7 +6551,8 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot() b1.ComplexProperty(typeof(Dictionary), "EntityWithStringKey", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", b2 => { b2.Property("Discriminator") - .IsRequired(); + .IsRequired() + .HasJsonPropertyName("Terminator"); b2.Property("Id"); From 98eda42f44dbedde9dae29ad7291909a46707594 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 19:15:52 +0000 Subject: [PATCH 19/21] Update Cosmos compiled-model baselines Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../ComplexTypes/PrincipalBaseEntityType.cs | 20 ++--- .../PrincipalDerivedEntityType.cs | 6 +- .../IndexedDataEntityType.cs | 82 +++++++++---------- .../No_NativeAOT/ManyTypesEntityType.cs | 12 +-- .../No_NativeAOT/PrincipalBaseEntityType.cs | 4 +- ...cipalDerivedDependentBasebyteEntityType.cs | 4 +- .../SimpleModel/DependentDerivedEntityType.cs | 76 ++++++++--------- 7 files changed, 102 insertions(+), 102 deletions(-) diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs index 2b6ee66c55a..09f1742af1e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalBaseEntityType.cs @@ -92,23 +92,23 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas id.SetComparer(new NullableValueComparer(id.TypeMapping.Comparer)); id.SetKeyComparer(new NullableValueComparer(id.TypeMapping.KeyComparer)); - var type = runtimeEntityType.AddProperty( - "$type", + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", typeof(string), afterSaveBehavior: PropertySaveBehavior.Throw, valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); - type.SetAccessors( + discriminator.SetAccessors( string (IInternalEntry entry) => entry.ReadShadowValue(0), string (IInternalEntry entry) => entry.ReadShadowValue(0), - string (IInternalEntry entry) => entry.ReadOriginalValue(type, 1), - string (IInternalEntry entry) => entry.GetCurrentValue(type)); - type.SetPropertyIndexes( + string (IInternalEntry entry) => entry.ReadOriginalValue(discriminator, 1), + string (IInternalEntry entry) => entry.GetCurrentValue(discriminator)); + discriminator.SetPropertyIndexes( index: 1, originalValueIndex: 1, shadowIndex: 0, relationshipIndex: -1, storeGenerationIndex: -1); - type.TypeMapping = CosmosTypeMapping.Default.Clone( + discriminator.TypeMapping = CosmosTypeMapping.Default.Clone( comparer: new ValueComparer( bool (string v1, string v2) => v1 == v2, int (string v) => ((object)v).GetHashCode(), @@ -2445,7 +2445,7 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { var id = runtimeEntityType.FindProperty("Id"); - var type = runtimeEntityType.FindProperty("$type"); + var discriminator = runtimeEntityType.FindProperty("Discriminator"); var enum1 = runtimeEntityType.FindProperty("Enum1"); var enum2 = runtimeEntityType.FindProperty("Enum2"); var flagsEnum1 = runtimeEntityType.FindProperty("FlagsEnum1"); @@ -2491,7 +2491,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ISnapshot (IInternalEntry source) => { var structuralType = ((CompiledModelTestBase.PrincipalBase)(source.Entity)); - var liftedArg = ((ISnapshot)(new Snapshot, IList, DateTime[], IEnumerable, IList, List, string, JObject, string, int, IEnumerable, IList, DateTime[], IEnumerable, IList, List, object, Guid, CompiledModelTestBase.AnEnum, CompiledModelTestBase.AnEnum?, CompiledModelTestBase.AFlagsEnum, CompiledModelTestBase.AFlagsEnum, long?>((source.GetCurrentValue(id) == null ? null : ((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id))), (source.GetCurrentValue(type) == null ? null : ((ValueComparer)(((IProperty)type).GetValueComparer())).Snapshot(source.GetCurrentValue(type))), ((ValueComparer)(((IProperty)enum1).GetValueComparer())).Snapshot(source.GetCurrentValue(enum1)), (source.GetCurrentValue(enum2) == null ? null : ((ValueComparer)(((IProperty)enum2).GetValueComparer())).Snapshot(source.GetCurrentValue(enum2))), ((ValueComparer)(((IProperty)flagsEnum1).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum1)), ((ValueComparer)(((IProperty)flagsEnum2).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum2)), (source.GetCurrentValue(principalBaseId) == null ? null : ((ValueComparer)(((IProperty)principalBaseId).GetValueComparer())).Snapshot(source.GetCurrentValue(principalBaseId))), (((object)(source.GetCurrentValue>(refTypeEnumerable))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable))))))), (((object)(source.GetCurrentValue>(refTypeIList))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray))))))), (source.GetCurrentValue>(valueTypeEnumerable) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList))))))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject))), (source.GetCurrentValue(details) == null ? null : ((ValueComparer)(((IProperty)details).GetValueComparer())).Snapshot(source.GetCurrentValue(details))), ((ValueComparer)(((IProperty)number).GetValueComparer())).Snapshot(source.GetCurrentValue(number)), (((object)(source.GetCurrentValue>(refTypeEnumerable0))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable0))))))), (((object)(source.GetCurrentValue>(refTypeIList0))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray0))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray0))))))), (source.GetCurrentValue>(valueTypeEnumerable0) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable0).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable0))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList0))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList0))))))), ((object)(source.GetCurrentValue(principal))), ((ValueComparer)(((IProperty)alternateId).GetValueComparer())).Snapshot(source.GetCurrentValue(alternateId)), ((ValueComparer)(((IProperty)enum10).GetValueComparer())).Snapshot(source.GetCurrentValue(enum10)), (source.GetCurrentValue(enum20) == null ? null : ((ValueComparer)(((IProperty)enum20).GetValueComparer())).Snapshot(source.GetCurrentValue(enum20))), ((ValueComparer)(((IProperty)flagsEnum10).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum10)), ((ValueComparer)(((IProperty)flagsEnum20).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum20)), (source.GetCurrentValue(id0) == null ? null : ((ValueComparer)(((IProperty)id0).GetValueComparer())).Snapshot(source.GetCurrentValue(id0)))))); + var liftedArg = ((ISnapshot)(new Snapshot, IList, DateTime[], IEnumerable, IList, List, string, JObject, string, int, IEnumerable, IList, DateTime[], IEnumerable, IList, List, object, Guid, CompiledModelTestBase.AnEnum, CompiledModelTestBase.AnEnum?, CompiledModelTestBase.AFlagsEnum, CompiledModelTestBase.AFlagsEnum, long?>((source.GetCurrentValue(id) == null ? null : ((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id))), (source.GetCurrentValue(discriminator) == null ? null : ((ValueComparer)(((IProperty)discriminator).GetValueComparer())).Snapshot(source.GetCurrentValue(discriminator))), ((ValueComparer)(((IProperty)enum1).GetValueComparer())).Snapshot(source.GetCurrentValue(enum1)), (source.GetCurrentValue(enum2) == null ? null : ((ValueComparer)(((IProperty)enum2).GetValueComparer())).Snapshot(source.GetCurrentValue(enum2))), ((ValueComparer)(((IProperty)flagsEnum1).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum1)), ((ValueComparer)(((IProperty)flagsEnum2).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum2)), (source.GetCurrentValue(principalBaseId) == null ? null : ((ValueComparer)(((IProperty)principalBaseId).GetValueComparer())).Snapshot(source.GetCurrentValue(principalBaseId))), (((object)(source.GetCurrentValue>(refTypeEnumerable))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable))))))), (((object)(source.GetCurrentValue>(refTypeIList))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray))))))), (source.GetCurrentValue>(valueTypeEnumerable) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList))))))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject))), (source.GetCurrentValue(details) == null ? null : ((ValueComparer)(((IProperty)details).GetValueComparer())).Snapshot(source.GetCurrentValue(details))), ((ValueComparer)(((IProperty)number).GetValueComparer())).Snapshot(source.GetCurrentValue(number)), (((object)(source.GetCurrentValue>(refTypeEnumerable0))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable0))))))), (((object)(source.GetCurrentValue>(refTypeIList0))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray0))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray0))))))), (source.GetCurrentValue>(valueTypeEnumerable0) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable0).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable0))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList0))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList0))))))), ((object)(source.GetCurrentValue(principal))), ((ValueComparer)(((IProperty)alternateId).GetValueComparer())).Snapshot(source.GetCurrentValue(alternateId)), ((ValueComparer)(((IProperty)enum10).GetValueComparer())).Snapshot(source.GetCurrentValue(enum10)), (source.GetCurrentValue(enum20) == null ? null : ((ValueComparer)(((IProperty)enum20).GetValueComparer())).Snapshot(source.GetCurrentValue(enum20))), ((ValueComparer)(((IProperty)flagsEnum10).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum10)), ((ValueComparer)(((IProperty)flagsEnum20).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum20)), (source.GetCurrentValue(id0) == null ? null : ((ValueComparer)(((IProperty)id0).GetValueComparer())).Snapshot(source.GetCurrentValue(id0)))))); var structuralType0 = ((CompiledModelTestBase.PrincipalBase)(source.Entity)); return ((ISnapshot)(new MultiSnapshot(new ISnapshot[] { liftedArg, ((ISnapshot)(new Snapshot, IList, DateTime[], IEnumerable, IList, List>((((object)(source.GetCurrentValue>(refTypeEnumerable1))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable1).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable1))))))), (((object)(source.GetCurrentValue>(refTypeIList1))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList1).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList1))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray1))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray1).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray1))))))), (source.GetCurrentValue>(valueTypeEnumerable1) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable1).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable1))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList1))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList1).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList1))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList1))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList1).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList1)))))))))) }))); }); @@ -2500,7 +2500,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) runtimeEntityType.SetTemporaryValuesFactory( ISnapshot (IInternalEntry source) => ((ISnapshot)(new Snapshot(default(long? ))))); runtimeEntityType.SetShadowValuesFactory( - ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("$type") ? ((string)(source["$type"])) : null), (source.ContainsKey("PrincipalBaseId") ? ((long? )(source["PrincipalBaseId"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); + ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("Discriminator") ? ((string)(source["Discriminator"])) : null), (source.ContainsKey("PrincipalBaseId") ? ((long? )(source["PrincipalBaseId"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); runtimeEntityType.SetEmptyShadowValuesFactory( ISnapshot () => ((ISnapshot)(new Snapshot(default(string), default(long? ), default(string), default(JObject))))); runtimeEntityType.SetRelationshipSnapshotFactory( diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs index 8645018f1ea..d1a3d06a914 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/ComplexTypes/PrincipalDerivedEntityType.cs @@ -1927,7 +1927,7 @@ public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { var id = runtimeEntityType.FindProperty("Id"); - var type = runtimeEntityType.FindProperty("$type"); + var discriminator = runtimeEntityType.FindProperty("Discriminator"); var enum1 = runtimeEntityType.FindProperty("Enum1"); var enum2 = runtimeEntityType.FindProperty("Enum2"); var flagsEnum1 = runtimeEntityType.FindProperty("FlagsEnum1"); @@ -1997,7 +1997,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ISnapshot (IInternalEntry source) => { var structuralType1 = ((CompiledModelTestBase.PrincipalDerived>)(source.Entity)); - var liftedArg0 = ((ISnapshot)(new Snapshot, IList, DateTime[], IEnumerable, IList, List, string, JObject, string, int, IEnumerable, IList, DateTime[], IEnumerable, IList, List, object, Guid, CompiledModelTestBase.AnEnum, CompiledModelTestBase.AnEnum?, CompiledModelTestBase.AFlagsEnum, CompiledModelTestBase.AFlagsEnum, long?>((source.GetCurrentValue(id) == null ? null : ((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id))), (source.GetCurrentValue(type) == null ? null : ((ValueComparer)(((IProperty)type).GetValueComparer())).Snapshot(source.GetCurrentValue(type))), ((ValueComparer)(((IProperty)enum1).GetValueComparer())).Snapshot(source.GetCurrentValue(enum1)), (source.GetCurrentValue(enum2) == null ? null : ((ValueComparer)(((IProperty)enum2).GetValueComparer())).Snapshot(source.GetCurrentValue(enum2))), ((ValueComparer)(((IProperty)flagsEnum1).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum1)), ((ValueComparer)(((IProperty)flagsEnum2).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum2)), (source.GetCurrentValue(principalBaseId) == null ? null : ((ValueComparer)(((IProperty)principalBaseId).GetValueComparer())).Snapshot(source.GetCurrentValue(principalBaseId))), (((object)(source.GetCurrentValue>(refTypeEnumerable))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable))))))), (((object)(source.GetCurrentValue>(refTypeIList))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray))))))), (source.GetCurrentValue>(valueTypeEnumerable) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList))))))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject))), (source.GetCurrentValue(details) == null ? null : ((ValueComparer)(((IProperty)details).GetValueComparer())).Snapshot(source.GetCurrentValue(details))), ((ValueComparer)(((IProperty)number).GetValueComparer())).Snapshot(source.GetCurrentValue(number)), (((object)(source.GetCurrentValue>(refTypeEnumerable0))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable0))))))), (((object)(source.GetCurrentValue>(refTypeIList0))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray0))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray0))))))), (source.GetCurrentValue>(valueTypeEnumerable0) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable0).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable0))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList0))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList0))))))), ((object)(source.GetCurrentValue(principal))), ((ValueComparer)(((IProperty)alternateId).GetValueComparer())).Snapshot(source.GetCurrentValue(alternateId)), ((ValueComparer)(((IProperty)enum10).GetValueComparer())).Snapshot(source.GetCurrentValue(enum10)), (source.GetCurrentValue(enum20) == null ? null : ((ValueComparer)(((IProperty)enum20).GetValueComparer())).Snapshot(source.GetCurrentValue(enum20))), ((ValueComparer)(((IProperty)flagsEnum10).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum10)), ((ValueComparer)(((IProperty)flagsEnum20).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum20)), (source.GetCurrentValue(id0) == null ? null : ((ValueComparer)(((IProperty)id0).GetValueComparer())).Snapshot(source.GetCurrentValue(id0)))))); + var liftedArg0 = ((ISnapshot)(new Snapshot, IList, DateTime[], IEnumerable, IList, List, string, JObject, string, int, IEnumerable, IList, DateTime[], IEnumerable, IList, List, object, Guid, CompiledModelTestBase.AnEnum, CompiledModelTestBase.AnEnum?, CompiledModelTestBase.AFlagsEnum, CompiledModelTestBase.AFlagsEnum, long?>((source.GetCurrentValue(id) == null ? null : ((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id))), (source.GetCurrentValue(discriminator) == null ? null : ((ValueComparer)(((IProperty)discriminator).GetValueComparer())).Snapshot(source.GetCurrentValue(discriminator))), ((ValueComparer)(((IProperty)enum1).GetValueComparer())).Snapshot(source.GetCurrentValue(enum1)), (source.GetCurrentValue(enum2) == null ? null : ((ValueComparer)(((IProperty)enum2).GetValueComparer())).Snapshot(source.GetCurrentValue(enum2))), ((ValueComparer)(((IProperty)flagsEnum1).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum1)), ((ValueComparer)(((IProperty)flagsEnum2).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum2)), (source.GetCurrentValue(principalBaseId) == null ? null : ((ValueComparer)(((IProperty)principalBaseId).GetValueComparer())).Snapshot(source.GetCurrentValue(principalBaseId))), (((object)(source.GetCurrentValue>(refTypeEnumerable))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable))))))), (((object)(source.GetCurrentValue>(refTypeIList))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray))))))), (source.GetCurrentValue>(valueTypeEnumerable) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList))))))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject))), (source.GetCurrentValue(details) == null ? null : ((ValueComparer)(((IProperty)details).GetValueComparer())).Snapshot(source.GetCurrentValue(details))), ((ValueComparer)(((IProperty)number).GetValueComparer())).Snapshot(source.GetCurrentValue(number)), (((object)(source.GetCurrentValue>(refTypeEnumerable0))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable0))))))), (((object)(source.GetCurrentValue>(refTypeIList0))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList0).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray0))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray0))))))), (source.GetCurrentValue>(valueTypeEnumerable0) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable0).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable0))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList0))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList0))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList0).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList0))))))), ((object)(source.GetCurrentValue(principal))), ((ValueComparer)(((IProperty)alternateId).GetValueComparer())).Snapshot(source.GetCurrentValue(alternateId)), ((ValueComparer)(((IProperty)enum10).GetValueComparer())).Snapshot(source.GetCurrentValue(enum10)), (source.GetCurrentValue(enum20) == null ? null : ((ValueComparer)(((IProperty)enum20).GetValueComparer())).Snapshot(source.GetCurrentValue(enum20))), ((ValueComparer)(((IProperty)flagsEnum10).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum10)), ((ValueComparer)(((IProperty)flagsEnum20).GetValueComparer())).Snapshot(source.GetCurrentValue(flagsEnum20)), (source.GetCurrentValue(id0) == null ? null : ((ValueComparer)(((IProperty)id0).GetValueComparer())).Snapshot(source.GetCurrentValue(id0)))))); var structuralType2 = ((CompiledModelTestBase.PrincipalDerived>)(source.Entity)); return ((ISnapshot)(new MultiSnapshot(new ISnapshot[] { liftedArg0, ((ISnapshot)(new Snapshot, IList, DateTime[], IEnumerable, IList, List, object, byte?, object>((((object)(source.GetCurrentValue>(refTypeEnumerable1))) == null ? null : ((IEnumerable)(((ValueComparer)(((IProperty)refTypeEnumerable1).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeEnumerable1))))))), (((object)(source.GetCurrentValue>(refTypeIList1))) == null ? null : ((IList)(((ValueComparer)(((IProperty)refTypeIList1).GetValueComparer())).Snapshot(((object)(source.GetCurrentValue>(refTypeIList1))))))), (((IEnumerable)(source.GetCurrentValue(valueTypeArray1))) == null ? null : ((DateTime[])(((ValueComparer>)(((IProperty)valueTypeArray1).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue(valueTypeArray1))))))), (source.GetCurrentValue>(valueTypeEnumerable1) == null ? null : ((ValueComparer>)(((IProperty)valueTypeEnumerable1).GetValueComparer())).Snapshot(source.GetCurrentValue>(valueTypeEnumerable1))), (((IEnumerable)(source.GetCurrentValue>(valueTypeIList1))) == null ? null : ((IList)(((ValueComparer>)(((IProperty)valueTypeIList1).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeIList1))))))), (((IEnumerable)(source.GetCurrentValue>(valueTypeList1))) == null ? null : ((List)(((ValueComparer>)(((IProperty)valueTypeList1).GetValueComparer())).Snapshot(((IEnumerable)(source.GetCurrentValue>(valueTypeList1))))))), ((object)(source.GetCurrentValue>(dependent))), (source.GetCurrentValue(id1) == null ? null : ((ValueComparer)(((IProperty)id1).GetValueComparer())).Snapshot(source.GetCurrentValue(id1))), SnapshotFactoryFactory.SnapshotComplexCollection(((IList)(source.GetCurrentValue>(manyOwned))), manyOwned)))) }))); }); @@ -2006,7 +2006,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) runtimeEntityType.SetTemporaryValuesFactory( ISnapshot (IInternalEntry source) => ((ISnapshot)(new Snapshot(default(long? ))))); runtimeEntityType.SetShadowValuesFactory( - ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("$type") ? ((string)(source["$type"])) : null), (source.ContainsKey("PrincipalBaseId") ? ((long? )(source["PrincipalBaseId"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); + ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("Discriminator") ? ((string)(source["Discriminator"])) : null), (source.ContainsKey("PrincipalBaseId") ? ((long? )(source["PrincipalBaseId"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); runtimeEntityType.SetEmptyShadowValuesFactory( ISnapshot () => ((ISnapshot)(new Snapshot(default(string), default(long? ), default(string), default(JObject))))); runtimeEntityType.SetRelationshipSnapshotFactory( diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs index 9a549751d78..5a238d83d3c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Cosmos_model_with_index_types/IndexedDataEntityType.cs @@ -137,38 +137,6 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas jsonValueReaderWriter: JsonStringReaderWriter.Instance); partitionId.SetCurrentValueComparer(new EntryCurrentValueComparer(partitionId)); - var type = runtimeEntityType.AddProperty( - "$type", - typeof(string), - afterSaveBehavior: PropertySaveBehavior.Throw, - valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); - type.SetAccessors( - string (IInternalEntry entry) => entry.ReadShadowValue(0), - string (IInternalEntry entry) => entry.ReadShadowValue(0), - string (IInternalEntry entry) => entry.ReadOriginalValue(type, 2), - string (IInternalEntry entry) => entry.GetCurrentValue(type)); - type.SetPropertyIndexes( - index: 2, - originalValueIndex: 2, - shadowIndex: 0, - relationshipIndex: -1, - storeGenerationIndex: -1); - type.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v), - keyComparer: new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v), - providerValueComparer: new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v), - clrType: typeof(string), - jsonValueReaderWriter: JsonStringReaderWriter.Instance); - var category = runtimeEntityType.AddProperty( "Category", typeof(string), @@ -192,11 +160,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas category.SetAccessors( string (IInternalEntry entry) => IndexedDataUnsafeAccessors.Category(((CompiledModelCosmosTest.IndexedData)(entry.Entity))), string (IInternalEntry entry) => IndexedDataUnsafeAccessors.Category(((CompiledModelCosmosTest.IndexedData)(entry.Entity))), - string (IInternalEntry entry) => entry.ReadOriginalValue(category, 3), + string (IInternalEntry entry) => entry.ReadOriginalValue(category, 2), string (IInternalEntry entry) => entry.GetCurrentValue(category)); category.SetPropertyIndexes( - index: 3, - originalValueIndex: 3, + index: 2, + originalValueIndex: 2, shadowIndex: -1, relationshipIndex: -1, storeGenerationIndex: -1); @@ -239,11 +207,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas description.SetAccessors( string (IInternalEntry entry) => IndexedDataUnsafeAccessors.Description(((CompiledModelCosmosTest.IndexedData)(entry.Entity))), string (IInternalEntry entry) => IndexedDataUnsafeAccessors.Description(((CompiledModelCosmosTest.IndexedData)(entry.Entity))), - string (IInternalEntry entry) => entry.ReadOriginalValue(description, 4), + string (IInternalEntry entry) => entry.ReadOriginalValue(description, 3), string (IInternalEntry entry) => entry.GetCurrentValue(description)); description.SetPropertyIndexes( - index: 4, - originalValueIndex: 4, + index: 3, + originalValueIndex: 3, shadowIndex: -1, relationshipIndex: -1, storeGenerationIndex: -1); @@ -265,6 +233,38 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas description.AddAnnotation("Cosmos:FullTextSearchLanguage", null); description.AddAnnotation("Cosmos:IsFullTextSearchEnabled", true); + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + discriminator.SetAccessors( + string (IInternalEntry entry) => entry.ReadShadowValue(0), + string (IInternalEntry entry) => entry.ReadShadowValue(0), + string (IInternalEntry entry) => entry.ReadOriginalValue(discriminator, 4), + string (IInternalEntry entry) => entry.GetCurrentValue(discriminator)); + discriminator.SetPropertyIndexes( + index: 4, + originalValueIndex: 4, + shadowIndex: 0, + relationshipIndex: -1, + storeGenerationIndex: -1); + discriminator.TypeMapping = CosmosTypeMapping.Default.Clone( + comparer: new ValueComparer( + bool (string v1, string v2) => v1 == v2, + int (string v) => ((object)v).GetHashCode(), + string (string v) => v), + keyComparer: new ValueComparer( + bool (string v1, string v2) => v1 == v2, + int (string v) => ((object)v).GetHashCode(), + string (string v) => v), + providerValueComparer: new ValueComparer( + bool (string v1, string v2) => v1 == v2, + int (string v) => ((object)v).GetHashCode(), + string (string v) => v), + clrType: typeof(string), + jsonValueReaderWriter: JsonStringReaderWriter.Instance); + var embedding = runtimeEntityType.AddProperty( "Embedding", typeof(ReadOnlyMemory), @@ -501,9 +501,9 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { var id = runtimeEntityType.FindProperty("Id"); var partitionId = runtimeEntityType.FindProperty("PartitionId"); - var type = runtimeEntityType.FindProperty("$type"); var category = runtimeEntityType.FindProperty("Category"); var description = runtimeEntityType.FindProperty("Description"); + var discriminator = runtimeEntityType.FindProperty("Discriminator"); var embedding = runtimeEntityType.FindProperty("Embedding"); var notes = runtimeEntityType.FindProperty("Notes"); var region = runtimeEntityType.FindProperty("Region"); @@ -516,14 +516,14 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ISnapshot (IInternalEntry source) => { var structuralType = ((CompiledModelCosmosTest.IndexedData)(source.Entity)); - return ((ISnapshot)(new Snapshot, string, string, string, JObject>(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id)), (source.GetCurrentValue(partitionId) == null ? null : ((ValueComparer)(((IProperty)partitionId).GetValueComparer())).Snapshot(source.GetCurrentValue(partitionId))), (source.GetCurrentValue(type) == null ? null : ((ValueComparer)(((IProperty)type).GetValueComparer())).Snapshot(source.GetCurrentValue(type))), (source.GetCurrentValue(category) == null ? null : ((ValueComparer)(((IProperty)category).GetValueComparer())).Snapshot(source.GetCurrentValue(category))), (source.GetCurrentValue(description) == null ? null : ((ValueComparer)(((IProperty)description).GetValueComparer())).Snapshot(source.GetCurrentValue(description))), ((ValueComparer>)(((IProperty)embedding).GetValueComparer())).Snapshot(source.GetCurrentValue>(embedding)), (source.GetCurrentValue(notes) == null ? null : ((ValueComparer)(((IProperty)notes).GetValueComparer())).Snapshot(source.GetCurrentValue(notes))), (source.GetCurrentValue(region) == null ? null : ((ValueComparer)(((IProperty)region).GetValueComparer())).Snapshot(source.GetCurrentValue(region))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject)))))); + return ((ISnapshot)(new Snapshot, string, string, string, JObject>(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id)), (source.GetCurrentValue(partitionId) == null ? null : ((ValueComparer)(((IProperty)partitionId).GetValueComparer())).Snapshot(source.GetCurrentValue(partitionId))), (source.GetCurrentValue(category) == null ? null : ((ValueComparer)(((IProperty)category).GetValueComparer())).Snapshot(source.GetCurrentValue(category))), (source.GetCurrentValue(description) == null ? null : ((ValueComparer)(((IProperty)description).GetValueComparer())).Snapshot(source.GetCurrentValue(description))), (source.GetCurrentValue(discriminator) == null ? null : ((ValueComparer)(((IProperty)discriminator).GetValueComparer())).Snapshot(source.GetCurrentValue(discriminator))), ((ValueComparer>)(((IProperty)embedding).GetValueComparer())).Snapshot(source.GetCurrentValue>(embedding)), (source.GetCurrentValue(notes) == null ? null : ((ValueComparer)(((IProperty)notes).GetValueComparer())).Snapshot(source.GetCurrentValue(notes))), (source.GetCurrentValue(region) == null ? null : ((ValueComparer)(((IProperty)region).GetValueComparer())).Snapshot(source.GetCurrentValue(region))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject)))))); }); runtimeEntityType.SetStoreGeneratedValuesFactory( ISnapshot () => ((ISnapshot)(new Snapshot((default(JObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(default(JObject))))))); runtimeEntityType.SetTemporaryValuesFactory( ISnapshot (IInternalEntry source) => ((ISnapshot)(new Snapshot(default(JObject))))); runtimeEntityType.SetShadowValuesFactory( - ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("$type") ? ((string)(source["$type"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); + ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("Discriminator") ? ((string)(source["Discriminator"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); runtimeEntityType.SetEmptyShadowValuesFactory( ISnapshot () => ((ISnapshot)(new Snapshot(default(string), default(string), default(JObject))))); runtimeEntityType.SetRelationshipSnapshotFactory( diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs index f72adba1643..43ac0135f2c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs @@ -44,12 +44,6 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas valueConverter: new CompiledModelTestBase.ManyTypesIdConverter()); id.SetSentinelFromProviderValue(0); - var type = runtimeEntityType.AddProperty( - "$type", - typeof(string), - afterSaveBehavior: PropertySaveBehavior.Throw, - valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); - var @bool = runtimeEntityType.AddProperty( "Bool", typeof(bool), @@ -268,6 +262,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas valueConverter: new NumberToStringConverter()); decimalNumberToStringConverterProperty.SetSentinelFromProviderValue("0"); + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + var @double = runtimeEntityType.AddProperty( "Double", typeof(double), diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs index c6796a07ca0..885d049f5ff 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBaseEntityType.cs @@ -49,8 +49,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas jsonValueReaderWriter: JsonGuidReaderWriter.Instance); alternateId.SetSentinelFromProviderValue("00000000-0000-0000-0000-000000000000"); - var type = runtimeEntityType.AddProperty( - "$type", + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", typeof(string), afterSaveBehavior: PropertySaveBehavior.Throw, valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs index 385bed9b0c9..9dec95ad330 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/PrincipalBasePrincipalDerivedDependentBasebyteEntityType.cs @@ -56,8 +56,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), afterSaveBehavior: PropertySaveBehavior.Throw); - var type = runtimeEntityType.AddProperty( - "$type", + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", typeof(string), propertyInfo: runtimeEntityType.FindIndexerPropertyInfo(), afterSaveBehavior: PropertySaveBehavior.Throw, diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs index 4450549e976..176923feb5b 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/SimpleModel/DependentDerivedEntityType.cs @@ -83,38 +83,6 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas jsonValueReaderWriter: JsonInt32ReaderWriter.Instance); id.SetCurrentValueComparer(new EntryCurrentValueComparer(id)); - var type = runtimeEntityType.AddProperty( - "$type", - typeof(string), - afterSaveBehavior: PropertySaveBehavior.Throw, - valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); - type.SetAccessors( - string (IInternalEntry entry) => entry.ReadShadowValue(0), - string (IInternalEntry entry) => entry.ReadShadowValue(0), - string (IInternalEntry entry) => entry.ReadOriginalValue(type, 1), - string (IInternalEntry entry) => entry.GetCurrentValue(type)); - type.SetPropertyIndexes( - index: 1, - originalValueIndex: 1, - shadowIndex: 0, - relationshipIndex: -1, - storeGenerationIndex: -1); - type.TypeMapping = CosmosTypeMapping.Default.Clone( - comparer: new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v), - keyComparer: new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v), - providerValueComparer: new ValueComparer( - bool (string v1, string v2) => v1 == v2, - int (string v) => ((object)v).GetHashCode(), - string (string v) => v), - clrType: typeof(string), - jsonValueReaderWriter: JsonStringReaderWriter.Instance); - var data = runtimeEntityType.AddProperty( "Data", typeof(string), @@ -139,11 +107,11 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas data.SetAccessors( string (IInternalEntry entry) => DependentDerivedUnsafeAccessors.Data(((CompiledModelTestBase.DependentDerived)(entry.Entity))), string (IInternalEntry entry) => DependentDerivedUnsafeAccessors.Data(((CompiledModelTestBase.DependentDerived)(entry.Entity))), - string (IInternalEntry entry) => entry.ReadOriginalValue(data, 2), + string (IInternalEntry entry) => entry.ReadOriginalValue(data, 1), string (IInternalEntry entry) => entry.GetCurrentValue(data)); data.SetPropertyIndexes( - index: 2, - originalValueIndex: 2, + index: 1, + originalValueIndex: 1, shadowIndex: -1, relationshipIndex: -1, storeGenerationIndex: -1); @@ -163,6 +131,38 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas clrType: typeof(string), jsonValueReaderWriter: JsonStringReaderWriter.Instance); + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + discriminator.SetAccessors( + string (IInternalEntry entry) => entry.ReadShadowValue(0), + string (IInternalEntry entry) => entry.ReadShadowValue(0), + string (IInternalEntry entry) => entry.ReadOriginalValue(discriminator, 2), + string (IInternalEntry entry) => entry.GetCurrentValue(discriminator)); + discriminator.SetPropertyIndexes( + index: 2, + originalValueIndex: 2, + shadowIndex: 0, + relationshipIndex: -1, + storeGenerationIndex: -1); + discriminator.TypeMapping = CosmosTypeMapping.Default.Clone( + comparer: new ValueComparer( + bool (string v1, string v2) => v1 == v2, + int (string v) => ((object)v).GetHashCode(), + string (string v) => v), + keyComparer: new ValueComparer( + bool (string v1, string v2) => v1 == v2, + int (string v) => ((object)v).GetHashCode(), + string (string v) => v), + providerValueComparer: new ValueComparer( + bool (string v1, string v2) => v1 == v2, + int (string v) => ((object)v).GetHashCode(), + string (string v) => v), + clrType: typeof(string), + jsonValueReaderWriter: JsonStringReaderWriter.Instance); + var __id = runtimeEntityType.AddProperty( "__id", typeof(string), @@ -240,8 +240,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { var id = runtimeEntityType.FindProperty("Id"); - var type = runtimeEntityType.FindProperty("$type"); var data = runtimeEntityType.FindProperty("Data"); + var discriminator = runtimeEntityType.FindProperty("Discriminator"); var __id = runtimeEntityType.FindProperty("__id"); var __jObject = runtimeEntityType.FindProperty("__jObject"); var key = runtimeEntityType.FindKey(new[] { id }); @@ -251,14 +251,14 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ISnapshot (IInternalEntry source) => { var structuralType = ((CompiledModelTestBase.DependentDerived)(source.Entity)); - return ((ISnapshot)(new Snapshot(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id)), (source.GetCurrentValue(type) == null ? null : ((ValueComparer)(((IProperty)type).GetValueComparer())).Snapshot(source.GetCurrentValue(type))), (source.GetCurrentValue(data) == null ? null : ((ValueComparer)(((IProperty)data).GetValueComparer())).Snapshot(source.GetCurrentValue(data))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject)))))); + return ((ISnapshot)(new Snapshot(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id)), (source.GetCurrentValue(data) == null ? null : ((ValueComparer)(((IProperty)data).GetValueComparer())).Snapshot(source.GetCurrentValue(data))), (source.GetCurrentValue(discriminator) == null ? null : ((ValueComparer)(((IProperty)discriminator).GetValueComparer())).Snapshot(source.GetCurrentValue(discriminator))), (source.GetCurrentValue(__id) == null ? null : ((ValueComparer)(((IProperty)__id).GetValueComparer())).Snapshot(source.GetCurrentValue(__id))), (source.GetCurrentValue(__jObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(source.GetCurrentValue(__jObject)))))); }); runtimeEntityType.SetStoreGeneratedValuesFactory( ISnapshot () => ((ISnapshot)(new Snapshot((default(JObject) == null ? null : ((ValueComparer)(((IProperty)__jObject).GetValueComparer())).Snapshot(default(JObject))))))); runtimeEntityType.SetTemporaryValuesFactory( ISnapshot (IInternalEntry source) => ((ISnapshot)(new Snapshot(default(JObject))))); runtimeEntityType.SetShadowValuesFactory( - ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("$type") ? ((string)(source["$type"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); + ISnapshot (IDictionary source) => ((ISnapshot)(new Snapshot((source.ContainsKey("Discriminator") ? ((string)(source["Discriminator"])) : null), (source.ContainsKey("__id") ? ((string)(source["__id"])) : null), (source.ContainsKey("__jObject") ? ((JObject)(source["__jObject"])) : null))))); runtimeEntityType.SetEmptyShadowValuesFactory( ISnapshot () => ((ISnapshot)(new Snapshot(default(string), default(string), default(JObject))))); runtimeEntityType.SetRelationshipSnapshotFactory( From 2a5cbf275514d78cb8cde485099a036690e84e2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 19:20:20 +0000 Subject: [PATCH 20/21] Avoid per-character identifier allocations Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore.Design/Design/Internal/CSharpHelper.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 100d32ccbab..d5651aa8333 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -297,17 +297,17 @@ private static string Identifier(string name, bool? capitalize) builder.Append(name[partStart..]); } - if (!ModelValidator.IsValidIdentifier(builder.ToString())) - { - builder.Insert(0, '_'); - } - if (capitalize != null) { ChangeFirstLetterCase(builder, capitalize.Value); } var identifier = builder.ToString(); + if (!ModelValidator.IsValidIdentifier(identifier)) + { + identifier = "_" + identifier; + } + return identifier; } @@ -1632,8 +1632,8 @@ public virtual string Expression( } private static bool IsIdentifierStartCharacter(char ch) - => ModelValidator.IsValidIdentifier(ch.ToString()); + => char.IsLetter(ch) || ch == '_'; private static bool IsIdentifierPartCharacter(char ch) - => ModelValidator.IsValidIdentifier($"_{ch}"); + => char.IsLetter(ch) || char.IsAsciiDigit(ch) || ch == '_'; } From 86de7a0643a127c3cea5a1ca2d56a029ed722a27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 19:24:50 +0000 Subject: [PATCH 21/21] Clarify final identifier validation Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore.Design/Design/Internal/CSharpHelper.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index d5651aa8333..11fabbefa39 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -302,13 +302,11 @@ private static string Identifier(string name, bool? capitalize) ChangeFirstLetterCase(builder, capitalize.Value); } - var identifier = builder.ToString(); - if (!ModelValidator.IsValidIdentifier(identifier)) - { - identifier = "_" + identifier; - } + var candidateIdentifier = builder.ToString(); - return identifier; + return ModelValidator.IsValidIdentifier(candidateIdentifier) + ? candidateIdentifier + : "_" + candidateIdentifier; } private static void ChangeFirstLetterCase(StringBuilder builder, bool capitalize)