Skip to content

Commit b247d15

Browse files
authored
Merge pull request #44 from Research-Institute/relationship-attributes
Relationship attributes
2 parents a683713 + 9d6c4a1 commit b247d15

File tree

24 files changed

+131
-92
lines changed

24 files changed

+131
-92
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,15 @@ public class Person : Identifiable<int>
113113
#### Relationships
114114

115115
In order for navigation properties to be identified in the model,
116-
they should be labeled as virtual.
116+
they should be labeled with the appropriate attribute (either `HasOne` or `HasMany`).
117117

118118
```csharp
119119
public class Person : Identifiable<int>
120120
{
121121
[Attr("first-name")]
122122
public string FirstName { get; set; }
123123

124+
[HasMany("todo-items")]
124125
public virtual List<TodoItem> TodoItems { get; set; }
125126
}
126127
```
@@ -135,6 +136,8 @@ public class TodoItem : Identifiable<int>
135136
public string Description { get; set; }
136137

137138
public int OwnerId { get; set; }
139+
140+
[HasOne("owner")]
138141
public virtual Person Owner { get; set; }
139142
}
140143
```

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,25 +124,25 @@ private void _addRelationships(DocumentData data, ContextEntity contextEntity, I
124124
{
125125
Links = new Links
126126
{
127-
Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.RelationshipName),
128-
Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.RelationshipName)
127+
Self = linkBuilder.GetSelfRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.InternalRelationshipName),
128+
Related = linkBuilder.GetRelatedRelationLink(contextEntity.EntityName, entity.Id.ToString(), r.InternalRelationshipName)
129129
}
130130
};
131131

132-
if (_relationshipIsIncluded(r.RelationshipName))
132+
if (_relationshipIsIncluded(r.InternalRelationshipName))
133133
{
134134
var navigationEntity = _jsonApiContext.ContextGraph
135-
.GetRelationship(entity, r.RelationshipName);
135+
.GetRelationship(entity, r.InternalRelationshipName);
136136

137137
if(navigationEntity == null)
138138
relationshipData.SingleData = null;
139139
else if (navigationEntity is IEnumerable)
140-
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.RelationshipName);
140+
relationshipData.ManyData = _getRelationships((IEnumerable<object>)navigationEntity, r.InternalRelationshipName);
141141
else
142-
relationshipData.SingleData = _getRelationship(navigationEntity, r.RelationshipName);
142+
relationshipData.SingleData = _getRelationship(navigationEntity, r.InternalRelationshipName);
143143
}
144144

145-
data.Relationships.Add(r.RelationshipName.Dasherize(), relationshipData);
145+
data.Relationships.Add(r.InternalRelationshipName.Dasherize(), relationshipData);
146146
});
147147
}
148148

@@ -152,9 +152,9 @@ private List<DocumentData> _getIncludedEntities(ContextEntity contextEntity, IId
152152

153153
contextEntity.Relationships.ForEach(r =>
154154
{
155-
if (!_relationshipIsIncluded(r.RelationshipName)) return;
155+
if (!_relationshipIsIncluded(r.InternalRelationshipName)) return;
156156

157-
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.RelationshipName);
157+
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.InternalRelationshipName);
158158

159159
if (navigationEntity is IEnumerable)
160160
foreach (var includedEntity in (IEnumerable)navigationEntity)

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public virtual async Task<IActionResult> PatchRelationshipsAsync(TId id, string
183183
var relationship = _jsonApiContext.ContextGraph
184184
.GetContextEntity(typeof(T))
185185
.Relationships
186-
.FirstOrDefault(r => r.RelationshipName == relationshipName);
186+
.FirstOrDefault(r => r.InternalRelationshipName == relationshipName);
187187

188188
var relationshipIds = relationships.Select(r=>r.Id);
189189

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
108108
return oldEntity;
109109
}
110110

111-
public async Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds)
111+
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds)
112112
{
113-
var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.BaseType, _context);
113+
var genericProcessor = GenericProcessorFactory.GetProcessor(relationship.Type, _context);
114114
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
115115
}
116116

@@ -131,7 +131,7 @@ public virtual async Task<bool> DeleteAsync(TId id)
131131
public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, string relationshipName)
132132
{
133133
var entity = _jsonApiContext.RequestEntity;
134-
if(entity.Relationships.Any(r => r.RelationshipName == relationshipName))
134+
if(entity.Relationships.Any(r => r.InternalRelationshipName == relationshipName))
135135
return entities.Include(relationshipName);
136136

137137
throw new JsonApiException("400", "Invalid relationship",

src/JsonApiDotNetCore/Data/IEntityRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public interface IEntityRepository<TEntity, in TId>
3434

3535
Task<TEntity> UpdateAsync(TId id, TEntity entity);
3636

37-
Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds);
37+
Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds);
3838

3939
Task<bool> DeleteAsync(TId id);
4040
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using JsonApiDotNetCore.Models;
34

45
namespace JsonApiDotNetCore.Internal
56
{
@@ -8,6 +9,6 @@ public class ContextEntity
89
public string EntityName { get; set; }
910
public Type EntityType { get; set; }
1011
public List<AttrAttribute> Attributes { get; set; }
11-
public List<Relationship> Relationships { get; set; }
12+
public List<RelationshipAttribute> Relationships { get; set; }
1213
}
1314
}

src/JsonApiDotNetCore/Internal/ContextGraph.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public string GetRelationshipName<TParent>(string relationshipName)
4646
e.EntityType == entityType)
4747
.Relationships
4848
.FirstOrDefault(r =>
49-
r.RelationshipName.ToLower() == relationshipName.ToLower())
50-
?.RelationshipName;
49+
r.InternalRelationshipName.ToLower() == relationshipName.ToLower())
50+
?.InternalRelationshipName;
5151
}
5252
}
5353
}

src/JsonApiDotNetCore/Internal/ContextGraphBuilder.cs

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Reflection;
55
using Microsoft.EntityFrameworkCore;
6+
using JsonApiDotNetCore.Models;
67

78
namespace JsonApiDotNetCore.Internal
89
{
@@ -14,7 +15,6 @@ public class ContextGraphBuilder<T> where T : DbContext
1415
public ContextGraph<T> Build()
1516
{
1617
_getFirstLevelEntities();
17-
_loadRelationships();
1818

1919
var graph = new ContextGraph<T>
2020
{
@@ -41,7 +41,8 @@ private void _getFirstLevelEntities()
4141
entities.Add(new ContextEntity {
4242
EntityName = property.Name,
4343
EntityType = entityType,
44-
Attributes = _getAttributes(entityType)
44+
Attributes = _getAttributes(entityType),
45+
Relationships = _getRelationships(entityType)
4546
});
4647
}
4748
}
@@ -65,38 +66,29 @@ private List<AttrAttribute> _getAttributes(Type entityType)
6566
return attributes;
6667
}
6768

68-
private void _loadRelationships()
69-
{
70-
_entities.ForEach(entity => {
71-
72-
var relationships = new List<Relationship>();
73-
var properties = entity.EntityType.GetProperties();
74-
75-
foreach(var entityProperty in properties)
76-
{
77-
var propertyType = entityProperty.PropertyType;
78-
79-
if(_isValidEntity(propertyType)
80-
|| (propertyType.GetTypeInfo().IsGenericType && _isValidEntity(propertyType.GetGenericArguments()[0])))
81-
relationships.Add(_getRelationshipFromPropertyInfo(entityProperty));
82-
}
83-
84-
entity.Relationships = relationships;
85-
});
86-
}
87-
88-
private bool _isValidEntity(Type type)
69+
private List<RelationshipAttribute> _getRelationships(Type entityType)
8970
{
90-
var validEntityRelationshipTypes = _entities.Select(e => e.EntityType);
91-
return validEntityRelationshipTypes.Contains(type);
71+
var attributes = new List<RelationshipAttribute>();
72+
73+
var properties = entityType.GetProperties();
74+
75+
foreach(var prop in properties)
76+
{
77+
var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute));
78+
if(attribute == null) continue;
79+
attribute.InternalRelationshipName = prop.Name;
80+
attribute.Type = _getRelationshipType(attribute, prop);
81+
attributes.Add(attribute);
82+
}
83+
return attributes;
9284
}
9385

94-
private Relationship _getRelationshipFromPropertyInfo(PropertyInfo propertyInfo)
86+
private Type _getRelationshipType(RelationshipAttribute relation, PropertyInfo prop)
9587
{
96-
return new Relationship {
97-
Type = propertyInfo.PropertyType,
98-
RelationshipName = propertyInfo.Name
99-
};
88+
if(relation.IsHasMany)
89+
return prop.PropertyType.GetGenericArguments()[0];
90+
else
91+
return prop.PropertyType;
10092
}
10193
}
10294
}

src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ public GenericProcessor(DbContext context)
1717
_context = context;
1818
}
1919

20-
public async Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds)
20+
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds)
2121
{
22-
var relationshipType = relationship.BaseType;
22+
var relationshipType = relationship.Type;
2323

24-
// TODO: replace with relationship.IsMany
25-
if(relationship.Type.GetInterfaces().Contains(typeof(IEnumerable)))
24+
if(relationship.IsHasMany)
2625
{
2726
var entities = _context.GetDbSet<T>().Where(x => relationshipIds.Contains(x.Id.ToString())).ToList();
2827
relationship.SetValue(parent, entities);
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using System.Collections.Generic;
22
using System.Threading.Tasks;
3+
using JsonApiDotNetCore.Models;
34

45
namespace JsonApiDotNetCore.Internal
56
{
67
public interface IGenericProcessor
78
{
8-
Task UpdateRelationshipsAsync(object parent, Relationship relationship, IEnumerable<string> relationshipIds);
9+
Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds);
910
}
1011
}

0 commit comments

Comments
 (0)