4
4
using System . Linq ;
5
5
using System . Threading . Tasks ;
6
6
using JsonApiDotNetCore . Extensions ;
7
- using JsonApiDotNetCore . Internal ;
8
7
using JsonApiDotNetCore . Internal . Contracts ;
9
8
using JsonApiDotNetCore . Internal . Generics ;
10
9
using JsonApiDotNetCore . Internal . Query ;
11
- using JsonApiDotNetCore . Managers . Contracts ;
12
10
using JsonApiDotNetCore . Models ;
13
11
using JsonApiDotNetCore . Serialization ;
14
12
using Microsoft . EntityFrameworkCore ;
@@ -20,52 +18,42 @@ namespace JsonApiDotNetCore.Data
20
18
/// Provides a default repository implementation and is responsible for
21
19
/// abstracting any EF Core APIs away from the service layer.
22
20
/// </summary>
23
- public class DefaultEntityRepository < TEntity , TId >
24
- : IEntityRepository < TEntity , TId > ,
25
- IEntityFrameworkRepository < TEntity >
21
+ public class DefaultEntityRepository < TEntity , TId > : IEntityRepository < TEntity , TId >
26
22
where TEntity : class , IIdentifiable < TId >
27
23
{
28
- private readonly ICurrentRequest _currentRequest ;
29
24
private readonly ITargetedFields _targetedFields ;
30
25
private readonly DbContext _context ;
31
26
private readonly DbSet < TEntity > _dbSet ;
32
- private readonly ILogger _logger ;
33
27
private readonly IResourceGraph _resourceGraph ;
34
28
private readonly IGenericProcessorFactory _genericProcessorFactory ;
35
- private readonly ResourceDefinition < TEntity > _resourceDefinition ;
36
29
37
30
public DefaultEntityRepository (
38
- ICurrentRequest currentRequest ,
39
- ITargetedFields updatedFields ,
31
+ ITargetedFields targetedFields ,
40
32
IDbContextResolver contextResolver ,
41
33
IResourceGraph resourceGraph ,
42
- IGenericProcessorFactory genericProcessorFactory ,
43
- ResourceDefinition < TEntity > resourceDefinition = null )
44
- : this ( currentRequest , updatedFields , contextResolver , resourceGraph , genericProcessorFactory , resourceDefinition , null )
34
+ IGenericProcessorFactory genericProcessorFactory )
35
+ : this ( targetedFields , contextResolver , resourceGraph , genericProcessorFactory , null )
45
36
{ }
46
37
47
38
public DefaultEntityRepository (
48
- ICurrentRequest currentRequest ,
49
- ITargetedFields updatedFields ,
39
+ ITargetedFields targetedFields ,
50
40
IDbContextResolver contextResolver ,
51
41
IResourceGraph resourceGraph ,
52
42
IGenericProcessorFactory genericProcessorFactory ,
53
- ResourceDefinition < TEntity > resourceDefinition = null ,
54
43
ILoggerFactory loggerFactory = null )
55
44
{
56
- _logger = loggerFactory ? . CreateLogger < DefaultEntityRepository < TEntity , TId > > ( ) ;
57
- _currentRequest = currentRequest ;
58
- _targetedFields = updatedFields ;
45
+ _targetedFields = targetedFields ;
59
46
_resourceGraph = resourceGraph ;
60
47
_genericProcessorFactory = genericProcessorFactory ;
61
48
_context = contextResolver . GetContext ( ) ;
62
49
_dbSet = _context . Set < TEntity > ( ) ;
63
- _resourceDefinition = resourceDefinition ;
64
50
}
65
51
66
52
/// <inheritdoc />
67
53
public virtual IQueryable < TEntity > Get ( ) => _dbSet ;
68
-
54
+ /// <inheritdoc />
55
+ public virtual IQueryable < TEntity > Get ( TId id ) => _dbSet . Where ( e => e . Id . Equals ( id ) ) ;
56
+
69
57
/// <inheritdoc />
70
58
public virtual IQueryable < TEntity > Select ( IQueryable < TEntity > entities , List < AttrAttribute > fields )
71
59
{
@@ -79,12 +67,9 @@ public virtual IQueryable<TEntity> Select(IQueryable<TEntity> entities, List<Att
79
67
public virtual IQueryable < TEntity > Filter ( IQueryable < TEntity > entities , FilterQueryContext filterQueryContext )
80
68
{
81
69
if ( filterQueryContext . IsCustom )
82
- { // todo: consider to move this business logic to service layer
83
- var filterQuery = filterQueryContext . Query ;
84
- var defaultQueryFilters = _resourceDefinition . GetQueryFilters ( ) ;
85
- if ( defaultQueryFilters != null && defaultQueryFilters . TryGetValue ( filterQuery . Target , out var defaultQueryFilter ) == true )
86
- return defaultQueryFilter ( entities , filterQuery ) ;
87
-
70
+ {
71
+ var query = ( Func < IQueryable < TEntity > , FilterQuery , IQueryable < TEntity > > ) filterQueryContext . CustomQuery ;
72
+ return query ( entities , filterQueryContext . Query ) ;
88
73
}
89
74
return entities . Filter ( filterQueryContext ) ;
90
75
}
@@ -95,45 +80,30 @@ public virtual IQueryable<TEntity> Sort(IQueryable<TEntity> entities, SortQueryC
95
80
return entities . Sort ( sortQueryContext ) ;
96
81
}
97
82
98
- /// <inheritdoc />
99
- public virtual async Task < TEntity > GetAsync ( TId id , List < AttrAttribute > fields = null )
100
- {
101
- return await Select ( Get ( ) , fields ) . SingleOrDefaultAsync ( e => e . Id . Equals ( id ) ) ;
102
- }
103
-
104
- /// <inheritdoc />
105
- public virtual async Task < TEntity > GetAndIncludeAsync ( TId id , RelationshipAttribute relationship , List < AttrAttribute > fields = null )
106
- {
107
- _logger ? . LogDebug ( $ "[JADN] GetAndIncludeAsync({ id } , { relationship . PublicRelationshipName } )") ;
108
- var includedSet = Include ( Select ( Get ( ) , fields ) , relationship ) ;
109
- var result = await includedSet . SingleOrDefaultAsync ( e => e . Id . Equals ( id ) ) ;
110
- return result ;
111
- }
112
-
113
83
/// <inheritdoc />
114
84
public virtual async Task < TEntity > CreateAsync ( TEntity entity )
115
85
{
116
86
foreach ( var relationshipAttr in _targetedFields . Relationships )
117
87
{
118
- var trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , entity , out bool wasAlreadyTracked ) ;
88
+ object trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , entity , out bool wasAlreadyTracked ) ;
119
89
LoadInverseRelationships ( trackedRelationshipValue , relationshipAttr ) ;
120
90
if ( wasAlreadyTracked )
121
- {
122
91
/// We only need to reassign the relationship value to the to-be-added
123
92
/// entity when we're using a different instance (because this different one
124
93
/// was already tracked) than the one assigned to the to-be-created entity.
125
94
AssignRelationshipValue ( entity , trackedRelationshipValue , relationshipAttr ) ;
126
- }
127
95
else if ( relationshipAttr is HasManyThroughAttribute throughAttr )
128
- {
129
96
/// even if we don't have to reassign anything because of already tracked
130
97
/// entities, we still need to assign the "through" entities in the case of many-to-many.
131
98
AssignHasManyThrough ( entity , throughAttr , ( IList ) trackedRelationshipValue ) ;
132
- }
133
99
}
134
100
_dbSet . Add ( entity ) ;
135
101
await _context . SaveChangesAsync ( ) ;
136
102
103
+ // this ensures relationships get reloaded from the database if they have
104
+ // been requested. See https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/343
105
+ DetachRelationships ( entity ) ;
106
+
137
107
return entity ;
138
108
}
139
109
@@ -169,7 +139,7 @@ private void LoadInverseRelationships(object trackedRelationshipValue, Relations
169
139
170
140
private bool IsHasOneRelationship ( string internalRelationshipName , Type type )
171
141
{
172
- var relationshipAttr = _resourceGraph . GetContextEntity ( type ) . Relationships . SingleOrDefault ( r => r . InternalRelationshipName == internalRelationshipName ) ;
142
+ var relationshipAttr = _resourceGraph . GetContextEntity ( type ) . Relationships . FirstOrDefault ( r => r . InternalRelationshipName == internalRelationshipName ) ;
173
143
if ( relationshipAttr != null )
174
144
{
175
145
if ( relationshipAttr is HasOneAttribute )
@@ -182,9 +152,7 @@ private bool IsHasOneRelationship(string internalRelationshipName, Type type)
182
152
return ! ( type . GetProperty ( internalRelationshipName ) . PropertyType . Inherits ( typeof ( IEnumerable ) ) ) ;
183
153
}
184
154
185
-
186
- /// <inheritdoc />
187
- public void DetachRelationshipPointers ( TEntity entity )
155
+ private void DetachRelationships ( TEntity entity )
188
156
{
189
157
foreach ( var relationshipAttr in _targetedFields . Relationships )
190
158
{
@@ -212,22 +180,22 @@ public void DetachRelationshipPointers(TEntity entity)
212
180
/// <inheritdoc />
213
181
public virtual async Task < TEntity > UpdateAsync ( TEntity updatedEntity )
214
182
{
215
- var databaseEntity = await GetAsync ( updatedEntity . Id ) ;
183
+ var databaseEntity = await Get ( updatedEntity . Id ) . FirstOrDefaultAsync ( ) ;
216
184
if ( databaseEntity == null )
217
185
return null ;
218
186
219
- foreach ( var attr in _targetedFields . Attributes )
220
- attr . SetValue ( databaseEntity , attr . GetValue ( updatedEntity ) ) ;
187
+ foreach ( var attribute in _targetedFields . Attributes )
188
+ attribute . SetValue ( databaseEntity , attribute . GetValue ( updatedEntity ) ) ;
221
189
222
190
foreach ( var relationshipAttr in _targetedFields . Relationships )
223
191
{
224
192
/// loads databasePerson.todoItems
225
193
LoadCurrentRelationships ( databaseEntity , relationshipAttr ) ;
226
- /// trackedRelationshipValue is either equal to updatedPerson.todoItems
227
- /// or replaced with the same set of todoItems from the EF Core change tracker,
228
- /// if they were already tracked
229
- object trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , updatedEntity , out bool wasAlreadyTracked ) ;
230
- /// loads into the db context any persons currentlresy related
194
+ /// trackedRelationshipValue is either equal to updatedPerson.todoItems,
195
+ /// or replaced with the same set (same ids) of todoItems from the EF Core change tracker,
196
+ /// which is the case if they were already tracked
197
+ object trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , updatedEntity , out _ ) ;
198
+ /// loads into the db context any persons currently related
231
199
/// to the todoItems in trackedRelationshipValue
232
200
LoadInverseRelationships ( trackedRelationshipValue , relationshipAttr ) ;
233
201
/// assigns the updated relationship to the database entity
@@ -238,7 +206,6 @@ public virtual async Task<TEntity> UpdateAsync(TEntity updatedEntity)
238
206
return databaseEntity ;
239
207
}
240
208
241
-
242
209
/// <summary>
243
210
/// Responsible for getting the relationship value for a given relationship
244
211
/// attribute of a given entity. It ensures that the relationship value
@@ -302,11 +269,10 @@ public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute
302
269
await genericProcessor . UpdateRelationshipsAsync ( parent , relationship , relationshipIds ) ;
303
270
}
304
271
305
-
306
272
/// <inheritdoc />
307
273
public virtual async Task < bool > DeleteAsync ( TId id )
308
274
{
309
- var entity = await GetAsync ( id ) ;
275
+ var entity = await Get ( id ) . FirstOrDefaultAsync ( ) ;
310
276
if ( entity == null ) return false ;
311
277
_dbSet . Remove ( entity ) ;
312
278
await _context . SaveChangesAsync ( ) ;
@@ -327,33 +293,6 @@ public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, params
327
293
return entities . Include ( internalRelationshipPath ) ;
328
294
}
329
295
330
- /// <inheritdoc />
331
- public virtual IQueryable < TEntity > Include ( IQueryable < TEntity > entities , string relationshipName )
332
- {
333
- if ( string . IsNullOrWhiteSpace ( relationshipName ) ) throw new JsonApiException ( 400 , "Include parameter must not be empty if provided" ) ;
334
-
335
- var relationshipChain = relationshipName . Split ( '.' ) ;
336
-
337
- // variables mutated in recursive loop
338
- // TODO: make recursive method
339
- string internalRelationshipPath = null ;
340
- var entity = _currentRequest . GetRequestResource ( ) ;
341
- for ( var i = 0 ; i < relationshipChain . Length ; i ++ )
342
- {
343
- var requestedRelationship = relationshipChain [ i ] ;
344
- var relationship = entity . Relationships . FirstOrDefault ( r => r . PublicRelationshipName == requestedRelationship ) ;
345
-
346
- internalRelationshipPath = ( internalRelationshipPath == null )
347
- ? relationship . RelationshipPath
348
- : $ "{ internalRelationshipPath } .{ relationship . RelationshipPath } ";
349
-
350
- if ( i < relationshipChain . Length )
351
- entity = _resourceGraph . GetContextEntity ( relationship . Type ) ;
352
- }
353
-
354
- return entities . Include ( internalRelationshipPath ) ;
355
- }
356
-
357
296
/// <inheritdoc />
358
297
public virtual async Task < IEnumerable < TEntity > > PageAsync ( IQueryable < TEntity > entities , int pageSize , int pageNumber )
359
298
{
@@ -493,16 +432,23 @@ private IIdentifiable AttachOrGetTracked(IIdentifiable relationshipValue)
493
432
}
494
433
495
434
/// <inheritdoc />
496
- public class DefaultEntityRepository < TEntity >
497
- : DefaultEntityRepository < TEntity , int > ,
498
- IEntityRepository < TEntity >
435
+ public class DefaultEntityRepository < TEntity > : DefaultEntityRepository < TEntity , int > , IEntityRepository < TEntity >
499
436
where TEntity : class , IIdentifiable < int >
500
437
{
501
- public DefaultEntityRepository ( ICurrentRequest currentRequest , ITargetedFields updatedFields , IDbContextResolver contextResolver , IResourceGraph resourceGraph , IGenericProcessorFactory genericProcessorFactory , ResourceDefinition < TEntity > resourceDefinition = null ) : base ( currentRequest , updatedFields , contextResolver , resourceGraph , genericProcessorFactory , resourceDefinition )
438
+ public DefaultEntityRepository ( ITargetedFields targetedFields ,
439
+ IDbContextResolver contextResolver ,
440
+ IResourceGraph resourceGraph ,
441
+ IGenericProcessorFactory genericProcessorFactory )
442
+ : base ( targetedFields , contextResolver , resourceGraph , genericProcessorFactory )
502
443
{
503
444
}
504
445
505
- public DefaultEntityRepository ( ICurrentRequest currentRequest , ITargetedFields updatedFields , IDbContextResolver contextResolver , IResourceGraph resourceGraph , IGenericProcessorFactory genericProcessorFactory , ResourceDefinition < TEntity > resourceDefinition = null , ILoggerFactory loggerFactory = null ) : base ( currentRequest , updatedFields , contextResolver , resourceGraph , genericProcessorFactory , resourceDefinition , loggerFactory )
446
+ public DefaultEntityRepository ( ITargetedFields targetedFields ,
447
+ IDbContextResolver contextResolver ,
448
+ IResourceGraph resourceGraph ,
449
+ IGenericProcessorFactory genericProcessorFactory ,
450
+ ILoggerFactory loggerFactory = null )
451
+ : base ( targetedFields , contextResolver , resourceGraph , genericProcessorFactory , loggerFactory )
506
452
{
507
453
}
508
454
}
0 commit comments