Skip to content

Fixes in resource definition callbacks #822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,12 @@ public QueryLayer WrapLayerForSecondaryEndpoint<TId>(QueryLayer secondaryLayer,
primaryProjection[secondaryRelationship] = secondaryLayer;
primaryProjection[primaryIdAttribute] = null;

var primaryFilter = GetFilter(Array.Empty<QueryExpression>(), primaryResourceContext);

return new QueryLayer(primaryResourceContext)
{
Include = RewriteIncludeForSecondaryEndpoint(innerInclude, secondaryRelationship),
Filter = CreateFilterById(primaryId, primaryResourceContext),
Filter = IncludeFilterById(primaryId, primaryResourceContext, primaryFilter),
Projection = primaryProjection
};
}
Expand All @@ -206,12 +208,16 @@ private IncludeExpression RewriteIncludeForSecondaryEndpoint(IncludeExpression r
return new IncludeExpression(new[] {parentElement});
}

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

return new ComparisonExpression(ComparisonOperator.Equals,
FilterExpression filterById = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(primaryIdAttribute), new LiteralConstantExpression(id.ToString()));

return existingFilter == null
? filterById
: new LogicalExpression(LogicalOperator.And, new[] {filterById, existingFilter});
}

public IDictionary<ResourceFieldAttribute, QueryLayer> GetSecondaryProjectionForRelationshipEndpoint(ResourceContext secondaryResourceContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ private IIdentifiable GetTrackedHasOneRelationshipValue(IIdentifiable relationsh
}

/// <inheritdoc />
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds)
public async Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds)
{
_traceWriter.LogMethodStart(new {parent, relationship, relationshipIds});
if (parent == null) throw new ArgumentNullException(nameof(parent));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public interface IResourceWriteRepository<in TResource, in TId>
Task UpdateAsync(TResource requestResource, TResource databaseResource);

/// <summary>
/// Updates relationships in the underlying data store.
/// Updates a relationship in the underlying data store.
/// </summary>
Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds);
Task UpdateRelationshipAsync(object parent, RelationshipAttribute relationship, IReadOnlyCollection<string> relationshipIds);

/// <summary>
/// Deletes a resource from the underlying data store.
Expand Down
17 changes: 12 additions & 5 deletions src/JsonApiDotNetCore/Services/JsonApiResourceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private async Task<TResource> GetPrimaryResourceById(TId id, bool allowTopSparse
var primaryLayer = _queryLayerComposer.Compose(_request.PrimaryResource);
primaryLayer.Sort = null;
primaryLayer.Pagination = null;
primaryLayer.Filter = CreateFilterById(id);
primaryLayer.Filter = IncludeFilterById(id, primaryLayer.Filter);

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

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

return new ComparisonExpression(ComparisonOperator.Equals,
FilterExpression filterById = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(primaryIdAttribute), new LiteralConstantExpression(id.ToString()));

return existingFilter == null
? filterById
: new LogicalExpression(LogicalOperator.And, new[] {filterById, existingFilter});
}

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

AssertRelationshipExists(relationshipName);

var secondaryLayer = _queryLayerComposer.Compose(_request.SecondaryResource);
secondaryLayer.Projection = _queryLayerComposer.GetSecondaryProjectionForRelationshipEndpoint(_request.SecondaryResource);
secondaryLayer.Include = null;

var primaryLayer = _queryLayerComposer.WrapLayerForSecondaryEndpoint(secondaryLayer, _request.PrimaryResource, id, _request.Relationship);
primaryLayer.Projection = null;

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

await _repository.UpdateRelationshipsAsync(primaryResource, _request.Relationship, relationshipIds ?? Array.Empty<string>());
await _repository.UpdateRelationshipAsync(primaryResource, _request.Relationship, relationshipIds ?? Array.Empty<string>());

if (_hookExecutor != null && primaryResource != null)
{
Expand Down
5 changes: 2 additions & 3 deletions test/JsonApiDotNetCoreExampleTests/AppDbContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System;
using System.Threading.Tasks;
using JsonApiDotNetCoreExample.Data;
using Microsoft.EntityFrameworkCore;
using Npgsql;

namespace JsonApiDotNetCoreExampleTests
{
public static class AppDbContextExtensions
{
public static async Task ClearTableAsync<TEntity>(this AppDbContext dbContext) where TEntity : class
public static async Task ClearTableAsync<TEntity>(this DbContext dbContext) where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
if (entityType == null)
Expand All @@ -30,7 +29,7 @@ public static async Task ClearTableAsync<TEntity>(this AppDbContext dbContext) w
}
}

public static void ClearTable<TEntity>(this AppDbContext dbContext) where TEntity : class
public static void ClearTable<TEntity>(this DbContext dbContext) where TEntity : class
{
var entityType = dbContext.Model.FindEntityType(typeof(TEntity));
if (entityType == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class CompaniesController : JsonApiController<Company>
{
public CompaniesController(IJsonApiOptions options, ILoggerFactory loggerFactory,
IResourceService<Company> resourceService)
: base(options, loggerFactory, resourceService)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class Company : Identifiable
{
[Attr]
public string Name { get; set; }

[Attr]
public bool IsSoftDeleted { get; set; }

[HasMany]
public ICollection<Department> Departments { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Linq;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class CompanyResourceDefinition : ResourceDefinition<Company>
{
private readonly IResourceGraph _resourceGraph;

public CompanyResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
{
_resourceGraph = resourceGraph;
}

public override FilterExpression OnApplyFilter(FilterExpression existingFilter)
{
var resourceContext = _resourceGraph.GetResourceContext<Company>();
var isSoftDeletedAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Company.IsSoftDeleted));

var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(isSoftDeletedAttribute), new LiteralConstantExpression("false"));

return existingFilter == null
? (FilterExpression) isNotSoftDeleted
: new LogicalExpression(LogicalOperator.And, new[] {isNotSoftDeleted, existingFilter});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class Department : Identifiable
{
[Attr]
public string Name { get; set; }

[Attr]
public bool IsSoftDeleted { get; set; }

[HasOne]
public Company Company { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Linq;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Queries.Expressions;
using JsonApiDotNetCore.Resources;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class DepartmentResourceDefinition : ResourceDefinition<Department>
{
private readonly IResourceGraph _resourceGraph;

public DepartmentResourceDefinition(IResourceGraph resourceGraph) : base(resourceGraph)
{
_resourceGraph = resourceGraph;
}

public override FilterExpression OnApplyFilter(FilterExpression existingFilter)
{
var resourceContext = _resourceGraph.GetResourceContext<Department>();
var isSoftDeletedAttribute = resourceContext.Attributes.Single(attribute => attribute.Property.Name == nameof(Department.IsSoftDeleted));

var isNotSoftDeleted = new ComparisonExpression(ComparisonOperator.Equals,
new ResourceFieldChainExpression(isSoftDeletedAttribute), new LiteralConstantExpression("false"));

return existingFilter == null
? (FilterExpression) isNotSoftDeleted
: new LogicalExpression(LogicalOperator.And, new[] {isNotSoftDeleted, existingFilter});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class DepartmentsController : JsonApiController<Department>
{
public DepartmentsController(IJsonApiOptions options, ILoggerFactory loggerFactory,
IResourceService<Department> resourceService)
: base(options, loggerFactory, resourceService)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion
{
public sealed class SoftDeletionDbContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<Department> Departments { get; set; }

public SoftDeletionDbContext(DbContextOptions<SoftDeletionDbContext> options) : base(options)
{
}
}
}
Loading