Skip to content

Commit 3751a4c

Browse files
author
Bart Koelman
committed
Fixes in resource definition callbacks
1 parent 938985f commit 3751a4c

13 files changed

+607
-14
lines changed

src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,12 @@ public QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer,
189189
primaryProjection[secondaryRelationship] = secondaryLayer;
190190
primaryProjection[primaryIdAttribute] = null;
191191

192+
var primaryFilter = GetFilter(Array.Empty<QueryExpression>(), primaryResourceContext);
193+
192194
return new QueryLayer(primaryResourceContext)
193195
{
194196
Include = RewriteIncludeForSecondaryEndpoint(innerInclude, secondaryRelationship),
195-
Filter = CreateFilterById(primaryId, primaryResourceContext),
197+
Filter = IncludeFilterById(primaryId, primaryResourceContext, primaryFilter),
196198
Projection = primaryProjection
197199
};
198200
}
@@ -206,12 +208,16 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression r
206208
return new IncludeExpression(new[] {parentElement});
207209
}
208210

209-
private FilterExpression CreateFilterById<TId>(TId id, ResourceContext resourceContext)
211+
private FilterExpression IncludeFilterById<TId>(TId id, ResourceContext resourceContext, FilterExpression existingFilter)
210212
{
211213
var primaryIdAttribute = resourceContext.Attributes.Single(a => a.Property.Name == nameof(Identifiable.Id));
212214

213-
return new ComparisonExpression(ComparisonOperator.Equals,
215+
FilterExpression filterById = new ComparisonExpression(ComparisonOperator.Equals,
214216
new ResourceFieldChainExpression(primaryIdAttribute), new LiteralConstantExpression(id.ToString()));
217+
218+
return existingFilter == null
219+
? filterById
220+
: new LogicalExpression(LogicalOperator.And, new[] {filterById, existingFilter});
215221
}
216222

217223
public IDictionary<ResourceFieldAttribute, QueryLayer> GetSecondaryProjectionForRelationshipEndpoint(ResourceContext secondaryResourceContext)

src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ private IIdentifiable GetTrackedHasOneRelationshipValue(IIdentifiable relationsh
285285
}
286286

287287
/// <inheritdoc />
288-
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds)
288+
public async Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds)
289289
{
290290
_traceWriter.LogMethodStart(new {parent, relationship, relationshipIds});
291291
if (parent == null) throw new ArgumentNullException(nameof(parent));

src/JsonApiDotNetCore/Repositories/IResourceWriteRepository.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ public interface IResourceWriteRepository<in TResource, in TId>
3232
Task UpdateAsync(TResource requestResource, TResource databaseResource);
3333

3434
/// <summary>
35-
/// Updates relationships in the underlying data store.
35+
/// Updates a relationship in the underlying data store.
3636
/// </summary>
37-
Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds);
37+
Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds);
3838

3939
/// <summary>
4040
/// Deletes a resource from the underlying data store.

src/JsonApiDotNetCore/Services/JsonApiResourceService.cs

+12-5
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ private async Task<TResource> GetPrimaryResourceById(TId id, bool allowTopSparse
156156
var primaryLayer = _queryLayerComposer.Compose(_request.PrimaryResource);
157157
primaryLayer.Sort = null;
158158
primaryLayer.Pagination = null;
159-
primaryLayer.Filter = CreateFilterById(id);
159+
primaryLayer.Filter = IncludeFilterById(id, primaryLayer.Filter);
160160

161161
if (!allowTopSparseFieldSet && primaryLayer.Projection != null)
162162
{
@@ -176,12 +176,16 @@ private async Task<TResource> GetPrimaryResourceById(TId id, bool allowTopSparse
176176
return primaryResource;
177177
}
178178

179-
private FilterExpression CreateFilterById(TId id)
179+
private FilterExpression IncludeFilterById(TId id, FilterExpression existingFilter)
180180
{
181181
var primaryIdAttribute = _request.PrimaryResource.Attributes.Single(a => a.Property.Name == nameof(Identifiable.Id));
182182

183-
return new ComparisonExpression(ComparisonOperator.Equals,
183+
FilterExpression filterById = new ComparisonExpression(ComparisonOperator.Equals,
184184
new ResourceFieldChainExpression(primaryIdAttribute), new LiteralConstantExpression(id.ToString()));
185+
186+
return existingFilter == null
187+
? filterById
188+
: new LogicalExpression(LogicalOperator.And, new[] {filterById, existingFilter});
185189
}
186190

187191
/// <inheritdoc />
@@ -279,12 +283,15 @@ public virtual async Task<TResource> UpdateAsync(TId id, TResource requestResour
279283
// triggered by PATCH /articles/1/relationships/{relationshipName}
280284
public virtual async Task UpdateRelationshipAsync(TId id, string relationshipName, object relationships)
281285
{
282-
_traceWriter.LogMethodStart(new {id, relationshipName, related = relationships});
286+
_traceWriter.LogMethodStart(new {id, relationshipName, relationships});
283287
if (relationshipName == null) throw new ArgumentNullException(nameof(relationshipName));
284288

285289
AssertRelationshipExists(relationshipName);
286290

287291
var secondaryLayer = _queryLayerComposer.Compose(_request.SecondaryResource);
292+
secondaryLayer.Projection = _queryLayerComposer.GetSecondaryProjectionForRelationshipEndpoint(_request.SecondaryResource);
293+
secondaryLayer.Include = null;
294+
288295
var primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship);
289296
primaryLayer.Projection = null;
290297

@@ -306,7 +313,7 @@ public virtual async Task UpdateRelationshipAsync(TId id, string relationshipNam
306313
: ((IEnumerable<IIdentifiable>) relationships).Select(e => e.StringId).ToArray();
307314
}
308315

309-
await _repository.UpdateRelationshipsAsync(primaryResource, _request.Relationship, relationshipIds ?? Array.Empty<string>());
316+
await _repository.UpdateRelationshipAsync(primaryResource, _request.Relationship, relationshipIds ?? Array.Empty<string>());
310317

311318
if (_hookExecutor != null && primaryResource != null)
312319
{

test/JsonApiDotNetCoreExampleTests/AppDbContextExtensions.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
using System;
22
using System.Threading.Tasks;
3-
using JsonApiDotNetCoreExample.Data;
43
using Microsoft.EntityFrameworkCore;
54
using Npgsql;
65

76
namespace JsonApiDotNetCoreExampleTests
87
{
98
public static class AppDbContextExtensions
109
{
11-
public static async Task ClearTableAsync<TEntity>(this AppDbContext dbContext) where TEntity : class
10+
public static async Task ClearTableAsync<TEntity>(this DbContext dbContext) where TEntity : class
1211
{
1312
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
1413
if (entityType == null)
@@ -30,7 +29,7 @@ public static async Task ClearTableAsync<TEntity>(this AppDbContext dbContext) w
3029
}
3130
}
3231

33-
public static void ClearTable<TEntity>(this AppDbContext dbContext) where TEntity : class
32+
public static void ClearTable<TEntity>(this DbContext dbContext) where TEntity : class
3433
{
3534
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
3635
if (entityType == null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
7+
{
8+
public sealed class CompaniesController : JsonApiController<Company>
9+
{
10+
public CompaniesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
11+
IResourceService<Company> resourceService)
12+
: base(options, loggerFactory, resourceService)
13+
{
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Resources;
3+
using JsonApiDotNetCore.Resources.Annotations;
4+
5+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
6+
{
7+
public sealed class Company : Identifiable
8+
{
9+
[Attr]
10+
public string Name { get; set; }
11+
12+
[Attr]
13+
public bool IsSoftDeleted { get; set; }
14+
15+
[HasMany]
16+
public ICollection<Department> Departments { get; set; }
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Linq;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Queries.Expressions;
4+
using JsonApiDotNetCore.Resources;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
7+
{
8+
public sealed class CompanyResourceDefinition : ResourceDefinition<Company>
9+
{
10+
private readonly IResourceGraph _resourceGraph;
11+
12+
public CompanyResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
13+
{
14+
_resourceGraph = resourceGraph;
15+
}
16+
17+
public override FilterExpression OnApplyFilter(FilterExpression existingFilter)
18+
{
19+
var resourceContext = _resourceGraph.GetResourceContext<Company>();
20+
var isSoftDeletedAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Company.IsSoftDeleted));
21+
22+
var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
23+
new ResourceFieldChainExpression(isSoftDeletedAttribute), new LiteralConstantExpression("false"));
24+
25+
return existingFilter == null
26+
? (FilterExpression) isNotSoftDeleted
27+
: new LogicalExpression(LogicalOperator.And, new[] {isNotSoftDeleted, existingFilter});
28+
}
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using JsonApiDotNetCore.Resources;
2+
using JsonApiDotNetCore.Resources.Annotations;
3+
4+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
5+
{
6+
public sealed class Department : Identifiable
7+
{
8+
[Attr]
9+
public string Name { get; set; }
10+
11+
[Attr]
12+
public bool IsSoftDeleted { get; set; }
13+
14+
[HasOne]
15+
public Company Company { get; set; }
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Linq;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Queries.Expressions;
4+
using JsonApiDotNetCore.Resources;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
7+
{
8+
public sealed class DepartmentResourceDefinition : ResourceDefinition<Department>
9+
{
10+
private readonly IResourceGraph _resourceGraph;
11+
12+
public DepartmentResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
13+
{
14+
_resourceGraph = resourceGraph;
15+
}
16+
17+
public override FilterExpression OnApplyFilter(FilterExpression existingFilter)
18+
{
19+
var resourceContext = _resourceGraph.GetResourceContext<Department>();
20+
var isSoftDeletedAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Department.IsSoftDeleted));
21+
22+
var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
23+
new ResourceFieldChainExpression(isSoftDeletedAttribute), new LiteralConstantExpression("false"));
24+
25+
return existingFilter == null
26+
? (FilterExpression) isNotSoftDeleted
27+
: new LogicalExpression(LogicalOperator.And, new[] {isNotSoftDeleted, existingFilter});
28+
}
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
7+
{
8+
public sealed class DepartmentsController : JsonApiController<Department>
9+
{
10+
public DepartmentsController(IJsonApiOptions options, ILoggerFactory loggerFactory,
11+
IResourceService<Department> resourceService)
12+
: base(options, loggerFactory, resourceService)
13+
{
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
4+
{
5+
public sealed class SoftDeletionDbContext : DbContext
6+
{
7+
public DbSet<Company> Companies { get; set; }
8+
public DbSet<Department> Departments { get; set; }
9+
10+
public SoftDeletionDbContext(DbContextOptions<SoftDeletionDbContext> options) : base(options)
11+
{
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)