Skip to content

Commit 4018482

Browse files
committed
Metadata work for json. Separated from main PR to make the review easier.
This PR includes: - builder methods, - conventions, - relational model, - model validation, - migrations, - update
1 parent 185cdcc commit 4018482

File tree

50 files changed

+4251
-136
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4251
-136
lines changed

src/EFCore.Relational/Design/AnnotationCodeGenerator.cs

+19-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
3535
RelationalAnnotationNames.InsertStoredProcedure,
3636
RelationalAnnotationNames.UpdateStoredProcedure,
3737
RelationalAnnotationNames.MappingFragments,
38-
RelationalAnnotationNames.RelationalOverrides
38+
RelationalAnnotationNames.RelationalOverrides,
39+
RelationalAnnotationNames.JsonColumnTypeMapping
3940
};
4041

4142
#region MethodInfos
@@ -136,6 +137,10 @@ private static readonly MethodInfo IndexHasFilterNameMethodInfo
136137
= typeof(RelationalIndexBuilderExtensions).GetRuntimeMethod(
137138
nameof(RelationalIndexBuilderExtensions.HasFilter), new[] { typeof(IndexBuilder), typeof(string) })!;
138139

140+
private static readonly MethodInfo ToJsonMethodInfo
141+
= typeof(RelationalOwnedNavigationBuilderExtensions).GetRuntimeMethod(
142+
nameof(RelationalOwnedNavigationBuilderExtensions.ToJson), new[] { typeof(OwnedNavigationBuilder), typeof(string) })!;
143+
139144
#endregion MethodInfos
140145

141146
/// <summary>
@@ -303,6 +308,19 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
303308
}
304309
}
305310

311+
if (annotations.TryGetValue(RelationalAnnotationNames.JsonColumnName, out var jsonColumnNameAnnotation)
312+
&& jsonColumnNameAnnotation != null && jsonColumnNameAnnotation.Value is string jsonColumnName
313+
&& entityType.IsOwned())
314+
{
315+
methodCallCodeFragments.Add(
316+
new MethodCallCodeFragment(
317+
ToJsonMethodInfo,
318+
jsonColumnName));
319+
320+
annotations.Remove(RelationalAnnotationNames.JsonColumnName);
321+
annotations.Remove(RelationalAnnotationNames.JsonColumnTypeMapping);
322+
}
323+
306324
methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi));
307325

308326
return methodCallCodeFragments;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore.Metadata.Internal
5+
{
6+
/// <summary>
7+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
8+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
9+
/// any release. You should only use it directly in your code with extreme caution and knowing that
10+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
11+
/// </summary>
12+
public static class RelationalPropertyInternalExtensions
13+
{
14+
/// <summary>
15+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
16+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
17+
/// any release. You should only use it directly in your code with extreme caution and knowing that
18+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
19+
/// </summary>
20+
public static bool IsOrdinalKeyProperty(this IReadOnlyProperty property)
21+
=> property.FindContainingPrimaryKey() is IReadOnlyKey key
22+
&& key.Properties.Count > 1
23+
&& !property.IsForeignKey()
24+
&& property.IsShadowProperty()
25+
&& property.ClrType == typeof(int)
26+
&& property.GetJsonPropertyName() == null;
27+
}
28+
}

src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs

+95-3
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static class RelationalEntityTypeExtensions
6262

6363
var ownership = entityType.FindOwnership();
6464
if (ownership != null
65-
&& ownership.IsUnique)
65+
&& (ownership.IsUnique || entityType.IsMappedToJson()))
6666
{
6767
return ownership.PrincipalEntityType.GetTableName();
6868
}
@@ -316,7 +316,7 @@ public static IEnumerable<ITableMapping> GetTableMappings(this IEntityType entit
316316

317317
var ownership = entityType.FindOwnership();
318318
return ownership != null
319-
&& ownership.IsUnique
319+
&& (ownership.IsUnique || entityType.IsMappedToJson())
320320
? ownership.PrincipalEntityType.GetViewName()
321321
: null;
322322
}
@@ -1385,7 +1385,7 @@ public static IEnumerable<IReadOnlyForeignKey> FindRowInternalForeignKeys(
13851385
StoreObjectIdentifier storeObject)
13861386
{
13871387
var primaryKey = entityType.FindPrimaryKey();
1388-
if (primaryKey == null)
1388+
if (primaryKey == null || entityType.IsMappedToJson())
13891389
{
13901390
yield break;
13911391
}
@@ -1855,4 +1855,96 @@ public static IEnumerable<ITrigger> GetDeclaredTriggers(this IEntityType entityT
18551855
=> Trigger.GetDeclaredTriggers(entityType).Cast<ITrigger>();
18561856

18571857
#endregion Trigger
1858+
1859+
#region Json
1860+
1861+
/// <summary>
1862+
/// Gets a value indicating whether the specified entity is mapped to a JSON column.
1863+
/// </summary>
1864+
/// <param name="entityType">The entity type.</param>
1865+
/// <returns>A value indicating whether the associated entity type is mapped to a JSON column.</returns>
1866+
public static bool IsMappedToJson(this IReadOnlyEntityType entityType)
1867+
=> !string.IsNullOrEmpty(entityType.GetJsonColumnName());
1868+
1869+
/// <summary>
1870+
/// Sets the name of the JSON column to which the entity type is mapped.
1871+
/// </summary>
1872+
/// <param name="entityType">The entity type to set the JSON column name for.</param>
1873+
/// <param name="columnName">The name to set.</param>
1874+
public static void SetJsonColumnName(this IMutableEntityType entityType, string? columnName)
1875+
=> entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.JsonColumnName, columnName);
1876+
1877+
/// <summary>
1878+
/// Sets the name of the JSON column to which the entity type is mapped.
1879+
/// </summary>
1880+
/// <param name="entityType">The entity type to set the JSON column name for.</param>
1881+
/// <param name="columnName">The name to set.</param>
1882+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
1883+
/// <returns>The configured value.</returns>
1884+
public static string? SetJsonColumnName(
1885+
this IConventionEntityType entityType,
1886+
string? columnName,
1887+
bool fromDataAnnotation = false)
1888+
=> (string?)entityType.SetAnnotation(RelationalAnnotationNames.JsonColumnName, columnName, fromDataAnnotation)?.Value;
1889+
1890+
/// <summary>
1891+
/// Gets the <see cref="ConfigurationSource" /> for the JSON column name.
1892+
/// </summary>
1893+
/// <param name="entityType">The entity type to set the JSON column name for.</param>
1894+
/// <returns>The <see cref="ConfigurationSource" /> for the JSON column name.</returns>
1895+
public static ConfigurationSource? GetJsonColumnNameConfigurationSource(this IConventionEntityType entityType)
1896+
=> entityType.FindAnnotation(RelationalAnnotationNames.JsonColumnName)
1897+
?.GetConfigurationSource();
1898+
1899+
/// <summary>
1900+
/// Gets the JSON column name to which the entity type is mapped.
1901+
/// </summary>
1902+
/// <param name="entityType">The entity type to get the JSON column name for.</param>
1903+
/// <returns>The JSON column name to which the entity type is mapped.</returns>
1904+
public static string? GetJsonColumnName(this IReadOnlyEntityType entityType)
1905+
=> entityType.FindAnnotation(RelationalAnnotationNames.JsonColumnName)?.Value is string jsonColumnName
1906+
? jsonColumnName
1907+
: (entityType.FindOwnership()?.PrincipalEntityType.GetJsonColumnName());
1908+
1909+
/// <summary>
1910+
/// Sets the type mapping for the JSON column to which the entity type is mapped.
1911+
/// </summary>
1912+
/// <param name="entityType">The entity type to set the JSON column type mapping for.</param>
1913+
/// <param name="typeMapping">The type mapping to set.</param>
1914+
public static void SetJsonColumnTypeMapping(this IMutableEntityType entityType, RelationalTypeMapping typeMapping)
1915+
=> entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.JsonColumnTypeMapping, typeMapping);
1916+
1917+
/// <summary>
1918+
/// Sets the type mapping for the JSON column to which the entity type is mapped.
1919+
/// </summary>
1920+
/// <param name="entityType">The entity type to set the JSON column type mapping for.</param>
1921+
/// <param name="typeMapping">The type mapping to set.</param>
1922+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
1923+
/// <returns>The configured value.</returns>
1924+
public static RelationalTypeMapping? SetJsonColumnTypeMapping(
1925+
this IConventionEntityType entityType,
1926+
RelationalTypeMapping? typeMapping,
1927+
bool fromDataAnnotation = false)
1928+
=> (RelationalTypeMapping?)entityType.SetAnnotation(RelationalAnnotationNames.JsonColumnTypeMapping, typeMapping, fromDataAnnotation)?.Value;
1929+
1930+
/// <summary>
1931+
/// Gets the <see cref="ConfigurationSource" /> for the JSON column type mapping.
1932+
/// </summary>
1933+
/// <param name="entityType">The entity type to set the JSON column type mapping for.</param>
1934+
/// <returns>The <see cref="ConfigurationSource" /> for the JSON column type mapping.</returns>
1935+
public static ConfigurationSource? GetJsonColumnTypeMappingConfigurationSource(this IConventionEntityType entityType)
1936+
=> entityType.FindAnnotation(RelationalAnnotationNames.JsonColumnTypeMapping)
1937+
?.GetConfigurationSource();
1938+
1939+
/// <summary>
1940+
/// Gets the JSON column type mapping to which the entity type is mapped.
1941+
/// </summary>
1942+
/// <param name="entityType">The entity type to get the JSON column type mapping for.</param>
1943+
/// <returns>The JSON column type mapping to which the entity type is mapped.</returns>
1944+
public static RelationalTypeMapping? GetJsonColumnTypeMapping(this IReadOnlyEntityType entityType)
1945+
=> entityType.FindAnnotation(RelationalAnnotationNames.JsonColumnTypeMapping)?.Value is RelationalTypeMapping jsonColumnTypeMapping
1946+
? jsonColumnTypeMapping
1947+
: (entityType.FindOwnership()?.PrincipalEntityType.GetJsonColumnTypeMapping());
1948+
1949+
#endregion
18581950
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore;
5+
6+
/// <summary>
7+
/// Relational database specific extension methods for <see cref="NavigationBuilder" />.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
11+
/// </remarks>
12+
public static class RelationalNavigationBuilderExtensions
13+
{
14+
/// <summary>
15+
/// Configures the navigation of an entity mapped to a JSON column, mapping the navigation to a specific JSON property,
16+
/// rather than using the navigation name.
17+
/// </summary>
18+
/// <param name="navigationBuilder">The builder for the navigation being configured.</param>
19+
/// <param name="name">JSON property name to be used.</param>
20+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
21+
/// <returns>
22+
/// The same builder instance if the configuration was applied,
23+
/// <see langword="null" /> otherwise.
24+
/// </returns>
25+
public static IConventionNavigationBuilder? HasJsonPropertyName(
26+
this IConventionNavigationBuilder navigationBuilder,
27+
string? name,
28+
bool fromDataAnnotation = false)
29+
{
30+
if (!navigationBuilder.CanSetJsonPropertyName(name, fromDataAnnotation))
31+
{
32+
return null;
33+
}
34+
35+
navigationBuilder.Metadata.SetJsonPropertyName(name, fromDataAnnotation);
36+
37+
return navigationBuilder;
38+
}
39+
40+
/// <summary>
41+
/// Returns a value indicating whether the given value can be used as a JSON property name for a given navigation.
42+
/// </summary>
43+
/// <param name="navigationBuilder">The builder for the navigation being configured.</param>
44+
/// <param name="name">JSON property name to be used.</param>
45+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
46+
/// <returns><see langword="true" /> if the given value can be set as JSON property name for this navigation.</returns>
47+
public static bool CanSetJsonPropertyName(
48+
this IConventionNavigationBuilder navigationBuilder,
49+
string? name,
50+
bool fromDataAnnotation = false)
51+
=> navigationBuilder.CanSetAnnotation(RelationalAnnotationNames.JsonPropertyName, name, fromDataAnnotation);
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.EntityFrameworkCore;
5+
6+
/// <summary>
7+
/// Navigation extension methods for relational database metadata.
8+
/// </summary>
9+
/// <remarks>
10+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
11+
/// </remarks>
12+
public static class RelationalNavigationExtensions
13+
{
14+
/// <summary>
15+
/// Gets the value of JSON property name used for the given navigation of an entity mapped to a JSON column.
16+
/// </summary>
17+
/// <remarks>
18+
/// Unless configured explicitly, navigation name is used.
19+
/// </remarks>
20+
/// <param name="navigation">The navigation.</param>
21+
/// <returns>
22+
/// The value for the JSON property used to store the value of this navigation.
23+
/// <see langword="null" /> is returned for navigations of entities that are not mapped to a JSON column.
24+
/// </returns>
25+
public static string? GetJsonPropertyName(this IReadOnlyNavigationBase navigation)
26+
=> (string?)navigation.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value
27+
?? (!navigation.DeclaringEntityType.IsMappedToJson() ? null : navigation.Name);
28+
29+
/// <summary>
30+
/// Sets the value of JSON property name used for the given navigation of an entity mapped to a JSON column.
31+
/// </summary>
32+
/// <param name="navigation">The navigation.</param>
33+
/// <param name="name">The name to be used.</param>
34+
public static void SetJsonPropertyName(this IMutableNavigationBase navigation, string? name)
35+
=> navigation.SetOrRemoveAnnotation(
36+
RelationalAnnotationNames.JsonPropertyName,
37+
Check.NullButNotEmpty(name, nameof(name)));
38+
39+
/// <summary>
40+
/// Sets the value of JSON property name used for the given navigation of an entity mapped to a JSON column.
41+
/// </summary>
42+
/// <param name="navigation">The navigation.</param>
43+
/// <param name="name">The name to be used.</param>
44+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
45+
/// <returns>The configured value.</returns>
46+
public static string? SetJsonPropertyName(
47+
this IConventionNavigationBase navigation,
48+
string? name,
49+
bool fromDataAnnotation = false)
50+
{
51+
navigation.SetOrRemoveAnnotation(
52+
RelationalAnnotationNames.JsonPropertyName,
53+
Check.NullButNotEmpty(name, nameof(name)),
54+
fromDataAnnotation);
55+
56+
return name;
57+
}
58+
59+
/// <summary>
60+
/// Gets the <see cref="ConfigurationSource" /> for the JSON property name for a given navigation.
61+
/// </summary>
62+
/// <param name="navigation">The navigation.</param>
63+
/// <returns>The <see cref="ConfigurationSource" /> for the JSON property name for a given navigation.</returns>
64+
public static ConfigurationSource? GetJsonPropertyNameConfigurationSource(this IConventionNavigationBase navigation)
65+
=> navigation.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.GetConfigurationSource();
66+
}

0 commit comments

Comments
 (0)