Skip to content

Commit

Permalink
EF code updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ajcvickers committed Aug 12, 2024
1 parent b3e6e4b commit b35704a
Show file tree
Hide file tree
Showing 30 changed files with 600 additions and 156 deletions.
12 changes: 12 additions & 0 deletions src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,18 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
#pragma warning restore CS0618
}

if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
&& containerColumnTypeAnnotation is { Value: string containerColumnType }
&& entityType.IsOwned())
{
methodCallCodeFragments.Add(
new MethodCallCodeFragment(
nameof(RelationalOwnedNavigationBuilderExtensions.HasColumnType),
containerColumnType));

annotations.Remove(RelationalAnnotationNames.ContainerColumnType);
}

methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi));

return methodCallCodeFragments;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static OwnedNavigationBuilder<TOwnerEntity, TDependentEntity> ToJson<TOwn
string? jsonColumnName)
where TOwnerEntity : class
where TDependentEntity : class
=> builder.ToJson(jsonColumnName, null);
=> (OwnedNavigationBuilder<TOwnerEntity, TDependentEntity>)((OwnedNavigationBuilder)builder).ToJson(jsonColumnName);

/// <summary>
/// Configures a relationship where this entity type and the entities that it owns are mapped to a JSON column in the database.
Expand All @@ -74,47 +74,42 @@ public static OwnedNavigationBuilder<TOwnerEntity, TDependentEntity> ToJson<TOwn
public static OwnedNavigationBuilder ToJson(
this OwnedNavigationBuilder builder,
string? jsonColumnName)
=> builder.ToJson(jsonColumnName, null);
{
builder.OwnedEntityType.SetContainerColumnName(jsonColumnName);

return builder;
}

/// <summary>
/// Configures a relationship where this entity type and the entities that it owns are mapped to a JSON column in the database.
/// Set the relational database column type to be used to store the document represented by this owned entity.
/// </summary>
/// <remarks>
/// This method should only be specified for the outer-most owned entity in the given ownership structure.
/// All entities owned by this will be automatically mapped to the same JSON column.
/// The ownerships must still be explicitly defined.
/// This method should only be specified for the outer-most owned entity in the given ownership structure and
/// only when mapping the column to a database document type.
/// </remarks>
/// <param name="builder">The builder for the owned navigation being configured.</param>
/// <param name="jsonColumnName">JSON column name to use.</param>
/// <param name="jsonColumnType">The database type for the JSON column, or <see langword="null"/> to use the database default.</param>
/// <param name="columnType">The database type for the column, or <see langword="null"/> to use the database default.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static OwnedNavigationBuilder<TOwnerEntity, TDependentEntity> ToJson<TOwnerEntity, TDependentEntity>(
public static OwnedNavigationBuilder<TOwnerEntity, TDependentEntity> HasColumnType<TOwnerEntity, TDependentEntity>(
this OwnedNavigationBuilder<TOwnerEntity, TDependentEntity> builder,
string? jsonColumnName,
string? jsonColumnType)
string? columnType)
where TOwnerEntity : class
where TDependentEntity : class
=> (OwnedNavigationBuilder<TOwnerEntity, TDependentEntity>)((OwnedNavigationBuilder)builder).ToJson(jsonColumnName, jsonColumnType);
=> (OwnedNavigationBuilder<TOwnerEntity, TDependentEntity>)((OwnedNavigationBuilder)builder).HasColumnType(columnType);

/// <summary>
/// Configures a relationship where this entity type and the entities that it owns are mapped to a JSON column in the database.
/// Set the relational database column type to be used to store the document represented by this owned entity.
/// </summary>
/// <remarks>
/// This method should only be specified for the outer-most owned entity in the given ownership structure.
/// All entities owned by this will be automatically mapped to the same JSON column.
/// The ownerships must still be explicitly defined.
/// This method should only be specified for the outer-most owned entity in the given ownership structure and
/// only when mapping the column to a database document type.
/// </remarks>
/// <param name="builder">The builder for the owned navigation being configured.</param>
/// <param name="jsonColumnName">JSON column name to use.</param>
/// <param name="jsonColumnType">The database type for the JSON column, or <see langword="null"/> to use the database default.</param>
/// <param name="columnType">The database type for the column, or <see langword="null"/> to use the database default.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static OwnedNavigationBuilder ToJson(
this OwnedNavigationBuilder builder,
string? jsonColumnName,
string? jsonColumnType)
public static OwnedNavigationBuilder HasColumnType(this OwnedNavigationBuilder builder, string? columnType)
{
builder.OwnedEntityType.SetContainerColumnName(jsonColumnName);
builder.OwnedEntityType.SetContainerColumnType(jsonColumnType);
builder.OwnedEntityType.SetContainerColumnType(columnType);

return builder;
}
Expand Down
17 changes: 17 additions & 0 deletions src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2592,6 +2592,23 @@ protected virtual void ValidateJsonEntities(
IModel model,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
foreach (var entityType in model.GetEntityTypes())
{
if (entityType[RelationalAnnotationNames.ContainerColumnType] != null)
{
if (entityType.FindOwnership()?.PrincipalEntityType.IsOwned() == true)
{
throw new InvalidOperationException(RelationalStrings.ContainerTypeOnNonRoot(entityType.DisplayName()));
}

if (!entityType.IsOwned()
|| entityType.GetContainerColumnName() == null)
{
throw new InvalidOperationException(RelationalStrings.ContainerTypeOnNonContainer(entityType.DisplayName()));
}
}
}

var tables = BuildSharedTableEntityMap(model.GetEntityTypes());
foreach (var (table, mappedTypes) in tables)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ public static class RelationalAnnotationNames
ModelDependencies,
FieldValueGetter,
ContainerColumnName,
ContainerColumnType,
#pragma warning disable CS0618 // Type or member is obsolete
ContainerColumnTypeMapping,
#pragma warning restore CS0618 // Type or member is obsolete
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@
<data name="ConflictingTypeMappingsInferredForColumn" xml:space="preserve">
<value>Conflicting type mappings were inferred for column '{column}'.</value>
</data>
<data name="ContainerTypeOnNonRoot" xml:space="preserve">
<value>The entity type '{entityType}' has a container column type configured, but is nested in another owned type. The container column type can only be specified on a top-level owned type mapped to a container.</value>
</data>
<data name="ContainerTypeOnNonContainer" xml:space="preserve">
<value>The entity type '{entityType}' has a container column type configured, but is not mapped to a container column, such as for JSON. The container column type can only be specified on a top-level owned type mapped to a container.</value>
</data>
<data name="CreateIndexOperationWithInvalidSortOrder" xml:space="preserve">
<value>{numSortOrderProperties} values were provided in CreateIndexOperations.IsDescending, but the operation has {numColumns} columns.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,12 @@ public class SqlServerLoggingDefinitions : RelationalLoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EventDefinitionBase? LogMissingViewDefinitionRights;

/// <summary>
/// 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.
/// </summary>
public EventDefinitionBase? LogJsonTypeExperimental;
}
20 changes: 17 additions & 3 deletions src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ public static class SqlServerEventId
// Try to use <Noun><Verb> naming and be consistent with existing names.
private enum Id
{
// Model validation events
// All events
// Don't insert or delete anything in the middle of this section!
DecimalTypeDefaultWarning = CoreEventId.ProviderBaseId,
ByteIdentityColumnWarning,
ConflictingValueGenerationStrategiesWarning,
DecimalTypeKeyWarning,

// Transaction events
SavepointsDisabledBecauseOfMARS,
JsonTypeExperimental,

// Scaffolding events
ColumnFound = CoreEventId.ProviderDesignBaseId,
Expand Down Expand Up @@ -115,6 +115,20 @@ private static EventId MakeValidationId(Id id)
/// </remarks>
public static readonly EventId ByteIdentityColumnWarning = MakeValidationId(Id.ByteIdentityColumnWarning);

/// <summary>
/// An entity type makes use of the SQL Server native 'json' type. Please note that support for this type in EF Core 9 is
/// experimental and may change in future releases.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Model.Validation" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="EntityTypeEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId JsonTypeExperimental = MakeValidationId(Id.JsonTypeExperimental);

/// <summary>
/// There are conflicting value generation methods for a property.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,32 @@ private static string ByteIdentityColumnWarning(EventDefinitionBase definition,
p.Property.DeclaringType.DisplayName());
}

/// <summary>
/// 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.
/// </summary>
public static void JsonTypeExperimental(
this IDiagnosticsLogger<DbLoggerCategory.Model.Validation> diagnostics,
IEntityType entityType)
{
var definition = SqlServerResources.LogJsonTypeExperimental(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, entityType.DisplayName());
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new EntityTypeEventData(definition, (d, p)
=> ((EventDefinition<string>)d).GenerateMessage(((EntityTypeEventData)p).EntityType.DisplayName()), entityType);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

/// <summary>
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ public override void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.
ValidateDecimalColumns(model, logger);
ValidateByteIdentityMapping(model, logger);
ValidateTemporalTables(model, logger);
ValidateUseOfJsonType(model, logger);
}

/// <summary>
/// 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.
/// </summary>
protected virtual void ValidateUseOfJsonType(
IModel model,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
foreach (var entityType in model.GetEntityTypes())
{
if (string.Equals(entityType.GetContainerColumnType(), "json", StringComparison.OrdinalIgnoreCase)
|| entityType.GetProperties().Any(p => string.Equals(p.GetColumnType(), "json", StringComparison.OrdinalIgnoreCase)))
{
logger.JsonTypeExperimental(entityType);
}
}
}

/// <summary>
Expand Down
25 changes: 25 additions & 0 deletions src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b35704a

Please sign in to comment.