Skip to content

Commit f13058b

Browse files
author
Chris Santero
committed
include relationships when materializing
1 parent 1abdddc commit f13058b

1 file changed

Lines changed: 43 additions & 12 deletions

File tree

JSONAPI.EntityFramework/EntityFrameworkResourceObjectMaterializer.cs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Data.Entity;
44
using System.Linq;
5+
using System.Linq.Expressions;
56
using System.Reflection;
67
using System.Threading;
78
using 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

Comments
 (0)