Skip to content

Commit

Permalink
Merge pull request #315 from json-api-dotnet/develop
Browse files Browse the repository at this point in the history
#312 Deserializer not linking included relationships
  • Loading branch information
jaredcnance authored Jun 27, 2018
2 parents 9013643 + 5791eb3 commit 4481147
Show file tree
Hide file tree
Showing 20 changed files with 407 additions and 69 deletions.
13 changes: 8 additions & 5 deletions src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,14 @@ private ResourceIdentifierObject GetRelationship(object entity)
var objType = entity.GetType();
var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(objType);

return new ResourceIdentifierObject
{
Type = contextEntity.EntityName,
Id = ((IIdentifiable)entity).StringId
};
if(entity is IIdentifiable identifiableEntity)
return new ResourceIdentifierObject
{
Type = contextEntity.EntityName,
Id = identifiableEntity.StringId
};

return null;
}

private ResourceIdentifierObject GetIndependentRelationshipIdentifier(HasOneAttribute hasOne, IIdentifiable entity)
Expand Down
34 changes: 29 additions & 5 deletions src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,19 @@ public virtual async Task<TEntity> GetAndIncludeAsync(TId id, string relationshi

public virtual async Task<TEntity> CreateAsync(TEntity entity)
{
AttachHasManyPointers();
AttachRelationships();
_dbSet.Add(entity);

await _context.SaveChangesAsync();
return entity;
}

protected virtual void AttachRelationships()
{
AttachHasManyPointers();
AttachHasOnePointers();
}

/// <summary>
/// This is used to allow creation of HasMany relationships when the
/// dependent side of the relationship already exists.
Expand All @@ -107,6 +113,18 @@ private void AttachHasManyPointers()
}
}

/// <summary>
/// This is used to allow creation of HasOne relationships when the
/// independent side of the relationship already exists.
/// </summary>
private void AttachHasOnePointers()
{
var relationships = _jsonApiContext.HasOneRelationshipPointers.Get();
foreach (var relationship in relationships)
if (_context.Entry(relationship.Value).State == EntityState.Detached && _context.EntityIsTracked(relationship.Value) == false)
_context.Entry(relationship.Value).State = EntityState.Unchanged;
}

public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
{
var oldEntity = await GetAsync(id);
Expand Down Expand Up @@ -185,17 +203,23 @@ public virtual async Task<IEnumerable<TEntity>> PageAsync(IQueryable<TEntity> en

public async Task<int> CountAsync(IQueryable<TEntity> entities)
{
return await entities.CountAsync();
return (entities is IAsyncEnumerable<TEntity>)
? await entities.CountAsync()
: entities.Count();
}

public Task<TEntity> FirstOrDefaultAsync(IQueryable<TEntity> entities)
public async Task<TEntity> FirstOrDefaultAsync(IQueryable<TEntity> entities)
{
return entities.FirstOrDefaultAsync();
return (entities is IAsyncEnumerable<TEntity>)
? await entities.FirstOrDefaultAsync()
: entities.FirstOrDefault();
}

public async Task<IReadOnlyList<TEntity>> ToListAsync(IQueryable<TEntity> entities)
{
return await entities.ToListAsync();
return (entities is IAsyncEnumerable<TEntity>)
? await entities.ToListAsync()
: entities.ToList();
}
}
}
21 changes: 21 additions & 0 deletions src/JsonApiDotNetCore/Extensions/DbContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Linq;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Models;
using Microsoft.EntityFrameworkCore;

namespace JsonApiDotNetCore.Extensions
Expand All @@ -8,5 +11,23 @@ public static class DbContextExtensions
[Obsolete("This is no longer required since the introduction of context.Set<T>", error: false)]
public static DbSet<T> GetDbSet<T>(this DbContext context) where T : class
=> context.Set<T>();

/// <summary>
/// Determines whether or not EF is already tracking an entity of the same Type and Id
/// </summary>
public static bool EntityIsTracked(this DbContext context, IIdentifiable entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));

var trackedEntries = context.ChangeTracker
.Entries()
.FirstOrDefault(entry =>
entry.Entity.GetType() == entity.GetType()
&& ((IIdentifiable)entry.Entity).StringId == entity.StringId
);

return trackedEntries != null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ public static void AddJsonApiInternals(
services.AddScoped<IJsonApiReader, JsonApiReader>();
services.AddScoped<IGenericProcessorFactory, GenericProcessorFactory>();
services.AddScoped(typeof(GenericProcessor<>));
services.AddScoped(typeof(GenericProcessor<,>));
services.AddScoped<IQueryAccessor, QueryAccessor>();
services.AddScoped<IQueryParser, QueryParser>();
services.AddScoped<IControllerContext, Services.ControllerContext>();
Expand Down
17 changes: 15 additions & 2 deletions src/JsonApiDotNetCore/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using JsonApiDotNetCore.Internal;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -51,8 +52,20 @@ public static TInterface New<TInterface>(this Type t)
{
if (t == null) throw new ArgumentNullException(nameof(t));

var instance = (TInterface)Activator.CreateInstance(t);
var instance = (TInterface)CreateNewInstance(t);
return instance;
}

private static object CreateNewInstance(Type type)
{
try
{
return Activator.CreateInstance(type);
}
catch (Exception e)
{
throw new JsonApiException(500, $"Type '{type}' cannot be instantiated using the default constructor.", e);
}
}
}
}
11 changes: 3 additions & 8 deletions src/JsonApiDotNetCore/Internal/Generics/GenericProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ public interface IGenericProcessor
void SetRelationships(object parent, RelationshipAttribute relationship, IEnumerable<string> relationshipIds);
}

public class GenericProcessor<T> : GenericProcessor<T, int> where T : class, IIdentifiable<int>
{
public GenericProcessor(IDbContextResolver contextResolver) : base(contextResolver) { }
}

public class GenericProcessor<T, TId> : IGenericProcessor where T : class, IIdentifiable<TId>
public class GenericProcessor<T> : IGenericProcessor where T : class, IIdentifiable
{
private readonly DbContext _context;
public GenericProcessor(IDbContextResolver contextResolver)
Expand All @@ -38,12 +33,12 @@ public void SetRelationships(object parent, RelationshipAttribute relationship,
{
if (relationship.IsHasMany)
{
var entities = _context.GetDbSet<T>().Where(x => relationshipIds.Contains(x.StringId)).ToList();
var entities = _context.Set<T>().Where(x => relationshipIds.Contains(x.StringId)).ToList();
relationship.SetValue(parent, entities);
}
else
{
var entity = _context.GetDbSet<T>().SingleOrDefault(x => relationshipIds.First() == x.StringId);
var entity = _context.Set<T>().SingleOrDefault(x => relationshipIds.First() == x.StringId);
relationship.SetValue(parent, entity);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/JsonApiDotNetCore.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>2.3.1</VersionPrefix>
<VersionPrefix>2.3.2</VersionPrefix>
<TargetFrameworks>$(NetStandardVersion)</TargetFrameworks>
<AssemblyName>JsonApiDotNetCore</AssemblyName>
<PackageId>JsonApiDotNetCore</PackageId>
Expand Down
9 changes: 8 additions & 1 deletion src/JsonApiDotNetCore/Models/Identifiable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class Identifiable<T> : IIdentifiable<T>
public string StringId
{
get => GetStringId(Id);
set => Id = (T)GetConcreteId(value);
set => Id = GetTypedId(value);
}

protected virtual string GetStringId(object value)
Expand All @@ -34,6 +34,13 @@ protected virtual string GetStringId(object value)
: stringValue;
}

protected virtual T GetTypedId(string value)
{
var convertedValue = TypeHelper.ConvertType(value, typeof(T));
return convertedValue == null ? default : (T)convertedValue;
}

[Obsolete("Use GetTypedId instead")]
protected virtual object GetConcreteId(string value)
{
return TypeHelper.ConvertType(value, typeof(T));
Expand Down
11 changes: 11 additions & 0 deletions src/JsonApiDotNetCore/Models/RelationshipAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI

public string PublicRelationshipName { get; }
public string InternalRelationshipName { get; internal set; }

/// <summary>
/// The related entity type. This does not necessarily match the navigation property type.
/// In the case of a HasMany relationship, this value will be the generic argument type.
/// </summary>
///
/// <example>
/// <code>
/// public List&lt;Articles&gt; Articles { get; set; } // Type => Article
/// </code>
/// </example>
public Type Type { get; internal set; }
public bool IsHasMany => GetType() == typeof(HasManyAttribute);
public bool IsHasOne => GetType() == typeof(HasOneAttribute);
Expand Down
46 changes: 46 additions & 0 deletions src/JsonApiDotNetCore/Request/HasOneRelationshipPointers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using JsonApiDotNetCore.Models;
using System;
using System.Collections.Generic;

namespace JsonApiDotNetCore.Request
{
/// <summary>
/// Stores information to set relationships for the request resource.
/// These relationships must already exist and should not be re-created.
///
/// The expected use case is POST-ing or PATCH-ing
/// an entity with HasOne relationships:
/// <code>
/// {
/// "data": {
/// "type": "photos",
/// "attributes": {
/// "title": "Ember Hamster",
/// "src": "http://example.com/images/productivity.png"
/// },
/// "relationships": {
/// "photographer": {
/// "data": { "type": "people", "id": "2" }
/// }
/// }
/// }
/// }
/// </code>
/// </summary>
public class HasOneRelationshipPointers
{
private Dictionary<Type, IIdentifiable> _hasOneRelationships = new Dictionary<Type, IIdentifiable>();

/// <summary>
/// Add the relationship to the list of relationships that should be
/// set in the repository layer.
/// </summary>
public void Add(Type dependentType, IIdentifiable entity)
=> _hasOneRelationships[dependentType] = entity;

/// <summary>
/// Get all the models that should be associated
/// </summary>
public Dictionary<Type, IIdentifiable> Get() => _hasOneRelationships;
}
}
4 changes: 2 additions & 2 deletions src/JsonApiDotNetCore/Serialization/IJsonApiDeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ public interface IJsonApiDeSerializer
TEntity Deserialize<TEntity>(string requestBody);
object DeserializeRelationship(string requestBody);
List<TEntity> DeserializeList<TEntity>(string requestBody);
object DocumentToObject(DocumentData data);
object DocumentToObject(DocumentData data, List<DocumentData> included = null);
}
}
}
Loading

0 comments on commit 4481147

Please sign in to comment.