diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs
index 2990718e5c0..22bb65d622e 100644
--- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs
+++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosDiscriminatorConvention.cs
@@ -83,11 +83,10 @@ private static void ProcessEntityType(IConventionEntityTypeBuilder entityTypeBui
{
return;
}
-
if (entityType.IsDocumentRoot())
{
- entityTypeBuilder.HasDiscriminator(entityType.Model.GetEmbeddedDiscriminatorName(), typeof(string))
- ?.HasValue(entityType, entityType.ShortName());
+ var discriminator = HasDiscriminator(entityTypeBuilder);
+ discriminator?.HasValue(entityType, entityType.ShortName());
}
else
{
@@ -125,7 +124,7 @@ public override void ProcessEntityTypeBaseTypeChanged(
{
if (entityType.IsDocumentRoot())
{
- entityTypeBuilder.HasDiscriminator(entityType.Model.GetEmbeddedDiscriminatorName(), typeof(string));
+ HasDiscriminator(entityTypeBuilder);
}
}
else
@@ -137,7 +136,7 @@ public override void ProcessEntityTypeBaseTypeChanged(
return;
}
- var discriminator = rootType.Builder.HasDiscriminator(entityType.Model.GetEmbeddedDiscriminatorName(), typeof(string));
+ var discriminator = HasDiscriminator(rootType.Builder);
if (discriminator != null)
{
SetDefaultDiscriminatorValues(entityTypeBuilder.Metadata.GetDerivedTypesInclusive(), discriminator);
@@ -145,6 +144,20 @@ public override void ProcessEntityTypeBaseTypeChanged(
}
}
+ private static IConventionDiscriminatorBuilder? HasDiscriminator(IConventionEntityTypeBuilder entityTypeBuilder)
+ {
+ var discriminator = entityTypeBuilder.HasDiscriminator(typeof(string));
+ var discriminatorProperty = discriminator?.EntityType.FindDiscriminatorProperty();
+ if (discriminatorProperty != null)
+ {
+ CosmosPropertyBuilderExtensions.ToJsonProperty(
+ discriminatorProperty.Builder,
+ (string?)entityTypeBuilder.Metadata.Model.FindAnnotation("EmbeddedDiscriminatorName")?.Value);
+ }
+
+ return discriminator;
+ }
+
///
protected override void SetDefaultDiscriminatorValues(
IEnumerable entityTypes,
diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs
index c62d8194854..11fabbefa39 100644
--- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs
+++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs
@@ -297,19 +297,16 @@ private static string Identifier(string name, bool? capitalize)
builder.Append(name[partStart..]);
}
- if (builder.Length == 0
- || !IsIdentifierStartCharacter(builder[0]))
- {
- builder.Insert(0, '_');
- }
-
if (capitalize != null)
{
ChangeFirstLetterCase(builder, capitalize.Value);
}
- var identifier = builder.ToString();
- return identifier;
+ var candidateIdentifier = builder.ToString();
+
+ return ModelValidator.IsValidIdentifier(candidateIdentifier)
+ ? candidateIdentifier
+ : "_" + candidateIdentifier;
}
private static void ChangeFirstLetterCase(StringBuilder builder, bool capitalize)
@@ -1633,72 +1630,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 == '_';
}
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/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
index caa17d220d1..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()
- ? "$type"
+ ? (string?)property.DeclaringType.Model.FindAnnotation("EmbeddedDiscriminatorName")?.Value
: property.Name);
///
diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs
index e68acbf39c9..8ec0fc0f7fb 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 identifier, which can cause issues in generated code.
+ ///
+ ///
+ ///
+ /// 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