22using System . Collections . Generic ;
33using System . Data . Entity ;
44using System . Linq ;
5+ using System . Linq . Expressions ;
56using System . Reflection ;
67using System . Threading ;
78using System . Threading . Tasks ;
@@ -20,6 +21,7 @@ public class EntityFrameworkResourceObjectMaterializer : IEntityFrameworkResourc
2021 private readonly DbContext _dbContext ;
2122 private readonly IResourceTypeRegistry _registry ;
2223 private readonly MethodInfo _openSetToManyRelationshipValueMethod ;
24+ private readonly MethodInfo _openGetExistingRecordGenericMethod ;
2325
2426 /// <summary>
2527 /// Creates a new EntityFrameworkEntityFrameworkResourceObjectMaterializer
@@ -32,13 +34,25 @@ public EntityFrameworkResourceObjectMaterializer(DbContext dbContext, IResourceT
3234 _registry = registry ;
3335 _openSetToManyRelationshipValueMethod = GetType ( )
3436 . GetMethod ( "SetToManyRelationshipValue" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
37+ _openGetExistingRecordGenericMethod = GetType ( )
38+ . GetMethod ( "GetExistingRecordGeneric" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
3539 }
3640
3741 public async Task < object > MaterializeResourceObject ( IResourceObject resourceObject , CancellationToken cancellationToken )
3842 {
3943 var registration = _registry . GetRegistrationForResourceTypeName ( resourceObject . Type ) ;
44+
45+ var relationshipsToInclude = new List < ResourceTypeRelationship > ( ) ;
46+ if ( resourceObject . Relationships != null )
47+ {
48+ relationshipsToInclude . AddRange (
49+ resourceObject . Relationships
50+ . Select ( relationshipObject => registration . GetFieldByName ( relationshipObject . Key ) )
51+ . OfType < ResourceTypeRelationship > ( ) ) ;
52+ }
4053
41- var material = await GetExistingRecord ( registration , resourceObject . Id , cancellationToken ) ;
54+
55+ var material = await GetExistingRecord ( registration , resourceObject . Id , relationshipsToInclude . ToArray ( ) , cancellationToken ) ;
4256 if ( material == null )
4357 {
4458 material = Activator . CreateInstance ( registration . Type ) ;
@@ -64,13 +78,13 @@ protected virtual Task SetIdForNewResource(IResourceObject resourceObject, objec
6478 /// <summary>
6579 /// Gets an existing record from the store by ID, if it exists
6680 /// </summary>
67- /// <param name="registration"></param>
68- /// <param name="id"></param>
69- /// <param name="cancellationToken"></param>
7081 /// <returns></returns>
71- protected virtual Task < object > GetExistingRecord ( IResourceTypeRegistration registration , string id , CancellationToken cancellationToken )
82+ protected virtual async Task < object > GetExistingRecord ( IResourceTypeRegistration registration , string id ,
83+ ResourceTypeRelationship [ ] relationshipsToInclude , CancellationToken cancellationToken )
7284 {
73- return _dbContext . Set ( registration . Type ) . FindAsync ( cancellationToken , id ) ;
85+ var method = _openGetExistingRecordGenericMethod . MakeGenericMethod ( registration . Type ) ;
86+ var result = ( dynamic ) method . Invoke ( this , new object [ ] { registration , id , relationshipsToInclude , cancellationToken } ) ;
87+ return await result ;
7488 }
7589
7690 /// <summary>
@@ -127,7 +141,7 @@ protected virtual async Task MergeFieldsIntoProperties(IResourceObject resourceO
127141 var relatedId = resourceIdentifierObject [ "id" ] . Value < string > ( ) ;
128142
129143 var relatedObjectRegistration = _registry . GetRegistrationForResourceTypeName ( relatedType ) ;
130- var relatedObject = await GetExistingRecord ( relatedObjectRegistration , relatedId , cancellationToken ) ;
144+ var relatedObject = await GetExistingRecord ( relatedObjectRegistration , relatedId , null , cancellationToken ) ;
131145 newCollection . Add ( relatedObject ) ;
132146 }
133147
@@ -142,9 +156,6 @@ protected virtual async Task MergeFieldsIntoProperties(IResourceObject resourceO
142156
143157 if ( linkage . LinkageToken == null )
144158 {
145- // For some reason we have to get the value first, or else setting it to null does nothing.
146- // TODO: This will cause a synchronous query. We can get rid of this line entirely by using Include when the object is first fetched.
147- typeRelationship . Property . GetValue ( material ) ;
148159 typeRelationship . Property . SetValue ( material , null ) ;
149160 }
150161 else
@@ -161,20 +172,40 @@ protected virtual async Task MergeFieldsIntoProperties(IResourceObject resourceO
161172
162173 var relatedObjectRegistration = _registry . GetRegistrationForResourceTypeName ( relatedType ) ;
163174 var relatedObject =
164- await GetExistingRecord ( relatedObjectRegistration , relatedId , cancellationToken ) ;
175+ await GetExistingRecord ( relatedObjectRegistration , relatedId , null , cancellationToken ) ;
165176
166177 typeRelationship . Property . SetValue ( material , relatedObject ) ;
167178 }
168179 }
169180 }
170181 }
171182
183+ /// <summary>
184+ /// Gets a record by ID
185+ /// </summary>
186+ protected async Task < TRecord > GetExistingRecordGeneric < TRecord > ( IResourceTypeRegistration registration ,
187+ string id , ResourceTypeRelationship [ ] relationshipsToInclude , CancellationToken cancellationToken ) where TRecord : class
188+ {
189+ var param = Expression . Parameter ( registration . Type ) ;
190+ var filterExpression = registration . GetFilterByIdExpression ( param , id ) ;
191+ var lambda = Expression . Lambda < Func < TRecord , bool > > ( filterExpression , param ) ;
192+ var query = _dbContext . Set < TRecord > ( ) . AsQueryable ( )
193+ . Where ( lambda ) ;
194+
195+ if ( relationshipsToInclude != null )
196+ {
197+ query = relationshipsToInclude . Aggregate ( query ,
198+ ( current , resourceTypeRelationship ) => current . Include ( resourceTypeRelationship . Property . Name ) ) ;
199+ }
200+
201+ return await query . FirstOrDefaultAsync ( cancellationToken ) ;
202+ }
203+
172204 /// <summary>
173205 /// Sets the value of a to-many relationship
174206 /// </summary>
175207 protected void SetToManyRelationshipValue < TRelated > ( object material , IEnumerable < object > relatedObjects , ResourceTypeRelationship relationship )
176208 {
177- // TODO: we need to fetch this property asynchronously first
178209 var currentValue = relationship . Property . GetValue ( material ) ;
179210 var typedArray = relatedObjects . Select ( o => ( TRelated ) o ) . ToArray ( ) ;
180211 if ( relationship . Property . PropertyType . IsAssignableFrom ( typeof ( List < TRelated > ) ) )
0 commit comments