-
Notifications
You must be signed in to change notification settings - Fork 225
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for JSON owned entities (#2922)
- Loading branch information
Showing
72 changed files
with
7,928 additions
and
1,077 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
src/EFCore.PG/Metadata/Conventions/NpgsqlJsonElementHackConvention.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Text.Json; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; | ||
|
||
/// <summary> | ||
/// This convention is a hack around https://github.com/dotnet/efcore/issues/32192. To support the EF owned entity JSON support, EF requires | ||
/// that a lookup of the CLR type <see cref="JsonElement" /> return the provider's special <see cref="JsonTypeMapping"/>. But Npgsql has | ||
/// its own JSON DOM support, where actually mapping <see cref="JsonElement" /> is allowed as a weakly-typed mapping strategy. The two | ||
/// JSON type mappings are incompatible notably because EF's <see cref="JsonTypeMapping" /> is expected to return UTF8 byte data which is | ||
/// then parsed via <see cref="Utf8JsonWriter" /> (and not a string). So for properties actually typed as <see cref="JsonElement" />, we | ||
/// hack here and set the type mapping rather than going through the regular type mapping process. | ||
/// </summary> | ||
public class NpgsqlJsonElementHackConvention : IPropertyAddedConvention | ||
{ | ||
private NpgsqlJsonTypeMapping? _jsonTypeMapping; | ||
|
||
/// <inheritdoc /> | ||
public void ProcessPropertyAdded(IConventionPropertyBuilder propertyBuilder, IConventionContext<IConventionPropertyBuilder> context) | ||
{ | ||
var property = propertyBuilder.Metadata; | ||
|
||
if (property.ClrType == typeof(JsonElement) && property.GetColumnType() is null) | ||
{ | ||
property.SetTypeMapping(_jsonTypeMapping ??= new("jsonb", typeof(JsonElement))); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; | ||
|
||
/// <summary> | ||
/// An expression that represents a PostgreSQL <c>unnest</c> function call in a SQL tree. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para> | ||
/// This expression is just a <see cref="TableValuedFunctionExpression" />, adding the ability to provide an explicit column name | ||
/// for its output (<c>SELECT * FROM unnest(array) AS f(foo)</c>). This is necessary since when the column name isn't explicitly | ||
/// specified, it is automatically identical to the table alias (<c>f</c> above); since the table alias may get uniquified by | ||
/// EF, this would break queries. | ||
/// </para> | ||
/// <para> | ||
/// See <see href="https://www.postgresql.org/docs/current/functions-array.html#ARRAY-FUNCTIONS-TABLE">unnest</see> for more | ||
/// information and examples. | ||
/// </para> | ||
/// <para> | ||
/// 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. | ||
/// </para> | ||
/// </remarks> | ||
public class PgTableValuedFunctionExpression : TableValuedFunctionExpression, IEquatable<PgTableValuedFunctionExpression> | ||
{ | ||
/// <summary> | ||
/// The name of the column to be projected out from the <c>unnest</c> call. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public virtual IReadOnlyList<ColumnInfo>? ColumnInfos { get; } | ||
|
||
/// <summary> | ||
/// Whether to project an additional ordinality column containing the index of each element in the array. | ||
/// </summary> | ||
/// <remarks> | ||
/// 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. | ||
/// </remarks> | ||
public virtual bool WithOrdinality { get; } | ||
|
||
/// <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 PgTableValuedFunctionExpression( | ||
string alias, | ||
string name, | ||
IReadOnlyList<SqlExpression> arguments, | ||
IReadOnlyList<ColumnInfo>? columnInfos, | ||
bool withOrdinality = true) | ||
: base(alias, name, schema: null, builtIn: true, arguments) | ||
{ | ||
ColumnInfos = columnInfos; | ||
WithOrdinality = withOrdinality; | ||
} | ||
|
||
/// <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 override PgTableValuedFunctionExpression Update(IReadOnlyList<SqlExpression> arguments) | ||
=> !arguments.SequenceEqual(Arguments) | ||
? new PgTableValuedFunctionExpression(Alias, Name, arguments, ColumnInfos, WithOrdinality) | ||
: this; | ||
|
||
/// <inheritdoc /> | ||
protected override void Print(ExpressionPrinter expressionPrinter) | ||
{ | ||
expressionPrinter.Append(Name); | ||
expressionPrinter.Append("("); | ||
expressionPrinter.VisitCollection(Arguments); | ||
expressionPrinter.Append(")"); | ||
|
||
if (WithOrdinality) | ||
{ | ||
expressionPrinter.Append(" WITH ORDINALITY"); | ||
} | ||
|
||
PrintAnnotations(expressionPrinter); | ||
|
||
expressionPrinter.Append(" AS ").Append(Alias); | ||
|
||
if (ColumnInfos is not null) | ||
{ | ||
expressionPrinter.Append("("); | ||
|
||
var isFirst = true; | ||
|
||
foreach (var column in ColumnInfos) | ||
{ | ||
if (isFirst) | ||
{ | ||
isFirst = false; | ||
} | ||
else | ||
{ | ||
expressionPrinter.Append(", "); | ||
} | ||
|
||
expressionPrinter.Append(column.Name); | ||
|
||
if (column.TypeMapping is not null) | ||
{ | ||
expressionPrinter.Append(" ").Append(column.TypeMapping.StoreType); | ||
} | ||
} | ||
|
||
expressionPrinter.Append(")"); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override bool Equals(object? obj) | ||
=> ReferenceEquals(obj, this) || obj is PgTableValuedFunctionExpression e && Equals(e); | ||
|
||
/// <inheritdoc /> | ||
public bool Equals(PgTableValuedFunctionExpression? expression) | ||
=> base.Equals(expression) | ||
&& ( | ||
expression.ColumnInfos is null && ColumnInfos is null | ||
|| expression.ColumnInfos is not null && ColumnInfos is not null && expression.ColumnInfos.SequenceEqual(ColumnInfos)) | ||
&& WithOrdinality == expression.WithOrdinality; | ||
|
||
/// <inheritdoc /> | ||
public override int GetHashCode() | ||
=> base.GetHashCode(); | ||
|
||
/// <summary> | ||
/// Defines the name of a column coming out of a <see cref="PgTableValuedFunctionExpression" /> and optionally its type. | ||
/// </summary> | ||
public readonly record struct ColumnInfo(string Name, RelationalTypeMapping? TypeMapping = null); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.