Skip to content

Commit 3233869

Browse files
committed
change Type to ResourceType and add EntityType that defaults to ResourceType. updated DefaultEntityRepository to correctly attach relationship when creating a resource
1 parent d513658 commit 3233869

File tree

12 files changed

+101
-44
lines changed

12 files changed

+101
-44
lines changed

src/Examples/JsonApiDotNetCoreExample/Models/Resources/CourseResource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using JsonApiDotNetCore.Models;
22
using System.Collections.Generic;
33
using System.ComponentModel.DataAnnotations;
4+
using JsonApiDotNetCoreExample.Models.Entities;
45

56
namespace JsonApiDotNetCoreExample.Models.Resources
67
{
@@ -17,7 +18,7 @@ public class CourseResource : Identifiable
1718
[Attr("description")]
1819
public string Description { get; set; }
1920

20-
[HasOne("department")]
21+
[HasOne("department", withEntityType: typeof(DepartmentEntity))]
2122
public DepartmentResource Department { get; set; }
2223
public int? DepartmentId { get; set; }
2324

src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,10 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
165165

166166
attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? JsonApiOptions.ResourceNameFormatter.FormatPropertyName(prop);
167167
attribute.InternalRelationshipName = prop.Name;
168-
attribute.Type = GetRelationshipType(attribute, prop);
168+
attribute.ResourceType = GetRelationshipType(attribute, prop);
169169
attributes.Add(attribute);
170170

171-
if(attribute is HasManyThroughAttribute hasManyThroughAttribute) {
171+
if (attribute is HasManyThroughAttribute hasManyThroughAttribute) {
172172
var throughProperty = properties.SingleOrDefault(p => p.Name == hasManyThroughAttribute.InternalThroughName);
173173
if(throughProperty == null)
174174
throw new JsonApiSetupException($"Invalid '{nameof(HasManyThroughAttribute)}' on type '{entityType}'. Type does not contain a property named '{hasManyThroughAttribute.InternalThroughName}'.");
@@ -198,26 +198,21 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
198198
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a relationship id property to type {entityType} with name {leftIdPropertyName}");
199199

200200
// Article → ArticleTag.Tag
201-
hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.Type)
202-
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.Type}");
201+
hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.ResourceType)
202+
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.ResourceType}");
203203

204204
// ArticleTag.TagId
205205
var rightIdPropertyName = JsonApiOptions.RelatedIdMapper.GetRelatedIdPropertyName(hasManyThroughAttribute.RightProperty.Name);
206206
hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName)
207-
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a relationship id property to type {hasManyThroughAttribute.Type} with name {rightIdPropertyName}");
207+
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a relationship id property to type {hasManyThroughAttribute.ResourceType} with name {rightIdPropertyName}");
208208
}
209209
}
210210

211211
return attributes;
212212
}
213213

214-
protected virtual Type GetRelationshipType(RelationshipAttribute relation, PropertyInfo prop)
215-
{
216-
if (relation.IsHasMany)
217-
return prop.PropertyType.GetGenericArguments()[0];
218-
else
219-
return prop.PropertyType;
220-
}
214+
protected virtual Type GetRelationshipType(RelationshipAttribute relation, PropertyInfo prop) =>
215+
relation.IsHasMany ? prop.PropertyType.GetGenericArguments()[0] : prop.PropertyType;
221216

222217
private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType);
223218

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ private List<ResourceObject> IncludeSingleResourceRelationships(
242242
{
243243
if (relationshipChainIndex < relationshipChain.Length)
244244
{
245-
var nextContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type);
245+
var nextContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.ResourceType);
246246
var resource = (IIdentifiable)navigationEntity;
247247
// recursive call
248248
if (relationshipChainIndex < relationshipChain.Length - 1)
@@ -329,7 +329,7 @@ private ResourceIdentifierObject GetIndependentRelationshipIdentifier(HasOneAttr
329329
if (independentRelationshipIdentifier == null)
330330
return null;
331331

332-
var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(hasOne.Type);
332+
var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(hasOne.ResourceType);
333333
if (relatedContextEntity == null) // TODO: this should probably be a debug log at minimum
334334
return null;
335335

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,15 @@ public virtual async Task<TEntity> CreateAsync(TEntity entity)
155155
protected virtual void AttachRelationships(TEntity entity = null)
156156
{
157157
AttachHasManyPointers(entity);
158-
AttachHasOnePointers();
158+
AttachHasOnePointers(entity);
159159
}
160160

161161
/// <inheritdoc />
162162
public void DetachRelationshipPointers(TEntity entity)
163163
{
164164
foreach (var hasOneRelationship in _jsonApiContext.HasOneRelationshipPointers.Get())
165165
{
166-
_context.Entry(hasOneRelationship.Value).State = EntityState.Detached;
166+
_context.Entry(hasOneRelationship.Key.EntityType).State = EntityState.Detached;
167167
}
168168

169169
foreach (var hasManyRelationship in _jsonApiContext.HasManyRelationshipPointers.Get())
@@ -227,12 +227,18 @@ private void AttachHasManyThrough(TEntity entity, HasManyThroughAttribute hasMan
227227
/// This is used to allow creation of HasOne relationships when the
228228
/// independent side of the relationship already exists.
229229
/// </summary>
230-
private void AttachHasOnePointers()
230+
private void AttachHasOnePointers(TEntity entity)
231231
{
232232
var relationships = _jsonApiContext.HasOneRelationshipPointers.Get();
233233
foreach (var relationship in relationships)
234-
if (_context.Entry(relationship.Value).State == EntityState.Detached && _context.EntityIsTracked(relationship.Value) == false)
235-
_context.Entry(relationship.Value).State = EntityState.Unchanged;
234+
{
235+
var relatedEntityType = relationship.Key.EntityType; // DepartmentEntity
236+
var relatedEntityMember = typeof(TEntity).GetProperties().SingleOrDefault(p => p.PropertyType == relatedEntityType)?.Name;
237+
var relatedEntity = relatedEntityMember == null ? null : entity.GetType().GetProperty(relatedEntityMember)?.GetValue(entity);
238+
239+
if (relatedEntity != null && _context.Entry(relatedEntity).State == EntityState.Detached && _context.EntityIsTracked((IIdentifiable)relatedEntity) == false)
240+
_context.Entry(relatedEntity).State = EntityState.Unchanged;
241+
}
236242
}
237243

238244
/// <inheritdoc />
@@ -265,7 +271,7 @@ public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute
265271
// of the property...
266272
var typeToUpdate = (relationship is HasManyThroughAttribute hasManyThrough)
267273
? hasManyThrough.ThroughType
268-
: relationship.Type;
274+
: relationship.ResourceType;
269275

270276
var genericProcessor = _genericProcessorFactory.GetProcessor<IGenericProcessor>(typeof(GenericProcessor<>), typeToUpdate);
271277
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
@@ -317,7 +323,7 @@ public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, string
317323
: $"{internalRelationshipPath}.{relationship.RelationshipPath}";
318324

319325
if(i < relationshipChain.Length)
320-
entity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type);
326+
entity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.ResourceType);
321327
}
322328

323329
return entities.Include(internalRelationshipPath);

src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ private static IQueryable<TSource> CallGenericWhereMethod<TSource>(IQueryable<TS
254254
if (relationProperty == null)
255255
throw new ArgumentException($"'{filter.Relationship.InternalRelationshipName}' is not a valid relationship of '{concreteType}'");
256256

257-
var relatedType = filter.Relationship.Type;
257+
var relatedType = filter.Relationship.ResourceType;
258258
property = relatedType.GetProperty(filter.Attribute.InternalAttributeName);
259259
if (property == null)
260260
throw new ArgumentException($"'{filter.Attribute.InternalAttributeName}' is not a valid attribute of '{filter.Relationship.InternalRelationshipName}'");

src/JsonApiDotNetCore/Models/HasManyAttribute.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class HasManyAttribute : RelationshipAttribute
1111
/// <param name="publicName">The relationship name as exposed by the API</param>
1212
/// <param name="documentLinks">Which links are available. Defaults to <see cref="Link.All"/></param>
1313
/// <param name="canInclude">Whether or not this relationship can be included using the <c>?include=public-name</c> query string</param>
14+
/// <param name="withEntityType">If the entity model of this relationship refers to a different type, specify that here</param>
1415
///
1516
/// <example>
1617
///
@@ -23,8 +24,8 @@ public class HasManyAttribute : RelationshipAttribute
2324
/// </code>
2425
///
2526
/// </example>
26-
public HasManyAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true)
27-
: base(publicName, documentLinks, canInclude)
27+
public HasManyAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, Type withEntityType = null)
28+
: base(publicName, documentLinks, canInclude, withEntityType)
2829
{ }
2930

3031
/// <summary>

src/JsonApiDotNetCore/Models/HasOneAttribute.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class HasOneAttribute : RelationshipAttribute
1111
///
1212
/// <param name="publicName">The relationship name as exposed by the API</param>
1313
/// <param name="documentLinks">Which links are available. Defaults to <see cref="Link.All"/></param>
14+
/// <param name="withEntityType">If the entity model of this relationship refers to a different type, specify that here</param>
1415
/// <param name="canInclude">Whether or not this relationship can be included using the <c>?include=public-name</c> query string</param>
1516
/// <param name="withForeignKey">The foreign key property name. Defaults to <c>"{RelationshipName}Id"</c></param>
1617
///
@@ -20,21 +21,21 @@ public class HasOneAttribute : RelationshipAttribute
2021
/// <code>
2122
/// public class Article : Identifiable
2223
/// {
23-
/// [HasOne("author", withForiegnKey: nameof(AuthorKey)]
24+
/// [HasOne("author", withForeignKey: nameof(AuthorKey)]
2425
/// public Author Author { get; set; }
2526
/// public int AuthorKey { get; set; }
2627
/// }
2728
/// </code>
2829
///
2930
/// </example>
30-
public HasOneAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, string withForeignKey = null)
31-
: base(publicName, documentLinks, canInclude)
31+
public HasOneAttribute(string publicName = null, Link documentLinks = Link.All, Type withEntityType = null, bool canInclude = true, string withForeignKey = null)
32+
: base(publicName, documentLinks, canInclude, withEntityType)
3233
{
3334
_explicitIdentifiablePropertyName = withForeignKey;
3435
}
3536

3637
private readonly string _explicitIdentifiablePropertyName;
37-
38+
3839
/// <summary>
3940
/// The independent resource identifier.
4041
/// </summary>
@@ -49,7 +50,7 @@ public HasOneAttribute(string publicName = null, Link documentLinks = Link.All,
4950
/// <param name="newValue">The new property value</param>
5051
public override void SetValue(object resource, object newValue)
5152
{
52-
var propertyName = (newValue?.GetType() == Type)
53+
var propertyName = (newValue?.GetType() == ResourceType)
5354
? InternalRelationshipName
5455
: IdentifiablePropertyName;
5556

src/JsonApiDotNetCore/Models/RelationshipAttribute.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ namespace JsonApiDotNetCore.Models
66
{
77
public abstract class RelationshipAttribute : Attribute
88
{
9-
protected RelationshipAttribute(string publicName, Link documentLinks, bool canInclude)
9+
protected RelationshipAttribute(string publicName, Link documentLinks, bool canInclude, Type withEntityType)
1010
{
1111
PublicRelationshipName = publicName;
1212
DocumentLinks = documentLinks;
1313
CanInclude = canInclude;
14+
_entityType = withEntityType;
1415
}
1516

1617
public string PublicRelationshipName { get; internal set; }
@@ -26,7 +27,11 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI
2627
/// public List&lt;Tag&gt; Tags { get; set; } // Type => Tag
2728
/// </code>
2829
/// </example>
29-
public Type Type { get; internal set; }
30+
public Type ResourceType { get; internal set; }
31+
32+
private readonly Type _entityType;
33+
public Type EntityType => _entityType == null ? ResourceType : _entityType;
34+
3035
public bool IsHasMany => GetType() == typeof(HasManyAttribute) || GetType().Inherits(typeof(HasManyAttribute));
3136
public bool IsHasOne => GetType() == typeof(HasOneAttribute);
3237
public Link DocumentLinks { get; } = Link.All;
@@ -55,10 +60,10 @@ public bool TryGetHasMany(out HasManyAttribute result)
5560
}
5661

5762
public abstract void SetValue(object entity, object newValue);
58-
63+
5964
public object GetValue(object entity) => entity
60-
?.GetType()
61-
.GetProperty(InternalRelationshipName)
65+
?.GetType()?
66+
.GetProperty(InternalRelationshipName)?
6267
.GetValue(entity);
6368

6469
public override string ToString()

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ private object SetHasOneRelationship(object entity,
218218
if(included != null)
219219
{
220220
var navigationPropertyValue = attr.GetValue(entity);
221-
var resourceGraphEntity = _jsonApiContext.ResourceGraph.GetContextEntity(attr.Type);
221+
var resourceGraphEntity = _jsonApiContext.ResourceGraph.GetContextEntity(attr.ResourceType);
222222
if(navigationPropertyValue != null && resourceGraphEntity != null)
223223
{
224224
var includedResource = included.SingleOrDefault(r => r.Type == rio.Type && r.Id == rio.Id);
@@ -292,7 +292,7 @@ private object SetHasManyRelationship(object entity,
292292
return instance;
293293
});
294294

295-
var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.Type);
295+
var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.ResourceType);
296296

297297
attr.SetValue(entity, convertedCollection);
298298

@@ -305,7 +305,7 @@ private object SetHasManyRelationship(object entity,
305305
private IIdentifiable GetIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier, List<ResourceObject> includedResources, RelationshipAttribute relationshipAttr)
306306
{
307307
// at this point we can be sure the relationshipAttr.Type is IIdentifiable because we were able to successfully build the ResourceGraph
308-
var relatedInstance = relationshipAttr.Type.New<IIdentifiable>();
308+
var relatedInstance = relationshipAttr.ResourceType.New<IIdentifiable>();
309309
relatedInstance.StringId = relatedResourceIdentifier.Id;
310310

311311
// can't provide any more data other than the rio since it is not contained in the included section
@@ -316,9 +316,9 @@ private IIdentifiable GetIncludedRelationship(ResourceIdentifierObject relatedRe
316316
if (includedResource == null)
317317
return relatedInstance;
318318

319-
var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationshipAttr.Type);
319+
var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationshipAttr.ResourceType);
320320
if (contextEntity == null)
321-
throw new JsonApiException(400, $"Included type '{relationshipAttr.Type}' is not a registered json:api resource.");
321+
throw new JsonApiException(400, $"Included type '{relationshipAttr.ResourceType}' is not a registered json:api resource.");
322322

323323
SetEntityAttributes(relatedInstance, contextEntity, includedResource.Attributes);
324324

src/JsonApiDotNetCore/Services/EntityResourceService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa
170170
.Relationships
171171
.FirstOrDefault(r => r.Is(relationshipName));
172172

173-
var relationshipType = relationship.Type;
173+
var relationshipType = relationship.ResourceType;
174174

175175
// update relationship type with internalname
176176
var entityProperty = typeof(TEntity).GetProperty(relationship.InternalRelationshipName);
@@ -180,15 +180,15 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa
180180
$"could not be found on entity.");
181181
}
182182

183-
relationship.Type = relationship.IsHasMany
183+
relationship.ResourceType = relationship.IsHasMany
184184
? entityProperty.PropertyType.GetGenericArguments()[0]
185185
: entityProperty.PropertyType;
186186

187187
var relationshipIds = relationships.Select(r => r?.Id?.ToString());
188188

189189
await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds);
190190

191-
relationship.Type = relationshipType;
191+
relationship.ResourceType = relationshipType;
192192
}
193193

194194
protected virtual async Task<IEnumerable<TResource>> ApplyPageQueryAsync(IQueryable<TEntity> entities)

src/JsonApiDotNetCore/Services/Operations/Processors/GetOpProcessor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ private async Task<object> GetRelationshipAsync(Operation operation)
135135
// TODO: need a better way to get the ContextEntity from a relationship name
136136
// when no generic parameter is available
137137
var relationshipType = _resourceGraph.GetContextEntity(operation.GetResourceTypeName())
138-
.Relationships.Single(r => r.Is(operation.Ref.Relationship)).Type;
138+
.Relationships.Single(r => r.Is(operation.Ref.Relationship)).ResourceType;
139139

140140
var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationshipType);
141141

0 commit comments

Comments
 (0)