5
5
using Postgrest . Exceptions ;
6
6
using Postgrest . Extensions ;
7
7
using Postgrest . Models ;
8
+
8
9
namespace Postgrest . Attributes
9
10
{
10
-
11
- /// <summary>
12
- /// Used to specify that a foreign key relationship exists in PostgreSQL
13
- ///
14
- /// See: https://postgrest.org/en/stable/api.html#resource-embedding
15
- /// </summary>
16
- [ AttributeUsage ( AttributeTargets . Property ) ]
17
- public class ReferenceAttribute : Attribute
18
- {
19
- /// <summary>
20
- /// Type of the model referenced
21
- /// </summary>
22
- public Type Model { get ; }
23
-
24
- /// <summary>
25
- /// Associated property name
26
- /// </summary>
27
- public string PropertyName { get ; private set ; }
28
-
29
- /// <summary>
30
- /// Table name of model
31
- /// </summary>
32
- public string TableName { get ; }
33
-
34
- /// <summary>
35
- /// Columns that exist on the model we will select from.
36
- /// </summary>
37
- public List < string > Columns { get ; private set ; } = new ( ) ;
38
-
39
- /// <summary>
40
- /// If the performed query is an Insert or Upsert, should this value be ignored? (DEFAULT TRUE)
41
- /// </summary>
42
- public bool IgnoreOnInsert { get ; private set ; }
43
-
44
- /// <summary>
45
- /// If the performed query is an Update, should this value be ignored? (DEFAULT TRUE)
46
- /// </summary>
47
- public bool IgnoreOnUpdate { get ; private set ; }
48
-
49
- /// <summary>
50
- /// If Reference should automatically be included in queries on this reference. (DEFAULT TRUE)
51
- /// </summary>
52
- public bool IncludeInQuery { get ; }
53
-
54
- /// <summary>
55
- /// As to whether the query will filter top-level rows.
56
- ///
57
- /// See: https://postgrest.org/en/stable/api.html#resource-embedding
58
- /// </summary>
59
- public bool ShouldFilterTopLevel { get ; }
60
-
61
- /// <param name="model">Model referenced</param>
62
- /// <param name="includeInQuery">Should referenced be included in queries?</param>
63
- /// <param name="ignoreOnInsert">Should reference data be excluded from inserts/upserts?</param>
64
- /// <param name="ignoreOnUpdate">Should reference data be excluded from updates?</param>
65
- /// <param name="shouldFilterTopLevel">As to whether the query will filter top-level rows.</param>
66
- /// <param name="propertyName"></param>
67
- /// <exception cref="Exception"></exception>
68
- public ReferenceAttribute ( Type model , bool includeInQuery = true , bool ignoreOnInsert = true , bool ignoreOnUpdate = true , bool shouldFilterTopLevel = true ,
69
- [ CallerMemberName ] string propertyName = "" )
70
- {
71
- if ( ! IsDerivedFromBaseModel ( model ) )
72
- {
73
- throw new PostgrestException ( "ReferenceAttribute must be used with Postgrest BaseModels." ) { Reason = FailureHint . Reason . InvalidArgument } ;
74
- }
75
-
76
- Model = model ;
77
- IncludeInQuery = includeInQuery ;
78
- IgnoreOnInsert = ignoreOnInsert ;
79
- IgnoreOnUpdate = ignoreOnUpdate ;
80
- PropertyName = propertyName ;
81
- ShouldFilterTopLevel = shouldFilterTopLevel ;
82
-
83
- var attr = GetCustomAttribute ( model , typeof ( TableAttribute ) ) ;
84
- TableName = attr is TableAttribute tableAttr ? tableAttr . Name : model . Name ;
85
- }
86
-
87
- internal void ParseProperties ( List < ReferenceAttribute > ? seenRefs = null )
88
- {
89
- seenRefs ??= new List < ReferenceAttribute > ( ) ;
90
-
91
- ParseColumns ( ) ;
92
- ParseRelationships ( seenRefs ) ;
93
- }
94
-
95
- private void ParseColumns ( )
96
- {
97
- foreach ( var property in Model . GetProperties ( ) )
98
- {
99
- var attrs = property . GetCustomAttributes ( true ) ;
100
-
101
- foreach ( var item in attrs )
102
- {
103
- switch ( item )
104
- {
105
- case ColumnAttribute columnAttribute :
106
- Columns . Add ( columnAttribute . ColumnName ) ;
107
- break ;
108
- case PrimaryKeyAttribute primaryKeyAttribute :
109
- Columns . Add ( primaryKeyAttribute . ColumnName ) ;
110
- break ;
111
- }
112
- }
113
- }
114
- }
115
-
116
- /// <inheritdoc />
117
- public override bool Equals ( object obj )
118
- {
119
- if ( obj is ReferenceAttribute attribute )
120
- {
121
- return TableName == attribute . TableName && PropertyName == attribute . PropertyName && Model == attribute . Model ;
122
- }
123
-
124
- return false ;
125
- }
126
-
127
- private void ParseRelationships ( List < ReferenceAttribute > seenRefs )
128
- {
129
- foreach ( var property in Model . GetProperties ( ) )
130
- {
131
- var attrs = property . GetCustomAttributes ( true ) ;
132
-
133
- foreach ( var attr in attrs )
134
- {
135
- if ( attr is not ReferenceAttribute { IncludeInQuery : true } refAttr ) continue ;
136
-
137
- if ( seenRefs . FirstOrDefault ( r => r . Equals ( refAttr ) ) != null ) continue ;
138
-
139
- seenRefs . Add ( refAttr ) ;
140
- refAttr . ParseProperties ( seenRefs ) ;
141
-
142
- Columns . Add ( ShouldFilterTopLevel ? $ "{ refAttr . TableName } !inner({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )" : $ "{ refAttr . TableName } ({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )") ;
143
- }
144
- }
145
- }
146
-
147
- private static bool IsDerivedFromBaseModel ( Type type ) =>
148
- type . GetInheritanceHierarchy ( ) . Any ( t => t == typeof ( BaseModel ) ) ;
149
- }
150
- }
11
+ /// <summary>
12
+ /// Used to specify that a foreign key relationship exists in PostgreSQL
13
+ ///
14
+ /// See: https://postgrest.org/en/stable/api.html#resource-embedding
15
+ /// </summary>
16
+ [ AttributeUsage ( AttributeTargets . Property ) ]
17
+ public class ReferenceAttribute : Attribute
18
+ {
19
+ /// <summary>
20
+ /// Specifies the Join type on this reference. PostgREST only allows for a LEFT join and an INNER join.
21
+ /// </summary>
22
+ public enum JoinType
23
+ {
24
+ /// <summary>
25
+ /// INNER JOIN: returns rows when there is a match on both the source and the referenced tables.
26
+ /// </summary>
27
+ Inner ,
28
+
29
+ /// <summary>
30
+ /// LEFT JOIN: returns all rows from the source table, even if there are no matches in the referenced table
31
+ /// </summary>
32
+ Left
33
+ }
34
+
35
+ /// <summary>
36
+ /// Type of the model referenced
37
+ /// </summary>
38
+ public Type Model { get ; }
39
+
40
+ /// <summary>
41
+ /// Associated property name
42
+ /// </summary>
43
+ public string PropertyName { get ; private set ; }
44
+
45
+ /// <summary>
46
+ /// Table name of model
47
+ /// </summary>
48
+ public string TableName { get ; }
49
+
50
+ /// <summary>
51
+ /// Columns that exist on the model we will select from.
52
+ /// </summary>
53
+ public List < string > Columns { get ; private set ; } = new ( ) ;
54
+
55
+ /// <summary>
56
+ /// If the performed query is an Insert or Upsert, should this value be ignored? (DEFAULT TRUE)
57
+ /// </summary>
58
+ public bool IgnoreOnInsert { get ; private set ; }
59
+
60
+ /// <summary>
61
+ /// If the performed query is an Update, should this value be ignored? (DEFAULT TRUE)
62
+ /// </summary>
63
+ public bool IgnoreOnUpdate { get ; private set ; }
64
+
65
+ /// <summary>
66
+ /// If Reference should automatically be included in queries on this reference. (DEFAULT TRUE)
67
+ /// </summary>
68
+ public bool IncludeInQuery { get ; }
69
+
70
+ /// <summary>
71
+ /// As to whether the query will filter top-level rows.
72
+ ///
73
+ /// See: https://postgrest.org/en/stable/api.html#resource-embedding
74
+ /// </summary>
75
+ public bool UseInnerJoin { get ; }
76
+
77
+ /// <summary>Establishes a reference between two tables</summary>
78
+ /// <param name="model">Model referenced</param>
79
+ /// <param name="includeInQuery">Should referenced be included in queries?</param>
80
+ /// <param name="ignoreOnInsert">Should reference data be excluded from inserts/upserts?</param>
81
+ /// <param name="ignoreOnUpdate">Should reference data be excluded from updates?</param>
82
+ /// <param name="joinType">Specifies the join type for this relationship</param>
83
+ /// <param name="propertyName"></param>
84
+ /// <exception cref="Exception"></exception>
85
+ public ReferenceAttribute ( Type model , JoinType joinType , bool includeInQuery = true , bool ignoreOnInsert = true ,
86
+ bool ignoreOnUpdate = true , [ CallerMemberName ] string propertyName = "" )
87
+ : this ( model , includeInQuery , ignoreOnInsert , ignoreOnUpdate , joinType == JoinType . Inner , propertyName )
88
+ {
89
+ }
90
+
91
+ /// <summary>Establishes a reference between two tables</summary>
92
+ /// <param name="model">Model referenced</param>
93
+ /// <param name="includeInQuery">Should referenced be included in queries?</param>
94
+ /// <param name="ignoreOnInsert">Should reference data be excluded from inserts/upserts?</param>
95
+ /// <param name="ignoreOnUpdate">Should reference data be excluded from updates?</param>
96
+ /// <param name="useInnerJoin">As to whether the query will filter top-level rows.</param>
97
+ /// <param name="propertyName"></param>
98
+ /// <exception cref="Exception"></exception>
99
+ public ReferenceAttribute ( Type model , bool includeInQuery = true , bool ignoreOnInsert = true ,
100
+ bool ignoreOnUpdate = true , bool useInnerJoin = true ,
101
+ [ CallerMemberName ] string propertyName = "" )
102
+ {
103
+ if ( ! IsDerivedFromBaseModel ( model ) )
104
+ throw new PostgrestException ( "ReferenceAttribute must be used with Postgrest BaseModels." )
105
+ { Reason = FailureHint . Reason . InvalidArgument } ;
106
+
107
+ Model = model ;
108
+ IncludeInQuery = includeInQuery ;
109
+ IgnoreOnInsert = ignoreOnInsert ;
110
+ IgnoreOnUpdate = ignoreOnUpdate ;
111
+ PropertyName = propertyName ;
112
+ UseInnerJoin = useInnerJoin ;
113
+
114
+ var attr = GetCustomAttribute ( model , typeof ( TableAttribute ) ) ;
115
+ TableName = attr is TableAttribute tableAttr ? tableAttr . Name : model . Name ;
116
+ }
117
+
118
+ internal void ParseProperties ( List < ReferenceAttribute > ? seenRefs = null )
119
+ {
120
+ seenRefs ??= new List < ReferenceAttribute > ( ) ;
121
+
122
+ ParseColumns ( ) ;
123
+ ParseRelationships ( seenRefs ) ;
124
+ }
125
+
126
+ private void ParseColumns ( )
127
+ {
128
+ foreach ( var property in Model . GetProperties ( ) )
129
+ {
130
+ var attrs = property . GetCustomAttributes ( true ) ;
131
+
132
+ foreach ( var item in attrs )
133
+ {
134
+ switch ( item )
135
+ {
136
+ case ColumnAttribute columnAttribute :
137
+ Columns . Add ( columnAttribute . ColumnName ) ;
138
+ break ;
139
+ case PrimaryKeyAttribute primaryKeyAttribute :
140
+ Columns . Add ( primaryKeyAttribute . ColumnName ) ;
141
+ break ;
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ /// <inheritdoc />
148
+ public override bool Equals ( object obj )
149
+ {
150
+ if ( obj is ReferenceAttribute attribute )
151
+ {
152
+ return TableName == attribute . TableName && PropertyName == attribute . PropertyName &&
153
+ Model == attribute . Model ;
154
+ }
155
+
156
+ return false ;
157
+ }
158
+
159
+
160
+ private void ParseRelationships ( List < ReferenceAttribute > seenRefs )
161
+ {
162
+ foreach ( var property in Model . GetProperties ( ) )
163
+ {
164
+ var attrs = property . GetCustomAttributes ( true ) ;
165
+
166
+ foreach ( var attr in attrs )
167
+ {
168
+ if ( attr is not ReferenceAttribute { IncludeInQuery : true } refAttr ) continue ;
169
+
170
+ if ( seenRefs . FirstOrDefault ( r => r . Equals ( refAttr ) ) != null ) continue ;
171
+
172
+ seenRefs . Add ( refAttr ) ;
173
+ refAttr . ParseProperties ( seenRefs ) ;
174
+
175
+ Columns . Add ( UseInnerJoin
176
+ ? $ "{ refAttr . TableName } !inner({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )"
177
+ : $ "{ refAttr . TableName } ({ string . Join ( "," , refAttr . Columns . ToArray ( ) ) } )") ;
178
+ }
179
+ }
180
+ }
181
+
182
+ private static bool IsDerivedFromBaseModel ( Type type ) =>
183
+ type . GetInheritanceHierarchy ( ) . Any ( t => t == typeof ( BaseModel ) ) ;
184
+ }
185
+ }
0 commit comments