Skip to content

Commit f19e606

Browse files
committed
style: IGenericProcessor --> IHasManyThroughUpdateHelper
1 parent 48495d3 commit f19e606

File tree

12 files changed

+112
-473
lines changed

12 files changed

+112
-473
lines changed

src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public void ConfigureServices()
155155
_services.AddScoped<IJsonApiWriter, JsonApiWriter>();
156156
_services.AddScoped<IJsonApiReader, JsonApiReader>();
157157
_services.AddScoped<IGenericProcessorFactory, GenericProcessorFactory>();
158-
_services.AddScoped(typeof(GenericProcessor<>));
158+
_services.AddScoped(typeof(HasManyThroughUpdateHelper<>));
159159
_services.AddScoped<IQueryParameterDiscovery, QueryParameterDiscovery>();
160160
_services.AddScoped<ITargetedFields, TargetedFields>();
161161
_services.AddScoped<IResourceDefinitionProvider, ResourceDefinitionProvider>();

src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ public DefaultResourceRepository(
5555
public virtual IQueryable<TResource> Get(TId id) => _dbSet.Where(e => e.Id.Equals(id));
5656

5757
/// <inheritdoc />
58-
public virtual IQueryable<TResource> Select(IQueryable<TResource> entities, List<AttrAttribute> fields)
58+
public virtual IQueryable<TResource> Select(IQueryable<TResource> entities, params AttrAttribute[] fields)
5959
{
60-
if (fields?.Count > 0)
60+
if (fields.Any())
6161
return entities.Select(fields);
6262

6363
return entities;
@@ -256,16 +256,20 @@ private IIdentifiable GetTrackedHasOneRelationshipValue(IIdentifiable relationsh
256256
/// <inheritdoc />
257257
public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds)
258258
{
259-
// TODO: it would be better to let this be determined within the relationship attribute...
260-
// need to think about the right way to do that since HasMany doesn't need to think about this
261-
// and setting the HasManyThrough.Type to the join type (ArticleTag instead of Tag) for this changes the semantics
262-
// of the property...
263-
var typeToUpdate = (relationship is HasManyThroughAttribute hasManyThrough)
264-
? hasManyThrough.ThroughType
265-
: relationship.RightType;
266-
267-
var genericProcessor = _genericProcessorFactory.GetProcessor<IGenericProcessor>(typeof(GenericProcessor<>), typeToUpdate);
268-
await genericProcessor.UpdateRelationshipsAsync(parent, relationship, relationshipIds);
259+
if (relationship is HasManyThroughAttribute hasManyThrough)
260+
{
261+
var helper = _genericProcessorFactory.GetProcessor<IHasManyThroughUpdateHelper>(typeof(HasManyThroughUpdateHelper<>), hasManyThrough.ThroughType);
262+
await helper.UpdateAsync((IIdentifiable)parent, hasManyThrough, relationshipIds);
263+
return;
264+
}
265+
266+
var context = _context.Set(relationship.RightType);
267+
var updatedValue = relationship is HasManyAttribute
268+
? context.Where(e => relationshipIds.Contains(((IIdentifiable)e).StringId)).Cast(relationship.RightType)
269+
: context.FirstOrDefault(e => relationshipIds.First() == ((IIdentifiable)e).StringId);
270+
271+
relationship.SetValue(parent, updatedValue);
272+
await _context.SaveChangesAsync();
269273
}
270274

271275
/// <inheritdoc />

src/JsonApiDotNetCore/Data/IResourceReadRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public interface IResourceReadRepository<TResource, in TId>
2626
/// <summary>
2727
/// Apply fields to the provided queryable
2828
/// </summary>
29-
IQueryable<TResource> Select(IQueryable<TResource> entities, List<AttrAttribute> fields);
29+
IQueryable<TResource> Select(IQueryable<TResource> entities, params AttrAttribute[] fields);
3030
/// <summary>
3131
/// Include a relationship in the query
3232
/// </summary>

src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ namespace JsonApiDotNetCore.Extensions
1010
{
1111
public static class DbContextExtensions
1212
{
13-
[Obsolete("This is no longer required since the introduction of context.Set<T>", error: false)]
14-
public static DbSet<T> GetDbSet<T>(this DbContext context) where T : class
15-
=> context.Set<T>();
16-
1713
/// <summary>
1814
/// Get the DbSet when the model type is unknown until runtime
1915
/// </summary>

src/JsonApiDotNetCore/Extensions/IQueryableExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public static IQueryable<TSource> Filter<TSource>(this IQueryable<TSource> sourc
6464
return CallGenericWhereMethod(source, filterQuery);
6565
}
6666

67-
public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, List<AttrAttribute> columns)
67+
public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, AttrAttribute[] columns)
6868
=> CallGenericSelectMethod(source, columns.Select(attr => attr.InternalAttributeName).ToList());
6969

7070
public static IOrderedQueryable<TSource> Sort<TSource>(this IQueryable<TSource> source, SortQueryContext sortQuery)

src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs

Lines changed: 0 additions & 105 deletions
This file was deleted.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Threading.Tasks;
6+
using JsonApiDotNetCore.Data;
7+
using JsonApiDotNetCore.Extensions;
8+
using JsonApiDotNetCore.Models;
9+
using Microsoft.EntityFrameworkCore;
10+
11+
namespace JsonApiDotNetCore.Internal.Generics
12+
{
13+
/// <summary>
14+
/// A special helper service that gets instantiated for the right-type of a many-to-many relationship and is responsible for
15+
/// processing updates for that relationships
16+
/// </summary>
17+
public interface IHasManyThroughUpdateHelper
18+
{
19+
Task UpdateAsync(IIdentifiable parent, HasManyThroughAttribute relationship, IEnumerable<string> relationshipIds);
20+
}
21+
22+
/// <summary>
23+
/// A special processor that gets instantiated for a generic type (&lt;T&gt;)
24+
/// when the actual type is not known until runtime. Specifically, this is used for updating
25+
/// relationships.
26+
/// </summary>
27+
public class HasManyThroughUpdateHelper<T> : IHasManyThroughUpdateHelper where T : class
28+
{
29+
private readonly DbContext _context;
30+
public HasManyThroughUpdateHelper(IDbContextResolver contextResolver)
31+
{
32+
_context = contextResolver.GetContext();
33+
}
34+
35+
public virtual async Task UpdateAsync(IIdentifiable parent, HasManyThroughAttribute relationship, IEnumerable<string> relationshipIds)
36+
{
37+
// we need to create a transaction for the HasManyThrough case so we can get and remove any existing
38+
// join entities and only commit if all operations are successful
39+
using (var transaction = await _context.GetCurrentOrCreateTransactionAsync())
40+
{
41+
// ArticleTag
42+
ParameterExpression parameter = Expression.Parameter(relationship.ThroughType);
43+
44+
// ArticleTag.ArticleId
45+
Expression property = Expression.Property(parameter, relationship.LeftIdProperty);
46+
47+
// article.Id
48+
var parentId = TypeHelper.ConvertType(parent.StringId, relationship.LeftIdProperty.PropertyType);
49+
Expression target = Expression.Constant(parentId);
50+
51+
// ArticleTag.ArticleId.Equals(article.Id)
52+
Expression equals = Expression.Call(property, "Equals", null, target);
53+
54+
var lambda = Expression.Lambda<Func<T, bool>>(equals, parameter);
55+
56+
// TODO: we shouldn't need to do this instead we should try updating the existing?
57+
// the challenge here is if a composite key is used, then we will fail to
58+
// create due to a unique key violation
59+
var oldLinks = _context
60+
.Set<T>()
61+
.Where(lambda.Compile())
62+
.ToList();
63+
64+
_context.RemoveRange(oldLinks);
65+
66+
var newLinks = relationshipIds.Select(x => {
67+
var link = Activator.CreateInstance(relationship.ThroughType);
68+
relationship.LeftIdProperty.SetValue(link, TypeHelper.ConvertType(parentId, relationship.LeftIdProperty.PropertyType));
69+
relationship.RightIdProperty.SetValue(link, TypeHelper.ConvertType(x, relationship.RightIdProperty.PropertyType));
70+
return link;
71+
});
72+
73+
_context.AddRange(newLinks);
74+
await _context.SaveChangesAsync();
75+
76+
transaction.Commit();
77+
}
78+
}
79+
}
80+
}

src/JsonApiDotNetCore/Serialization/Server/FieldsToSerialize.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using JsonApiDotNetCore.Internal.Contracts;
22
using System;
33
using System.Collections.Generic;
4-
using JsonApiDotNetCore.Services;
54
using JsonApiDotNetCore.Query;
65
using System.Linq;
76
using JsonApiDotNetCore.Models;

src/JsonApiDotNetCore/Services/DefaultResourceService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ protected virtual IQueryable<TResource> ApplySelect(IQueryable<TResource> entiti
263263
{
264264
var fields = _sparseFieldsService.Get();
265265
if (fields != null && fields.Any())
266-
entities = _repository.Select(entities, fields);
266+
entities = _repository.Select(entities, fields.ToArray());
267267

268268
return entities;
269269
}
@@ -276,7 +276,7 @@ protected virtual IQueryable<TResource> ApplySelect(IQueryable<TResource> entiti
276276
private async Task<TResource> GetWithRelationshipsAsync(TId id)
277277
{
278278
var sparseFieldset = _sparseFieldsService.Get();
279-
var query = _repository.Select(_repository.Get(id), sparseFieldset);
279+
var query = _repository.Select(_repository.Get(id), sparseFieldset.ToArray());
280280

281281
foreach (var chain in _includeService.Get())
282282
query = _repository.Include(query, chain.ToArray());

test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/SparseFieldSetTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public async Task Can_Select_Sparse_Fieldsets()
7373
var query = _dbContext
7474
.TodoItems
7575
.Where(t => t.Id == todoItem.Id)
76-
.Select(_resourceGraph.GetAttributes<TodoItem>(e => new { e.Id, e.Description, e.CreatedDate, e.AchievedDate } ).ToList());
76+
.Select(_resourceGraph.GetAttributes<TodoItem>(e => new { e.Id, e.Description, e.CreatedDate, e.AchievedDate } ).ToArray());
7777

7878
var resultSql = StringExtensions.Normalize(query.ToSql());
7979
var result = await query.FirstAsync();

test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services()
5656
Assert.NotNull(provider.GetService<IJsonApiReader>());
5757
Assert.NotNull(provider.GetService<IJsonApiDeserializer>());
5858
Assert.NotNull(provider.GetService<IGenericProcessorFactory>());
59-
Assert.NotNull(provider.GetService(typeof(GenericProcessor<TodoItem>)));
59+
Assert.NotNull(provider.GetService(typeof(HasManyThroughUpdateHelper<TodoItem>)));
6060
}
6161

6262
[Fact]

0 commit comments

Comments
 (0)