Skip to content

Commit

Permalink
Metadata for primitive collection mapping
Browse files Browse the repository at this point in the history
Part of #30730

Some stuff remaining, but wanted to get this out there for initial review.

Missing:
- ElementType in compiled model
- Negative cases
- More tests
  • Loading branch information
ajcvickers committed Jul 26, 2023
1 parent d7fa2be commit 3dc3923
Show file tree
Hide file tree
Showing 51 changed files with 4,729 additions and 532 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore;

/// <summary>
/// Relational database specific extension methods for <see cref="ElementTypeBuilder" />.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
public static class RelationalElementTypeBuilderExtensions
{
/// <summary>
/// Configures the data type of the elements of the collection.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
/// <param name="elementTypeBuilder">The builder for the elements being configured.</param>
/// <param name="typeName">The name of the data type of the elements.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static ElementTypeBuilder ElementsHaveDatabaseType(
this ElementTypeBuilder elementTypeBuilder,
string? typeName)
{
Check.NullButNotEmpty(typeName, nameof(typeName));

elementTypeBuilder.Metadata.SetStoreType(typeName);

return elementTypeBuilder;
}

/// <summary>
/// Configures the data type of the elements of the collection.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
/// <param name="elementTypeBuilder"> builder for the elements being configured.</param>
/// <param name="typeName">The name of the data type of the elements.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The same builder instance if the configuration was applied, <see langword="null" /> otherwise.</returns>
public static IConventionElementTypeBuilder? ElementsHaveDatabaseType(
this IConventionElementTypeBuilder elementTypeBuilder,
string? typeName,
bool fromDataAnnotation = false)
{
if (!elementTypeBuilder.CanSetDatabaseType(typeName, fromDataAnnotation))
{
return null;
}

elementTypeBuilder.Metadata.SetStoreType(typeName, fromDataAnnotation);
return elementTypeBuilder;
}

/// <summary>
/// Returns a value indicating whether the given data type can be set for the elements.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
/// <param name="elementTypeBuilder"> builder for the elements being configured.</param>
/// <param name="typeName">The name of the data type of the elements.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns><see langword="true" /> if the given data type can be set for the property.</returns>
public static bool CanSetDatabaseType(
this IConventionElementTypeBuilder elementTypeBuilder,
string? typeName,
bool fromDataAnnotation = false)
=> elementTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.StoreType, typeName, fromDataAnnotation);

/// <summary>
/// Configures the elements as capable of storing only fixed-length data, such as strings.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
/// <param name="elementTypeBuilder">The builder for the elements being configured.</param>
/// <param name="fixedLength">A value indicating whether the elements are constrained to fixed length values.</param>
/// <returns>The same builder instance so that multiple configuration calls can be chained.</returns>
public static ElementTypeBuilder ElementsAreFixedLength(
this ElementTypeBuilder elementTypeBuilder,
bool fixedLength = true)
{
elementTypeBuilder.Metadata.SetIsFixedLength(fixedLength);

return elementTypeBuilder;
}

/// <summary>
/// Configures the elements as capable of storing only fixed-length data, such as strings.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
/// <param name="elementTypeBuilder"> builder for the elements being configured.</param>
/// <param name="fixedLength">A value indicating whether the elements are constrained to fixed length values.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns> The same builder instance if the configuration was applied, <see langword="null" /> otherwise.</returns>
public static IConventionElementTypeBuilder? ElementsAreFixedLength(
this IConventionElementTypeBuilder elementTypeBuilder,
bool? fixedLength,
bool fromDataAnnotation = false)
{
if (!elementTypeBuilder.CanSetFixedLength(fixedLength, fromDataAnnotation))
{
return null;
}

elementTypeBuilder.Metadata.SetIsFixedLength(fixedLength, fromDataAnnotation);
return elementTypeBuilder;
}

/// <summary>
/// Returns a value indicating whether the elements can be configured as being fixed length or not.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
/// <param name="elementTypeBuilder"> builder for the elements being configured.</param>
/// <param name="fixedLength">A value indicating whether the elements are constrained to fixed length values.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns><see langword="true" /> if the elements can be configured as being fixed length or not.</returns>
public static bool CanSetFixedLength(
this IConventionElementTypeBuilder elementTypeBuilder,
bool? fixedLength,
bool fromDataAnnotation = false)
=> elementTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength, fromDataAnnotation);
}
143 changes: 143 additions & 0 deletions src/EFCore.Relational/Extensions/RelationalElementTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore;

/// <summary>
/// <see cref="IElementType" /> extension methods for relational database metadata.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
public static class RelationalElementTypeExtensions
{
/// <summary>
/// Returns the database type of the elements, or <see langword="null" /> if the database type could not be found.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>
/// The database type of the elements, or <see langword="null" /> if the database type could not be found.
/// </returns>
public static string? GetStoreType(this IReadOnlyElementType elementType)
=> (string?)(elementType.FindRelationalTypeMapping()?.StoreType
?? elementType.FindAnnotation(RelationalAnnotationNames.StoreType)?.Value);

/// <summary>
/// Returns the database type of the elements.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>The database type of the elements.</returns>
public static string GetStoreType(this IElementType elementType)
=> ((IReadOnlyElementType)elementType).GetStoreType()!;

/// <summary>
/// Sets the database type of the elements.
/// </summary>
/// <param name="elementType">The element.</param>
/// <param name="value">The value to set.</param>
public static void SetStoreType(this IMutableElementType elementType, string? value)
=> elementType.SetOrRemoveAnnotation(
RelationalAnnotationNames.StoreType,
Check.NullButNotEmpty(value, nameof(value)));

/// <summary>
/// Sets the database type of the elements.
/// </summary>
/// <param name="elementType">The element.</param>
/// <param name="value">The value to set.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The configured value.</returns>
public static string? SetStoreType(
this IConventionElementType elementType,
string? value,
bool fromDataAnnotation = false)
=> (string?)elementType.SetOrRemoveAnnotation(
RelationalAnnotationNames.StoreType,
Check.NullButNotEmpty(value, nameof(value)),
fromDataAnnotation)?.Value;

/// <summary>
/// Gets the <see cref="ConfigurationSource" /> for the database type.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>The <see cref="ConfigurationSource" /> for the column name.</returns>
public static ConfigurationSource? GetStoreTypeConfigurationSource(this IConventionElementType elementType)
=> elementType.FindAnnotation(RelationalAnnotationNames.StoreType)?.GetConfigurationSource();

/// <summary>
/// Returns a flag indicating whether the elements are capable of storing only fixed-length data, such as strings.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>A flag indicating whether the elements arecapable of storing only fixed-length data, such as strings.</returns>
public static bool? IsFixedLength(this IReadOnlyElementType elementType)
=> (bool?)elementType.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.Value;

/// <summary>
/// Returns a flag indicating whether the elements are capable of storing only fixed-length data, such as strings.
/// </summary>
/// <param name="elementType">The element.</param>
/// <param name="storeObject">The identifier of the table-like store object containing the column.</param>
/// <returns>A flag indicating whether the elements are capable of storing only fixed-length data, such as strings.</returns>
public static bool? IsFixedLength(this IReadOnlyElementType elementType, in StoreObjectIdentifier storeObject)
=> (bool?)elementType.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.Value;

/// <summary>
/// Sets a flag indicating whether the elements are capable of storing only fixed-length data, such as strings.
/// </summary>
/// <param name="elementType">The element.</param>
/// <param name="fixedLength">A value indicating whether the elements are constrained to fixed length values.</param>
public static void SetIsFixedLength(this IMutableElementType elementType, bool? fixedLength)
=> elementType.SetOrRemoveAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength);

/// <summary>
/// Sets a flag indicating whether the elements are capable of storing only fixed-length data, such as strings.
/// </summary>
/// <param name="elementType">The element.</param>
/// <param name="fixedLength">A value indicating whether the element are constrained to fixed length values.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>The configured value.</returns>
public static bool? SetIsFixedLength(
this IConventionElementType elementType,
bool? fixedLength,
bool fromDataAnnotation = false)
=> (bool?)elementType.SetOrRemoveAnnotation(
RelationalAnnotationNames.IsFixedLength,
fixedLength,
fromDataAnnotation)?.Value;

/// <summary>
/// Gets the <see cref="ConfigurationSource" /> for <see cref="IsFixedLength(IReadOnlyElementType)" />.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>The <see cref="ConfigurationSource" /> for <see cref="IsFixedLength(IReadOnlyElementType)" />.</returns>
public static ConfigurationSource? GetIsFixedLengthConfigurationSource(this IConventionElementType elementType)
=> elementType.FindAnnotation(RelationalAnnotationNames.IsFixedLength)?.GetConfigurationSource();

/// <summary>
/// Returns the collation to be used for the column.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>The collation for the column this element is mapped to.</returns>
public static string? GetCollation(this IReadOnlyElementType elementType)
=> (elementType is RuntimeElementType)
? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
: (string?)elementType.FindAnnotation(RelationalAnnotationNames.Collation)?.Value;

/// <summary>
/// Returns the <see cref="RelationalTypeMapping" /> for the given element on a finalized model.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>The type mapping.</returns>
[DebuggerStepThrough]
public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyElementType elementType)
=> (RelationalTypeMapping)elementType.GetTypeMapping();

/// <summary>
/// Returns the <see cref="RelationalTypeMapping" /> for the given element on a finalized model.
/// </summary>
/// <param name="elementType">The element.</param>
/// <returns>The type mapping, or <see langword="null" /> if none was found.</returns>
[DebuggerStepThrough]
public static RelationalTypeMapping? FindRelationalTypeMapping(this IReadOnlyElementType elementType)
=> (RelationalTypeMapping?)elementType.FindTypeMapping();
}
11 changes: 5 additions & 6 deletions src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public static string GetColumnName(this IReadOnlyProperty property)
{
tableFound = true;
}
else if(property.DeclaringType is IReadOnlyEntityType declaringEntityType)
else if (property.DeclaringType is IReadOnlyEntityType declaringEntityType)
{
foreach (var containingType in declaringEntityType.GetDerivedTypesInclusive())
{
Expand All @@ -84,7 +84,7 @@ public static string GetColumnName(this IReadOnlyProperty property)
return null;
}
}
else
else
{
var declaringEntityType = property.DeclaringType.ContainingEntityType;
if (declaringEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy)
Expand Down Expand Up @@ -213,7 +213,6 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property)
return sharedTablePrincipalConcurrencyProperty.GetColumnName(storeObject)!;
}


StringBuilder? builder = null;
var currentStoreObject = storeObject;
if (property.DeclaringType is IReadOnlyEntityType entityType)
Expand Down Expand Up @@ -242,8 +241,8 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property)
}
}
else if (StoreObjectIdentifier.Create(property.DeclaringType, currentStoreObject.StoreObjectType) == currentStoreObject
|| property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType)
.Any(f => f.StoreObject == currentStoreObject))
|| property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType)
.Any(f => f.StoreObject == currentStoreObject))
{
var complexType = (IReadOnlyComplexType)property.DeclaringType;
builder ??= new StringBuilder();
Expand Down Expand Up @@ -1176,7 +1175,7 @@ public static bool IsColumnNullable(this IReadOnlyProperty property, in StoreObj
return property.IsNullable
|| (property.DeclaringType is IReadOnlyEntityType entityType
&& ((entityType.BaseType != null
&& entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
&& entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
|| IsOptionalSharingDependent(entityType, storeObject, 0)));
}

Expand Down
5 changes: 5 additions & 0 deletions src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,9 @@ public static class RelationalAnnotationNames
/// The JSON property name for the element that the property/navigation maps to.
/// </summary>
public const string JsonPropertyName = Prefix + "JsonPropertyName";

/// <summary>
/// The name for store (database) type annotations.
/// </summary>
public const string StoreType = Prefix + "StoreType";
}
Loading

0 comments on commit 3dc3923

Please sign in to comment.