Skip to content
This repository has been archived by the owner on Jul 18, 2023. It is now read-only.

Commit

Permalink
Modify Include method so that it accepts QueryExtensions methods. Imp…
Browse files Browse the repository at this point in the history
…lemented task 2 from issue #2
  • Loading branch information
Giorgi committed Apr 2, 2017
1 parent 84a14ca commit 14f55ca
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 111 deletions.
90 changes: 90 additions & 0 deletions GraphQLinq/ExtensionsUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace GraphQLinq
{
static class ExtensionsUtils
{
internal static bool IsPrimitiveOrString(this Type type)
{
return type.IsPrimitive || type == typeof(string);
}

internal static bool IsList(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>);
}

internal static bool HasNestedProperties(this Type type)
{
var trueType = GetTypeOrListType(type);

return !IsPrimitiveOrString(trueType);
}

internal static Type GetTypeOrListType(this Type type)
{
if (type.IsList())
{
var genericArguments = type.GetGenericArguments();

return genericArguments[0].GetTypeOrListType();
}

return type;
}

internal static Expression RemoveConvert(this Expression expression)
{
while ((expression != null)
&& (expression.NodeType == ExpressionType.Convert
|| expression.NodeType == ExpressionType.ConvertChecked))
{
expression = RemoveConvert(((UnaryExpression)expression).Operand);
}

return expression;
}

internal static string ToCamelCase(this string input)
{
if (char.IsLower(input[0]))
{
return input;
}
return input.Substring(0, 1).ToLower() + input.Substring(1);
}

internal static string ToGraphQlType(this Type type)
{
if (type == typeof(bool))
{
return "Boolean";
}

if (type == typeof(int))
{
return "Int";
}

if (type == typeof(string))
{
return "String!";
}

if (type == typeof(float))
{
return "Float";
}

if (type.IsList())
{
var listType = type.GetTypeOrListType();
return "[" + ToGraphQlType(listType) + "]";
}

return type.Name;
}
}
}
1 change: 1 addition & 0 deletions GraphQLinq/GraphQLinq.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExtensionsUtils.cs" />
<Compile Include="GraphContext.cs" />
<Compile Include="GraphQuery.cs" />
<Compile Include="GraphQueryBuilder.cs" />
Expand Down
171 changes: 69 additions & 102 deletions GraphQLinq/GraphQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace GraphQLinq
{
Expand All @@ -14,7 +15,7 @@ public abstract class GraphQuery<T>

internal string QueryName { get; }
internal LambdaExpression Selector { get; private set; }
internal List<string> Includes { get; private set; } = new List<string>();
internal List<IncludeDetails> Includes { get; private set; } = new List<IncludeDetails>();
internal Dictionary<string, object> Arguments { get; set; } = new Dictionary<string, object>();

internal GraphQuery(GraphContext graphContext, string queryName)
Expand Down Expand Up @@ -48,61 +49,94 @@ protected GraphQuery<TR> Clone<TR>()
return instance;
}

protected static bool TryParsePath(Expression expression, out string path)
internal static IncludeDetails ParseIncludePath(Expression expression)
{
path = null;
string path = null;
var withoutConvert = expression.RemoveConvert(); // Removes boxing
var memberExpression = withoutConvert as MemberExpression;
var callExpression = withoutConvert as MethodCallExpression;

if (memberExpression != null)
{
var thisPart = memberExpression.Member.Name;
string parentPart;
if (!TryParsePath(memberExpression.Expression, out parentPart))
var parentPath = ParseIncludePath(memberExpression.Expression);
if (parentPath == null)
{
return false;
return null;
}
path = parentPart == null ? thisPart : (parentPart + "." + thisPart);

var thisPart = memberExpression.Member.Name;
path = parentPath.Path == null ? thisPart : (parentPath.Path + "." + thisPart);
}
else if (callExpression != null)
{
if (callExpression.Method.Name == "Select"
&& callExpression.Arguments.Count == 2)
if (callExpression.Method.Name == "Select" && callExpression.Arguments.Count == 2)
{
string parentPart;
if (!TryParsePath(callExpression.Arguments[0], out parentPart))
var parentPath = ParseIncludePath(callExpression.Arguments[0]);
if (parentPath == null)
{
return false;
return null;
}
if (parentPart != null)

if (parentPath.Path != null)
{
var subExpression = callExpression.Arguments[1] as LambdaExpression;
if (subExpression != null)
{
string thisPart;
if (!TryParsePath(subExpression.Body, out thisPart))
var thisPath = ParseIncludePath(subExpression.Body);
if (thisPath == null)
{
return false;
return null;
}
if (thisPart != null)

if (thisPath.Path != null)
{
path = parentPart + "." + thisPart;
return true;
path = parentPath.Path + "." + thisPath.Path;
var result = new IncludeDetails { Path = path};

result.MethodIncludes.AddRange(parentPath.MethodIncludes);
result.MethodIncludes.AddRange(thisPath.MethodIncludes);

return result;
}
}
}
}
return false;
if (callExpression.Method.DeclaringType?.Name == "QueryExtensions")
{
var parentPath = ParseIncludePath(callExpression.Arguments[0]);
if (parentPath == null)
{
return null;
}

path = parentPath.Path == null ? callExpression.Method.Name : parentPath.Path + "." + callExpression.Method.Name;

var arguments = callExpression.Arguments.Zip(callExpression.Method.GetParameters(), (argument, parameter) => new
{
Argument = argument,
parameter.Name
}).Skip(1).ToDictionary(arg => arg.Name, arg => (arg.Argument as ConstantExpression).Value);

var result = new IncludeDetails { Path = path };
result.MethodIncludes.Add(new IncludeMethodDetails
{
Method = callExpression.Method,
Parameters = arguments
});
result.MethodIncludes.AddRange(parentPath.MethodIncludes);
return result;
}

return null;
}

return true;
return new IncludeDetails { Path = path };
}

protected GraphQuery<T> BuildInclude<TProperty>(Expression<Func<T, TProperty>> path)
{
string include;
if (!TryParsePath(path.Body, out include) || include == null)
var include = ParseIncludePath(path.Body);
if (include?.Path == null)
{
throw new ArgumentException("Invalid Include Path Expression", nameof(path));
}
Expand Down Expand Up @@ -130,7 +164,7 @@ internal IEnumerator<T> BuildEnumerator<TSource>(QueryType queryType)
{
var query = lazyQuery.Value;

var mapper = (Func<TSource, T>) Selector?.Compile();
var mapper = (Func<TSource, T>)Selector?.Compile();

return new GraphQueryEnumerator<T, TSource>(query.FullQuery, context.BaseUrl, context.Authorization, queryType, mapper);
}
Expand Down Expand Up @@ -209,87 +243,20 @@ internal enum QueryType
Collection
}

static class ExtensionsUtils
class IncludeDetails
{
internal static bool IsPrimitiveOrString(this Type type)
{
return type.IsPrimitive || type == typeof(string);
}

internal static bool IsList(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>);
}

internal static bool HasNestedProperties(this Type type)
{
var trueType = GetTypeOrListType(type);

return !IsPrimitiveOrString(trueType);
}

internal static Type GetTypeOrListType(this Type type)
{
if (type.IsList())
{
var genericArguments = type.GetGenericArguments();

return genericArguments[0].GetTypeOrListType();
}

return type;
}

internal static Expression RemoveConvert(this Expression expression)
{
while ((expression != null)
&& (expression.NodeType == ExpressionType.Convert
|| expression.NodeType == ExpressionType.ConvertChecked))
{
expression = RemoveConvert(((UnaryExpression)expression).Operand);
}

return expression;
}
public string Path { get; set; }
public List<IncludeMethodDetails> MethodIncludes { get; } = new List<IncludeMethodDetails>();
}

internal static string ToCamelCase(this string input)
{
if (char.IsLower(input[0]))
{
return input;
}
return input.Substring(0, 1).ToLower() + input.Substring(1);
}
class IncludeMethodDetails
{
public MethodInfo Method { get; set; }
public Dictionary<string, object> Parameters { get; set; }

internal static string ToGraphQlType(this Type type)
public override string ToString()
{
if (type == typeof(bool))
{
return "Boolean";
}

if (type == typeof(int))
{
return "Int";
}

if (type == typeof(string))
{
return "String!";
}

if (type == typeof(float))
{
return "Float";
}

if (type.IsList())
{
var listType = type.GetTypeOrListType();
return "[" + ToGraphQlType(listType) + "]";
}

return type.Name;
return Method.Name;
}
}
}
21 changes: 12 additions & 9 deletions GraphQLinq/GraphQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class GraphQueryBuilder<T>
private const string QueryTemplate = @"query {0} {{ {1}: {2} {3} {{ {4} }}}}";
internal const string ResultAlias = "result";

public GraphQLQuery BuildQuery(GraphQuery<T> graphQuery, List<string> includes)
public GraphQLQuery BuildQuery(GraphQuery<T> graphQuery, List<IncludeDetails> includes)
{
var selectClause = "";

Expand Down Expand Up @@ -105,7 +105,7 @@ private static string BuildSelectClauseForType(Type targetType, int depth = 1)
return selectClause;
}

private static string BuildSelectClauseForType(Type targetType, IEnumerable<string> includes)
private static string BuildSelectClauseForType(Type targetType, IEnumerable<IncludeDetails> includes)
{
var selectClause = BuildSelectClauseForType(targetType);

Expand All @@ -118,8 +118,9 @@ private static string BuildSelectClauseForType(Type targetType, IEnumerable<stri
return selectClause;
}

private static string BuildSelectClauseForInclude(Type targetType, string include, int depth = 1)
private static string BuildSelectClauseForInclude(Type targetType, IncludeDetails includeDetails, int depth = 1)
{
var include = includeDetails.Path;
if (string.IsNullOrEmpty(include))
{
return BuildSelectClauseForType(targetType, depth);
Expand All @@ -128,18 +129,20 @@ private static string BuildSelectClauseForInclude(Type targetType, string includ

var dotIndex = include.IndexOf(".", StringComparison.InvariantCultureIgnoreCase);

var restOfTheIncludePath = dotIndex >= 0 ? include.Substring(dotIndex + 1) : "";
var currentPropertyName = dotIndex >= 0 ? include.Substring(0, dotIndex) : include;
var currentIncludeName = dotIndex >= 0 ? include.Substring(0, dotIndex) : include;

var propertyType = targetType.GetProperty(currentPropertyName).PropertyType.GetTypeOrListType();
var propertyInfo = targetType.GetProperty(currentIncludeName);
var propertyType = propertyInfo.PropertyType.GetTypeOrListType();

if (propertyType.IsPrimitiveOrString())
{
return leftPadding + currentPropertyName.ToCamelCase();
return leftPadding + currentIncludeName.ToCamelCase();
}

var fieldsFromInclude = BuildSelectClauseForInclude(propertyType, restOfTheIncludePath, depth + 1);
fieldsFromInclude = $"{leftPadding}{currentPropertyName.ToCamelCase()} {{{Environment.NewLine}{fieldsFromInclude}{Environment.NewLine}{leftPadding}}}";
var restOfTheInclude = new IncludeDetails {Path = dotIndex >= 0 ? include.Substring(dotIndex + 1) : "" };

var fieldsFromInclude = BuildSelectClauseForInclude(propertyType, restOfTheInclude, depth + 1);
fieldsFromInclude = $"{leftPadding}{currentIncludeName.ToCamelCase()} {{{Environment.NewLine}{fieldsFromInclude}{Environment.NewLine}{leftPadding}}}";
return fieldsFromInclude;
}
}
Expand Down

0 comments on commit 14f55ca

Please sign in to comment.