Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e58a48d

Browse files
Merge pull request #446 from roblankey/fix/#445
fix/#445: create resource with relationship in Entity-Resource separation mode
2 parents cd02b20 + bf3cec4 commit e58a48d

File tree

11 files changed

+201
-39
lines changed

11 files changed

+201
-39
lines changed

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,6 @@ _Pvt_Extensions
234234

235235
# FAKE - F# Make
236236
.fake/
237+
238+
### Rider ###
239+
.idea/

‎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",mappedBy:"Department")]
2122
public DepartmentResource Department { get; set; }
2223
public int? DepartmentId { get; set; }
2324

‎src/Examples/JsonApiDotNetCoreExample/Models/Resources/DepartmentResource.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class DepartmentResource : Identifiable
88
[Attr("name")]
99
public string Name { get; set; }
1010

11-
[HasMany("courses")]
11+
[HasMany("courses",mappedBy:"Courses")]
1212
public List<CourseResource> Courses { get; set; }
1313
}
1414
}

‎src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
168168
attribute.Type = 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}'.");
@@ -211,13 +211,8 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
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/Data/DefaultEntityRepository.cs‎

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -155,24 +155,46 @@ 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+
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())
170180
{
171-
foreach (var pointer in hasManyRelationship.Value)
181+
var hasMany = (HasManyAttribute) hasManyRelationship.Key;
182+
if (hasMany.EntityPropertyName != null)
172183
{
173-
_context.Entry(pointer).State = EntityState.Detached;
184+
var relatedList = (IList)entity.GetType().GetProperty(hasMany.EntityPropertyName)?.GetValue(entity);
185+
foreach (var related in relatedList)
186+
{
187+
_context.Entry(related).State = EntityState.Detached;
188+
}
174189
}
175-
190+
else
191+
{
192+
foreach (var pointer in hasManyRelationship.Value)
193+
{
194+
_context.Entry(pointer).State = EntityState.Detached;
195+
}
196+
}
197+
176198
// HACK: detaching has many relationships doesn't appear to be sufficient
177199
// the navigation property actually needs to be nulled out, otherwise
178200
// EF adds duplicate instances to the collection
@@ -192,14 +214,27 @@ private void AttachHasManyPointers(TEntity entity)
192214
if (relationship.Key is HasManyThroughAttribute hasManyThrough)
193215
AttachHasManyThrough(entity, hasManyThrough, relationship.Value);
194216
else
195-
AttachHasMany(relationship.Key as HasManyAttribute, relationship.Value);
217+
AttachHasMany(entity,relationship.Key as HasManyAttribute, relationship.Value);
196218
}
197219
}
198220

199-
private void AttachHasMany(HasManyAttribute relationship, IList pointers)
221+
private void AttachHasMany(TEntityentity,HasManyAttribute relationship, IList pointers)
200222
{
201-
foreach (var pointer in pointers)
202-
_context.Entry(pointer).State = EntityState.Unchanged;
223+
if (relationship.EntityPropertyName != null)
224+
{
225+
var relatedList = (IList)entity.GetType().GetProperty(relationship.EntityPropertyName)?.GetValue(entity);
226+
foreach (var related in relatedList)
227+
{
228+
_context.Entry(related).State = EntityState.Unchanged;
229+
}
230+
}
231+
else
232+
{
233+
foreach (var pointer in pointers)
234+
{
235+
_context.Entry(pointer).State = EntityState.Unchanged;
236+
}
237+
}
203238
}
204239

205240
private void AttachHasManyThrough(TEntity entity, HasManyThroughAttribute hasManyThrough, IList pointers)
@@ -227,12 +262,27 @@ private void AttachHasManyThrough(TEntity entity, HasManyThroughAttribute hasMan
227262
/// This is used to allow creation of HasOne relationships when the
228263
/// independent side of the relationship already exists.
229264
/// </summary>
230-
private void AttachHasOnePointers()
265+
private void AttachHasOnePointers(TEntityentity)
231266
{
232267
var relationships = _jsonApiContext.HasOneRelationshipPointers.Get();
233268
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;
269+
{
270+
if (relationship.Key.GetType() != typeof(HasOneAttribute))
271+
continue;
272+
273+
var hasOne = (HasOneAttribute) relationship.Key;
274+
if (hasOne.EntityPropertyName != null)
275+
{
276+
var relatedEntity = entity.GetType().GetProperty(hasOne.EntityPropertyName)?.GetValue(entity);
277+
if (relatedEntity != null && _context.Entry(relatedEntity).State == EntityState.Detached && _context.EntityIsTracked((IIdentifiable)relatedEntity) == false)
278+
_context.Entry(relatedEntity).State = EntityState.Unchanged;
279+
}
280+
else
281+
{
282+
if (_context.Entry(relationship.Value).State == EntityState.Detached && _context.EntityIsTracked(relationship.Value) == false)
283+
_context.Entry(relationship.Value).State = EntityState.Unchanged;
284+
}
285+
}
236286
}
237287

238288
/// <inheritdoc />

‎src/JsonApiDotNetCore/JsonApiDotNetCore.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<VersionPrefix>3.0.0</VersionPrefix>
3+
<VersionPrefix>3.0.1</VersionPrefix>
44
<TargetFrameworks>$(NetStandardVersion)</TargetFrameworks>
55
<AssemblyName>JsonApiDotNetCore</AssemblyName>
66
<PackageId>JsonApiDotNetCore</PackageId>

‎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="mappedBy">The name of the entity mapped property, defaults to null</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,stringmappedBy=null)
28+
: base(publicName, documentLinks, canInclude,mappedBy)
2829
{ }
2930

3031
/// <summary>

‎src/JsonApiDotNetCore/Models/HasManyThroughAttribute.cs‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Reflection;
3+
using System.Security;
34

45
namespace JsonApiDotNetCore.Models
56
{
@@ -30,14 +31,15 @@ public class HasManyThroughAttribute : HasManyAttribute
3031
/// <param name="internalThroughName">The name of the navigation property that will be used to get the HasMany relationship</param>
3132
/// <param name="documentLinks">Which links are available. Defaults to <see cref="Link.All"/></param>
3233
/// <param name="canInclude">Whether or not this relationship can be included using the <c>?include=public-name</c> query string</param>
34+
/// <param name="mappedBy">The name of the entity mapped property, defaults to null</param>
3335
///
3436
/// <example>
3537
/// <code>
3638
/// [HasManyThrough(nameof(ArticleTags), documentLinks: Link.All, canInclude: true)]
3739
/// </code>
3840
/// </example>
39-
public HasManyThroughAttribute(string internalThroughName, Link documentLinks = Link.All, bool canInclude = true)
40-
: base(null, documentLinks, canInclude)
41+
public HasManyThroughAttribute(string internalThroughName, Link documentLinks = Link.All, bool canInclude = true,stringmappedBy=null)
42+
: base(null, documentLinks, canInclude,mappedBy)
4143
{
4244
InternalThroughName = internalThroughName;
4345
}
@@ -50,14 +52,15 @@ public HasManyThroughAttribute(string internalThroughName, Link documentLinks =
5052
/// <param name="internalThroughName">The name of the navigation property that will be used to get the HasMany relationship</param>
5153
/// <param name="documentLinks">Which links are available. Defaults to <see cref="Link.All"/></param>
5254
/// <param name="canInclude">Whether or not this relationship can be included using the <c>?include=public-name</c> query string</param>
55+
/// <param name="mappedBy">The name of the entity mapped property, defaults to null</param>
5356
///
5457
/// <example>
5558
/// <code>
5659
/// [HasManyThrough("tags", nameof(ArticleTags), documentLinks: Link.All, canInclude: true)]
5760
/// </code>
5861
/// </example>
59-
public HasManyThroughAttribute(string publicName, string internalThroughName, Link documentLinks = Link.All, bool canInclude = true)
60-
: base(publicName, documentLinks, canInclude)
62+
public HasManyThroughAttribute(string publicName, string internalThroughName, Link documentLinks = Link.All, bool canInclude = true,stringmappedBy=null)
63+
: base(publicName, documentLinks, canInclude,mappedBy)
6164
{
6265
InternalThroughName = internalThroughName;
6366
}
@@ -161,4 +164,4 @@ public HasManyThroughAttribute(string publicName, string internalThroughName, Li
161164
/// </example>
162165
public override string RelationshipPath => $"{InternalThroughName}.{RightProperty.Name}";
163166
}
164-
}
167+
}

‎src/JsonApiDotNetCore/Models/HasOneAttribute.cs‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,29 @@ public class HasOneAttribute : RelationshipAttribute
1313
/// <param name="documentLinks">Which links are available. Defaults to <see cref="Link.All"/></param>
1414
/// <param name="canInclude">Whether or not this relationship can be included using the <c>?include=public-name</c> query string</param>
1515
/// <param name="withForeignKey">The foreign key property name. Defaults to <c>"{RelationshipName}Id"</c></param>
16+
/// <param name="mappedBy">The name of the entity mapped property, defaults to null</param>
1617
///
1718
/// <example>
1819
/// Using an alternative foreign key:
1920
///
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, bool canInclude = true, string withForeignKey = null,stringmappedBy=null)
32+
: base(publicName, documentLinks, canInclude,mappedBy)
3233
{
3334
_explicitIdentifiablePropertyName = withForeignKey;
3435
}
3536

3637
private readonly string _explicitIdentifiablePropertyName;
37-
38+
3839
/// <summary>
3940
/// The independent resource identifier.
4041
/// </summary>

‎src/JsonApiDotNetCore/Models/RelationshipAttribute.cs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ 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,stringmappedBy)
1010
{
1111
PublicRelationshipName = publicName;
1212
DocumentLinks = documentLinks;
1313
CanInclude = canInclude;
14+
EntityPropertyName = mappedBy;
1415
}
1516

1617
public string PublicRelationshipName { get; internal set; }
17-
public string InternalRelationshipName { get; internal set; }
18+
public string InternalRelationshipName { get; internal set; }
1819

1920
/// <summary>
2021
/// The related entity type. This does not necessarily match the navigation property type.
@@ -31,6 +32,7 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI
3132
public bool IsHasOne => GetType() == typeof(HasOneAttribute);
3233
public Link DocumentLinks { get; } = Link.All;
3334
public bool CanInclude { get; }
35+
public string EntityPropertyName { get; }
3436

3537
public bool TryGetHasOne(out HasOneAttribute result)
3638
{
@@ -55,10 +57,10 @@ public bool TryGetHasMany(out HasManyAttribute result)
5557
}
5658

5759
public abstract void SetValue(object entity, object newValue);
58-
60+
5961
public object GetValue(object entity) => entity
60-
?.GetType()
61-
.GetProperty(InternalRelationshipName)
62+
?.GetType()?
63+
.GetProperty(InternalRelationshipName)?
6264
.GetValue(entity);
6365

6466
public override string ToString()

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /