Skip to content

Commit a7a22bb

Browse files
committed
re-change the typing since it was causing issues when the entity had more than one property with that type
1 parent 649648f commit a7a22bb

File tree

12 files changed

+61
-41
lines changed

12 files changed

+61
-41
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class CourseResource : Identifiable
1818
[Attr("description")]
1919
public string Description { get; set; }
2020

21-
[HasOne("department", withEntityType: typeof(DepartmentEntity))]
21+
[HasOne("department", withEntity: "Department")]
2222
public DepartmentResource Department { get; set; }
2323
public int? DepartmentId { get; set; }
2424

src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs

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

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

171171
if (attribute is HasManyThroughAttribute hasManyThroughAttribute) {
@@ -198,13 +198,13 @@ 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.ResourceType)
202-
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.ResourceType}");
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}");
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.ResourceType} with name {rightIdPropertyName}");
207+
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a relationship id property to type {hasManyThroughAttribute.Type} with name {rightIdPropertyName}");
208208
}
209209
}
210210

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ private List<ResourceObject> IncludeSingleResourceRelationships(
240240
{
241241
if (relationshipChainIndex < relationshipChain.Length)
242242
{
243-
var nextContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.ResourceType);
243+
var nextContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type);
244244
var resource = (IIdentifiable)navigationEntity;
245245
// recursive call
246246
if (relationshipChainIndex < relationshipChain.Length - 1)
@@ -327,7 +327,7 @@ private ResourceIdentifierObject GetIndependentRelationshipIdentifier(HasOneAttr
327327
if (independentRelationshipIdentifier == null)
328328
return null;
329329

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

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,17 @@ public void DetachRelationshipPointers(TEntity entity)
163163
{
164164
foreach (var hasOneRelationship in _jsonApiContext.HasOneRelationshipPointers.Get())
165165
{
166-
_context.Entry(hasOneRelationship.Key.EntityType).State = EntityState.Detached;
166+
var hasOne = (HasOneAttribute) hasOneRelationship.Key;
167+
if (hasOne.EntityPropertyName != null)
168+
{
169+
var relatedEntity = entity.GetType().GetProperty(hasOne.EntityPropertyName)?.GetValue(entity);
170+
if (relatedEntity != null)
171+
_context.Entry(relatedEntity).State = EntityState.Detached;
172+
}
173+
else
174+
{
175+
_context.Entry(hasOneRelationship.Value).State = EntityState.Detached;
176+
}
167177
}
168178

169179
foreach (var hasManyRelationship in _jsonApiContext.HasManyRelationshipPointers.Get())
@@ -232,12 +242,21 @@ private void AttachHasOnePointers(TEntity entity)
232242
var relationships = _jsonApiContext.HasOneRelationshipPointers.Get();
233243
foreach (var relationship in relationships)
234244
{
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;
245+
if (relationship.Key.GetType() != typeof(HasOneAttribute))
246+
continue;
247+
248+
var hasOne = (HasOneAttribute) relationship.Key;
249+
if (hasOne.EntityPropertyName != null)
250+
{
251+
var relatedEntity = entity.GetType().GetProperty(hasOne.EntityPropertyName)?.GetValue(entity);
252+
if (relatedEntity != null && _context.Entry(relatedEntity).State == EntityState.Detached && _context.EntityIsTracked((IIdentifiable)relatedEntity) == false)
253+
_context.Entry(relatedEntity).State = EntityState.Unchanged;
254+
}
255+
else
256+
{
257+
if (_context.Entry(relationship.Value).State == EntityState.Detached && _context.EntityIsTracked(relationship.Value) == false)
258+
_context.Entry(relationship.Value).State = EntityState.Unchanged;
259+
}
241260
}
242261
}
243262

@@ -271,7 +290,7 @@ public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute
271290
// of the property...
272291
var typeToUpdate = (relationship is HasManyThroughAttribute hasManyThrough)
273292
? hasManyThrough.ThroughType
274-
: relationship.ResourceType;
293+
: relationship.Type;
275294

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

325344
if(i < relationshipChain.Length)
326-
entity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.ResourceType);
345+
entity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type);
327346
}
328347

329348
return entities.Include(internalRelationshipPath);

src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs

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

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

src/JsonApiDotNetCore/Internal/Query/BaseAttrQuery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private RelationshipAttribute GetRelationship(string propertyName)
4848

4949
private AttrAttribute GetAttribute(RelationshipAttribute relationship, string attribute)
5050
{
51-
var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.ResourceType);
51+
var relatedContextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type);
5252
return relatedContextEntity.Attributes
5353
.FirstOrDefault(a => a.Is(attribute));
5454
}

src/JsonApiDotNetCore/Models/HasManyAttribute.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ 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>
1514
///
1615
/// <example>
1716
///
@@ -24,8 +23,8 @@ public class HasManyAttribute : RelationshipAttribute
2423
/// </code>
2524
///
2625
/// </example>
27-
public HasManyAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, Type withEntityType = null)
28-
: base(publicName, documentLinks, canInclude, withEntityType)
26+
public HasManyAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true)
27+
: base(publicName, documentLinks, canInclude)
2928
{ }
3029

3130
/// <summary>

src/JsonApiDotNetCore/Models/HasOneAttribute.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ 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>
1514
/// <param name="canInclude">Whether or not this relationship can be included using the <c>?include=public-name</c> query string</param>
1615
/// <param name="withForeignKey">The foreign key property name. Defaults to <c>"{RelationshipName}Id"</c></param>
16+
/// <param name="withEntity">If the entity model of this relationship refers to a different type, specify that here</param>
1717
///
1818
/// <example>
1919
/// Using an alternative foreign key:
@@ -28,13 +28,15 @@ public class HasOneAttribute : RelationshipAttribute
2828
/// </code>
2929
///
3030
/// </example>
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)
31+
public HasOneAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, string withForeignKey = null, string withEntity = null)
32+
: base(publicName, documentLinks, canInclude)
3333
{
3434
_explicitIdentifiablePropertyName = withForeignKey;
35+
EntityPropertyName = withEntity;
3536
}
3637

3738
private readonly string _explicitIdentifiablePropertyName;
39+
private readonly string _relatedEntityPropertyName;
3840

3941
/// <summary>
4042
/// The independent resource identifier.
@@ -43,14 +45,19 @@ public HasOneAttribute(string publicName = null, Link documentLinks = Link.All,
4345
? JsonApiOptions.RelatedIdMapper.GetRelatedIdPropertyName(InternalRelationshipName)
4446
: _explicitIdentifiablePropertyName;
4547

48+
/// <summary>
49+
/// For use in entity / resource separation when the related property is also separated
50+
/// </summary>
51+
public string EntityPropertyName { get; }
52+
4653
/// <summary>
4754
/// Sets the value of the property identified by this attribute
4855
/// </summary>
4956
/// <param name="resource">The target object</param>
5057
/// <param name="newValue">The new property value</param>
5158
public override void SetValue(object resource, object newValue)
5259
{
53-
var propertyName = (newValue?.GetType() == ResourceType)
60+
var propertyName = (newValue?.GetType() == Type)
5461
? InternalRelationshipName
5562
: IdentifiablePropertyName;
5663

src/JsonApiDotNetCore/Models/RelationshipAttribute.cs

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

1716
public string PublicRelationshipName { get; internal set; }
@@ -27,11 +26,7 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI
2726
/// public List&lt;Tag&gt; Tags { get; set; } // Type => Tag
2827
/// </code>
2928
/// </example>
30-
public Type ResourceType { get; internal set; }
31-
32-
private readonly Type _entityType;
33-
public Type EntityType => _entityType == null ? ResourceType : _entityType;
34-
29+
public Type Type { get; internal set; }
3530
public bool IsHasMany => GetType() == typeof(HasManyAttribute) || GetType().Inherits(typeof(HasManyAttribute));
3631
public bool IsHasOne => GetType() == typeof(HasOneAttribute);
3732
public Link DocumentLinks { get; } = Link.All;

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.ResourceType);
221+
var resourceGraphEntity = _jsonApiContext.ResourceGraph.GetContextEntity(attr.Type);
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.ResourceType);
295+
var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.Type);
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.ResourceType.New<IIdentifiable>();
308+
var relatedInstance = relationshipAttr.Type.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.ResourceType);
319+
var contextEntity = _jsonApiContext.ResourceGraph.GetContextEntity(relationshipAttr.Type);
320320
if (contextEntity == null)
321-
throw new JsonApiException(400, $"Included type '{relationshipAttr.ResourceType}' is not a registered json:api resource.");
321+
throw new JsonApiException(400, $"Included type '{relationshipAttr.Type}' 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.ResourceType;
173+
var relationshipType = relationship.Type;
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.ResourceType = relationship.IsHasMany
183+
relationship.Type = 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.ResourceType = relationshipType;
191+
relationship.Type = 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)).ResourceType;
138+
.Relationships.Single(r => r.Is(operation.Ref.Relationship)).Type;
139139

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

0 commit comments

Comments
 (0)