forked from npgsql/efcore.pg
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNpgsqlPostgresModelFinalizingConvention.cs
More file actions
132 lines (122 loc) · 5.31 KB
/
NpgsqlPostgresModelFinalizingConvention.cs
File metadata and controls
132 lines (122 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.ChangeTracking.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions;
/// <summary>
/// A convention that discovers certain common PostgreSQL extensions based on store types used in the model (e.g. hstore).
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-conventions">Model building conventions</see>.
/// </remarks>
public class NpgsqlPostgresModelFinalizingConvention(
NpgsqlTypeMappingSource typeMappingSource,
IReadOnlyList<EnumDefinition> enumDefinitions) : IModelFinalizingConvention
{
/// <inheritdoc />
public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
foreach (var property in entityType.GetDeclaredProperties())
{
var typeMapping = (RelationalTypeMapping?)property.FindTypeMapping()
?? typeMappingSource.FindMapping((IProperty)property);
if (typeMapping is not null)
{
DiscoverPostgresExtensions(property, typeMapping, modelBuilder);
ProcessRowVersionProperty(property, typeMapping);
SetRangeCurrentValueComparer(property, typeMapping);
}
}
DiscoverBtreeGistForWithoutOverlaps(entityType, modelBuilder);
}
SetupEnums(modelBuilder);
}
/// <summary>
/// Discovers the btree_gist extension if any keys or indexes use WITHOUT OVERLAPS.
/// </summary>
protected virtual void DiscoverBtreeGistForWithoutOverlaps(
IConventionEntityType entityType,
IConventionModelBuilder modelBuilder)
{
foreach (var key in entityType.GetDeclaredKeys())
{
if (key.GetWithoutOverlaps() == true)
{
modelBuilder.HasPostgresExtension("btree_gist");
return;
}
}
}
/// <summary>
/// Configures the model to create PostgreSQL enums based on the user's enum definitions in the context options.
/// </summary>
protected virtual void SetupEnums(IConventionModelBuilder modelBuilder)
{
foreach (var enumDefinition in enumDefinitions)
{
modelBuilder.HasPostgresEnum(
enumDefinition.StoreTypeSchema,
enumDefinition.StoreTypeName,
enumDefinition.Labels.Values.Order(StringComparer.Ordinal).ToArray());
}
}
/// <summary>
/// Discovers certain common PostgreSQL extensions based on property store types (e.g. hstore).
/// </summary>
protected virtual void DiscoverPostgresExtensions(
IConventionProperty property,
RelationalTypeMapping typeMapping,
IConventionModelBuilder modelBuilder)
{
// TODO: does not work if CREATE EXTENSION was done on a non-default schema. #3177
switch (typeMapping.StoreType)
{
case "hstore":
modelBuilder.HasPostgresExtension("hstore");
break;
case "citext":
modelBuilder.HasPostgresExtension("citext");
break;
case "ltree":
case "lquery":
case "ltxtquery":
modelBuilder.HasPostgresExtension("ltree");
break;
case "cube":
modelBuilder.HasPostgresExtension("cube");
break;
}
}
/// <summary>
/// Detects properties which are uint, OnAddOrUpdate and configured as concurrency tokens, and maps these to the PostgreSQL
/// internal "xmin" column, which changes every time the row is modified.
/// </summary>
protected virtual void ProcessRowVersionProperty(IConventionProperty property, RelationalTypeMapping typeMapping)
{
if (property is { ValueGenerated: ValueGenerated.OnAddOrUpdate, IsConcurrencyToken: true }
&& typeMapping.StoreType == "xid")
{
property.Builder.HasColumnName("xmin");
}
}
/// <summary>
/// Pre-sets the current value comparer for range key/FK properties, since <see cref="NpgsqlRange{T}" /> doesn't
/// implement <see cref="IComparable" /> and the default <see cref="CurrentValueComparerFactory" /> would reject it.
/// </summary>
protected virtual void SetRangeCurrentValueComparer(IConventionProperty property, RelationalTypeMapping typeMapping)
{
if ((property.IsKey() || property.IsForeignKey())
&& typeMapping is NpgsqlRangeTypeMapping
&& property is PropertyBase propertyBase)
{
#pragma warning disable EF1001 // Internal EF Core API usage.
propertyBase.SetCurrentValueComparer(
new EntryCurrentValueComparer((IProperty)property, new NpgsqlRangeCurrentValueComparer(property.ClrType)));
#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
}