Skip to content

Commit e6fb475

Browse files
committed
Support a few more type mappings in the compiled model
Part of #2949
1 parent 92f881c commit e6fb475

8 files changed

+240
-30
lines changed

src/EFCore.PG/Design/Internal/NpgsqlCSharpRuntimeAnnotationCodeGenerator.cs

+101
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Microsoft.EntityFrameworkCore.Design.Internal;
55
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
6+
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
67

78
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Design.Internal;
89

@@ -28,6 +29,106 @@ public NpgsqlCSharpRuntimeAnnotationCodeGenerator(
2829
{
2930
}
3031

32+
/// <summary>
33+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
34+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
35+
/// any release. You should only use it directly in your code with extreme caution and knowing that
36+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
37+
/// </summary>
38+
public override bool Create(
39+
CoreTypeMapping typeMapping,
40+
CSharpRuntimeAnnotationCodeGeneratorParameters parameters,
41+
ValueComparer? valueComparer = null,
42+
ValueComparer? keyValueComparer = null,
43+
ValueComparer? providerValueComparer = null)
44+
{
45+
var result = base.Create(typeMapping, parameters, valueComparer, keyValueComparer, providerValueComparer);
46+
47+
var mainBuilder = parameters.MainBuilder;
48+
49+
var npgsqlDbTypeBasedDefaultInstance = typeMapping switch
50+
{
51+
NpgsqlStringTypeMapping => NpgsqlStringTypeMapping.Default,
52+
NpgsqlULongTypeMapping => NpgsqlULongTypeMapping.Default,
53+
// NpgsqlMultirangeTypeMapping => NpgsqlMultirangeTypeMapping.Default,
54+
_ => (INpgsqlTypeMapping?)null
55+
};
56+
57+
if (npgsqlDbTypeBasedDefaultInstance is not null)
58+
{
59+
var npgsqlDbType = ((INpgsqlTypeMapping)typeMapping).NpgsqlDbType;
60+
61+
if (npgsqlDbType != npgsqlDbTypeBasedDefaultInstance.NpgsqlDbType)
62+
{
63+
mainBuilder.AppendLine(";");
64+
65+
mainBuilder.Append(
66+
$"{parameters.TargetName}.TypeMapping = (({typeMapping.GetType().Name}){parameters.TargetName}.TypeMapping).Clone(");
67+
68+
mainBuilder
69+
.Append(nameof(NpgsqlTypes))
70+
.Append(".")
71+
.Append(nameof(NpgsqlDbType))
72+
.Append(".")
73+
.Append(npgsqlDbType.ToString());
74+
75+
mainBuilder
76+
.Append(")")
77+
.DecrementIndent();
78+
}
79+
80+
}
81+
82+
switch (typeMapping)
83+
{
84+
#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete
85+
case NpgsqlEnumTypeMapping enumTypeMapping:
86+
if (enumTypeMapping.NameTranslator != NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator)
87+
{
88+
throw new NotSupportedException(
89+
"Mapped enums are only supported in the compiled model if they use the default name translator");
90+
}
91+
break;
92+
#pragma warning restore CS0618
93+
94+
case NpgsqlRangeTypeMapping rangeTypeMapping:
95+
{
96+
var defaultInstance = NpgsqlRangeTypeMapping.Default;
97+
98+
var npgsqlDbTypeDifferent = rangeTypeMapping.NpgsqlDbType != defaultInstance.NpgsqlDbType;
99+
var subtypeTypeMappingIsDifferent = rangeTypeMapping.SubtypeMapping != defaultInstance.SubtypeMapping;
100+
101+
if (npgsqlDbTypeDifferent || subtypeTypeMappingIsDifferent)
102+
{
103+
mainBuilder.AppendLine(";");
104+
105+
mainBuilder.AppendLine(
106+
$"{parameters.TargetName}.TypeMapping = ((NpgsqlRangeTypeMapping){parameters.TargetName}.TypeMapping).Clone(")
107+
.IncrementIndent();
108+
109+
mainBuilder
110+
.Append(nameof(NpgsqlTypes))
111+
.Append(".")
112+
.Append(nameof(NpgsqlDbType))
113+
.Append(".")
114+
.Append(rangeTypeMapping.NpgsqlDbType.ToString())
115+
.AppendLine(",");
116+
117+
Create(rangeTypeMapping.SubtypeMapping, parameters);
118+
119+
mainBuilder
120+
.Append(")")
121+
.DecrementIndent();
122+
}
123+
124+
break;
125+
}
126+
127+
}
128+
129+
return result;
130+
}
131+
31132
/// <inheritdoc />
32133
public override void Generate(IModel model, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
33134
{

src/EFCore.PG/Storage/Internal/Mapping/NpgsqlEnumTypeMapping.cs

+52-17
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
1111
/// </summary>
1212
public class NpgsqlEnumTypeMapping : RelationalTypeMapping
1313
{
14-
private readonly ISqlGenerationHelper _sqlGenerationHelper;
15-
private readonly INpgsqlNameTranslator _nameTranslator;
16-
1714
/// <summary>
1815
/// Translates the CLR member value to the PostgreSQL value label.
1916
/// </summary>
@@ -25,14 +22,25 @@ public class NpgsqlEnumTypeMapping : RelationalTypeMapping
2522
/// any release. You should only use it directly in your code with extreme caution and knowing that
2623
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2724
/// </summary>
28-
public NpgsqlEnumTypeMapping(
29-
string storeType,
30-
string? storeTypeSchema,
31-
Type enumType,
32-
ISqlGenerationHelper sqlGenerationHelper,
33-
INpgsqlNameTranslator? nameTranslator = null)
25+
public static NpgsqlEnumTypeMapping Default { get; } = new();
26+
27+
/// <summary>
28+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
29+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
30+
/// any release. You should only use it directly in your code with extreme caution and knowing that
31+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
32+
/// </summary>
33+
public INpgsqlNameTranslator NameTranslator { get; }
34+
35+
/// <summary>
36+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
37+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
38+
/// any release. You should only use it directly in your code with extreme caution and knowing that
39+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
40+
/// </summary>
41+
public NpgsqlEnumTypeMapping(string storeType, Type enumType, INpgsqlNameTranslator? nameTranslator = null)
3442
: base(
35-
sqlGenerationHelper.DelimitIdentifier(storeType, storeTypeSchema),
43+
storeType,
3644
enumType,
3745
jsonValueReaderWriter: (JsonValueReaderWriter?)Activator.CreateInstance(
3846
typeof(JsonPgEnumReaderWriter<>).MakeGenericType(enumType)))
@@ -46,8 +54,7 @@ public NpgsqlEnumTypeMapping(
4654
nameTranslator ??= NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator;
4755
#pragma warning restore CS0618
4856

49-
_nameTranslator = nameTranslator;
50-
_sqlGenerationHelper = sqlGenerationHelper;
57+
NameTranslator = nameTranslator;
5158
_members = CreateValueMapping(enumType, nameTranslator);
5259
}
5360

@@ -59,23 +66,32 @@ public NpgsqlEnumTypeMapping(
5966
/// </summary>
6067
protected NpgsqlEnumTypeMapping(
6168
RelationalTypeMappingParameters parameters,
62-
ISqlGenerationHelper sqlGenerationHelper,
6369
INpgsqlNameTranslator nameTranslator)
6470
: base(parameters)
6571
{
66-
_nameTranslator = nameTranslator;
67-
_sqlGenerationHelper = sqlGenerationHelper;
72+
NameTranslator = nameTranslator;
6873
_members = CreateValueMapping(parameters.CoreParameters.ClrType, nameTranslator);
6974
}
7075

76+
// This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled
77+
// models. The constructor creates a completely blank type mapping, which will get cloned with all the correct details.
78+
private NpgsqlEnumTypeMapping()
79+
: base("some_enum", typeof(int))
80+
{
81+
#pragma warning disable CS0618 // NpgsqlConnection.GlobalTypeMapper is obsolete
82+
NameTranslator = NpgsqlConnection.GlobalTypeMapper.DefaultNameTranslator;
83+
#pragma warning restore CS0618
84+
_members = null!;
85+
}
86+
7187
/// <summary>
7288
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
7389
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
7490
/// any release. You should only use it directly in your code with extreme caution and knowing that
7591
/// doing so can result in application failures when updating to a new Entity Framework Core release.
7692
/// </summary>
7793
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
78-
=> new NpgsqlEnumTypeMapping(parameters, _sqlGenerationHelper, _nameTranslator);
94+
=> new NpgsqlEnumTypeMapping(parameters, NameTranslator);
7995

8096
/// <summary>
8197
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -98,12 +114,31 @@ private static Dictionary<object, string> CreateValueMapping(Type enumType, INpg
98114
x => x.GetValue(null)!,
99115
x => x.GetCustomAttribute<PgNameAttribute>()?.PgName ?? nameTranslator.TranslateMemberName(x.Name));
100116

101-
private sealed class JsonPgEnumReaderWriter<T> : JsonValueReaderWriter<T>
117+
// This is public for the compiled model
118+
/// <summary>
119+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
120+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
121+
/// any release. You should only use it directly in your code with extreme caution and knowing that
122+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
123+
/// </summary>
124+
public sealed class JsonPgEnumReaderWriter<T> : JsonValueReaderWriter<T>
102125
where T : struct, Enum
103126
{
127+
/// <summary>
128+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
129+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
130+
/// any release. You should only use it directly in your code with extreme caution and knowing that
131+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
132+
/// </summary>
104133
public override T FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null)
105134
=> Enum.Parse<T>(manager.CurrentReader.GetString()!);
106135

136+
/// <summary>
137+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
138+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
139+
/// any release. You should only use it directly in your code with extreme caution and knowing that
140+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
141+
/// </summary>
107142
public override void ToJsonTyped(Utf8JsonWriter writer, T value)
108143
=> writer.WriteStringValue(value.ToString());
109144
}

src/EFCore.PG/Storage/Internal/Mapping/NpgsqlMultirangeTypeMapping.cs

+27
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public virtual NpgsqlRangeTypeMapping RangeMapping
2828
/// </summary>
2929
public virtual NpgsqlDbType NpgsqlDbType { get; }
3030

31+
/// <summary>
32+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
33+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
34+
/// any release. You should only use it directly in your code with extreme caution and knowing that
35+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
36+
/// </summary>
37+
public static NpgsqlMultirangeTypeMapping Default { get; } = new();
38+
3139
/// <summary>
3240
/// Constructs an instance of the <see cref="NpgsqlRangeTypeMapping" /> class.
3341
/// </summary>
@@ -62,6 +70,25 @@ protected NpgsqlMultirangeTypeMapping(
6270
NpgsqlDbType = npgsqlDbType;
6371
}
6472

73+
// This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled
74+
// models. The constructor creates a completely blank type mapping, which will get cloned with all the correct details.
75+
private NpgsqlMultirangeTypeMapping()
76+
: this("int4multirange", typeof(List<NpgsqlRange<int>>), rangeMapping: null!)
77+
{
78+
}
79+
80+
/// <summary>
81+
/// This method exists only to support the compiled model.
82+
/// </summary>
83+
/// <remarks>
84+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
85+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
86+
/// any release. You should only use it directly in your code with extreme caution and knowing that
87+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
88+
/// </remarks>
89+
public NpgsqlMultirangeTypeMapping Clone(NpgsqlDbType npgsqlDbType)
90+
=> new(Parameters, npgsqlDbType);
91+
6592
/// <summary>
6693
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
6794
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.PG/Storage/Internal/Mapping/NpgsqlRangeTypeMapping.cs

+27
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ public class NpgsqlRangeTypeMapping : NpgsqlTypeMapping
2424
private ConstructorInfo? _rangeConstructor2;
2525
private ConstructorInfo? _rangeConstructor3;
2626

27+
/// <summary>
28+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
29+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
30+
/// any release. You should only use it directly in your code with extreme caution and knowing that
31+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
32+
/// </summary>
33+
public static NpgsqlRangeTypeMapping Default { get; } = new();
34+
2735
// ReSharper disable once MemberCanBePrivate.Global
2836
/// <summary>
2937
/// The relational type mapping of the range's subtype.
@@ -94,6 +102,25 @@ protected NpgsqlRangeTypeMapping(
94102
SubtypeMapping = subtypeMapping;
95103
}
96104

105+
// This constructor exists only to support the static Default property above, which is necessary to allow code generation for compiled
106+
// models. The constructor creates a completely blank type mapping, which will get cloned with all the correct details.
107+
private NpgsqlRangeTypeMapping()
108+
: this("int4range", typeof(NpgsqlRange<int>), NpgsqlDbType.IntegerRange, subtypeMapping: null!)
109+
{
110+
}
111+
112+
/// <summary>
113+
/// This method exists only to support the compiled model.
114+
/// </summary>
115+
/// <remarks>
116+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
117+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
118+
/// any release. You should only use it directly in your code with extreme caution and knowing that
119+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
120+
/// </remarks>
121+
public NpgsqlRangeTypeMapping Clone(NpgsqlDbType npgsqlDbType, RelationalTypeMapping subtypeTypeMapping)
122+
=> new(Parameters, npgsqlDbType, subtypeTypeMapping);
123+
97124
/// <summary>
98125
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
99126
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.PG/Storage/Internal/Mapping/NpgsqlStringTypeMapping.cs

+20
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
88
/// </summary>
99
public class NpgsqlStringTypeMapping : StringTypeMapping, INpgsqlTypeMapping
1010
{
11+
/// <summary>
12+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
13+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
14+
/// any release. You should only use it directly in your code with extreme caution and knowing that
15+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
16+
/// </summary>
17+
public static new NpgsqlStringTypeMapping Default { get; } = new("text", NpgsqlDbType.Text);
18+
1119
/// <summary>
1220
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
1321
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -51,6 +59,18 @@ protected NpgsqlStringTypeMapping(
5159
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
5260
=> new NpgsqlStringTypeMapping(parameters, NpgsqlDbType);
5361

62+
/// <summary>
63+
/// This method exists only to support the compiled model.
64+
/// </summary>
65+
/// <remarks>
66+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
67+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
68+
/// any release. You should only use it directly in your code with extreme caution and knowing that
69+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
70+
/// </remarks>
71+
public NpgsqlStringTypeMapping Clone(NpgsqlDbType npgsqlDbType)
72+
=> new(Parameters, npgsqlDbType);
73+
5474
/// <summary>
5575
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
5676
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.PG/Storage/Internal/Mapping/NpgsqlULongTypeMapping.cs

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
1010
/// </summary>
1111
public class NpgsqlULongTypeMapping : NpgsqlTypeMapping
1212
{
13+
/// <summary>
14+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
15+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
16+
/// any release. You should only use it directly in your code with extreme caution and knowing that
17+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
18+
/// </summary>
19+
public static NpgsqlULongTypeMapping Default { get; } = new("xid8", NpgsqlDbType.Xid8);
20+
1321
/// <summary>
1422
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
1523
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,9 @@ is PropertyInfo globalEnumTypeMappingsProperty
399399
var name = components.Length > 1 ? string.Join(null, components.Skip(1)) : adoEnumMapping.PgTypeName;
400400

401401
var mapping = new NpgsqlEnumTypeMapping(
402-
name, schema, adoEnumMapping.EnumClrType, sqlGenerationHelper, adoEnumMapping.NameTranslator);
402+
sqlGenerationHelper.DelimitIdentifier(name, schema),
403+
adoEnumMapping.EnumClrType,
404+
adoEnumMapping.NameTranslator);
403405
ClrTypeMappings[adoEnumMapping.EnumClrType] = mapping;
404406
StoreTypeMappings[mapping.StoreType] = new RelationalTypeMapping[] { mapping };
405407
}

0 commit comments

Comments
 (0)