@@ -38,8 +38,11 @@ internal static class PostgresPropertyMapping
3838 var value => throw new NotSupportedException ( $ "Mapping for type '{ value . GetType ( ) . Name } ' to a vector is not supported.")
3939 } ;
4040
41- public static NpgsqlDbType ? GetNpgsqlDbType ( Type propertyType ) =>
42- ( Nullable . GetUnderlyingType ( propertyType ) ?? propertyType ) switch
41+ /// <summary>
42+ /// Gets the NpgsqlDbType for a property, taking into account any store type annotation.
43+ /// </summary>
44+ internal static NpgsqlDbType ? GetNpgsqlDbType ( PropertyModel property )
45+ => ( Nullable . GetUnderlyingType ( property . Type ) ?? property . Type ) switch
4346 {
4447 Type t when t == typeof ( bool ) => NpgsqlDbType . Boolean ,
4548 Type t when t == typeof ( short ) => NpgsqlDbType . Smallint ,
@@ -50,19 +53,21 @@ internal static class PostgresPropertyMapping
5053 Type t when t == typeof ( decimal ) => NpgsqlDbType . Numeric ,
5154 Type t when t == typeof ( string ) => NpgsqlDbType . Text ,
5255 Type t when t == typeof ( byte [ ] ) => NpgsqlDbType . Bytea ,
53- Type t when t == typeof ( DateTime ) => NpgsqlDbType . Timestamp ,
54- Type t when t == typeof ( DateTimeOffset ) => NpgsqlDbType . TimestampTz ,
5556 Type t when t == typeof ( Guid ) => NpgsqlDbType . Uuid ,
57+ Type t when t == typeof ( DateTimeOffset ) => NpgsqlDbType . TimestampTz ,
58+
59+ // DateTime properties map to PG's "timestamp with time zone" (UTC timestamps) by default, aligning with Npgsql/EF/etc.
60+ // Users can explicitly opt into "timestamp without time zone".
61+ Type t when t == typeof ( DateTime ) && property . IsTimestampWithoutTimezone ( ) => NpgsqlDbType . Timestamp ,
62+ Type t when t == typeof ( DateTime ) => NpgsqlDbType . TimestampTz ,
5663
5764 _ => null
5865 } ;
5966
6067 /// <summary>
61- /// Maps a .NET type to a PostgreSQL type name.
68+ /// Maps a .NET type to a PostgreSQL type name, taking into account any store type annotation on the property .
6269 /// </summary>
63- /// <param name="propertyType">The .NET type.</param>
64- /// <returns>Tuple of the the PostgreSQL type name and whether it can be NULL</returns>
65- public static ( string PgType , bool IsNullable ) GetPostgresTypeName ( Type propertyType )
70+ internal static ( string PgType , bool IsNullable ) GetPostgresTypeName ( PropertyModel property )
6671 {
6772 static bool TryGetBaseType ( Type type , [ NotNullWhen ( true ) ] out string ? typeName )
6873 {
@@ -77,7 +82,7 @@ static bool TryGetBaseType(Type type, [NotNullWhen(true)] out string? typeName)
7782 Type t when t == typeof ( decimal ) => "NUMERIC" ,
7883 Type t when t == typeof ( string ) => "TEXT" ,
7984 Type t when t == typeof ( byte [ ] ) => "BYTEA" ,
80- Type t when t == typeof ( DateTime ) => "TIMESTAMP " ,
85+ Type t when t == typeof ( DateTime ) => "TIMESTAMPTZ " ,
8186 Type t when t == typeof ( DateTimeOffset ) => "TIMESTAMPTZ" ,
8287 Type t when t == typeof ( Guid ) => "UUID" ,
8388 _ => null
@@ -86,30 +91,43 @@ static bool TryGetBaseType(Type type, [NotNullWhen(true)] out string? typeName)
8691 return typeName is not null ;
8792 }
8893
94+ var propertyType = property . Type ;
95+
8996 // TODO: Handle NRTs properly via NullabilityInfoContext
9097
98+ ( string PgType , bool IsNullable ) result ;
99+
91100 if ( TryGetBaseType ( propertyType , out string ? pgType ) )
92101 {
93- return ( pgType , ! propertyType . IsValueType ) ;
102+ result = ( pgType , ! propertyType . IsValueType ) ;
94103 }
95-
96104 // Handle nullable types (e.g. Nullable<int>)
97- if ( Nullable . GetUnderlyingType ( propertyType ) is Type unwrappedType
105+ else if ( Nullable . GetUnderlyingType ( propertyType ) is Type unwrappedType
98106 && TryGetBaseType ( unwrappedType , out string ? underlyingPgType ) )
99107 {
100- return ( underlyingPgType , true ) ;
108+ result = ( underlyingPgType , true ) ;
101109 }
102-
103110 // Handle collections
104- if ( ( propertyType . IsArray && TryGetBaseType ( propertyType . GetElementType ( ) ! , out string ? elementPgType ) )
111+ else if ( ( propertyType . IsArray && TryGetBaseType ( propertyType . GetElementType ( ) ! , out string ? elementPgType ) )
105112 || ( propertyType . IsGenericType
106113 && propertyType . GetGenericTypeDefinition ( ) == typeof ( List < > )
107114 && TryGetBaseType ( propertyType . GetGenericArguments ( ) [ 0 ] , out elementPgType ) ) )
108115 {
109- return ( elementPgType + "[]" , true ) ;
116+ result = ( elementPgType + "[]" , true ) ;
117+ }
118+ else
119+ {
120+ throw new NotSupportedException ( $ "Type { propertyType . Name } is not supported by this store.") ;
121+ }
122+
123+ if ( property . IsTimestampWithoutTimezone ( ) )
124+ {
125+ // Replace TIMESTAMPTZ with TIMESTAMP in the PG type name.
126+ // This handles both "TIMESTAMPTZ" and "TIMESTAMPTZ[]" cases.
127+ result = ( "TIMESTAMP" , result . IsNullable ) ;
110128 }
111129
112- throw new NotSupportedException ( $ "Type { propertyType . Name } is not supported by this store." ) ;
130+ return result ;
113131 }
114132
115133 /// <summary>
0 commit comments