Skip to content

Commit 90934f6

Browse files
author
Reng van Oord
committed
Support for field name aliases when joining / including
1 parent e8284bc commit 90934f6

File tree

4 files changed

+83
-31
lines changed

4 files changed

+83
-31
lines changed

src/Sanity.Linq/Attributes/IncludeAttribute.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,15 @@ namespace Sanity.Linq
2424
[AttributeUsage(AttributeTargets.Property)]
2525
public class IncludeAttribute : Attribute
2626
{
27+
public IncludeAttribute()
28+
{
29+
}
30+
31+
public IncludeAttribute(string fieldName)
32+
{
33+
FieldName = fieldName;
34+
}
35+
36+
public string FieldName { get; }
2737
}
2838
}

src/Sanity.Linq/QueryProvider/SanityExpressionParser.cs

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,19 @@ protected string TransformMethodCallExpression(MethodCallExpression e)
260260
{
261261
var fieldPath = TransformOperand(l.Body);
262262
var propertyType = l.Body.Type;
263-
var projection = GetJoinProjection(fieldPath.Split(new[] { '.', '>' }).LastOrDefault(), propertyType);
263+
var targetName = fieldPath.Split(new[] { '.', '>' }).LastOrDefault();
264+
var sourceName = targetName;
265+
266+
// Arg 2: fieldName
267+
if (e.Arguments.Count > 2 && e.Arguments[2] is ConstantExpression c)
268+
{
269+
sourceName = c.Value?.ToString();
270+
if (string.IsNullOrEmpty(sourceName))
271+
{
272+
sourceName = targetName;
273+
}
274+
}
275+
var projection = GetJoinProjection(sourceName, targetName, propertyType);
264276
QueryBuilder.Includes[fieldPath] = projection;
265277
return projection;
266278
}
@@ -552,12 +564,15 @@ protected static List<string> GetPropertyProjectionList(Type type)
552564
var isIgnored = prop.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length > 0;
553565
if (!isIgnored)
554566
{
555-
var name = (prop.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() as JsonPropertyAttribute)?.PropertyName ?? prop.Name.ToCamelCase();
556-
var isIncluded = (prop.GetCustomAttributes<IncludeAttribute>(true).FirstOrDefault() != null);
567+
var targetName = (prop.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() as JsonPropertyAttribute)?.PropertyName ?? prop.Name.ToCamelCase();
568+
var includeAttr = prop.GetCustomAttributes<IncludeAttribute>(true).FirstOrDefault();
569+
var sourceName = !string.IsNullOrEmpty(includeAttr?.FieldName) ? includeAttr.FieldName : targetName;
570+
var fieldRef = targetName == sourceName ? sourceName : $"\"{targetName}\": {sourceName}";
571+
bool isIncluded = includeAttr != null;
557572
if (isIncluded)
558573
{
559574
// Add a join projection for [Include]d properties
560-
result.Add(GetJoinProjection(name, prop.PropertyType));
575+
result.Add(GetJoinProjection(sourceName, targetName, prop.PropertyType));
561576
}
562577
else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
563578
{
@@ -566,12 +581,16 @@ protected static List<string> GetPropertyProjectionList(Type type)
566581
if (isList)
567582
{
568583
// Array Case: Recursively add projection list for class types
569-
result.Add($"{name}[]{{{GetPropertyProjectionList(listInterface.GetGenericArguments()[0]).Aggregate((c, n) => c + "," + n)}}}");
584+
var listItemProjection = GetPropertyProjectionList(listInterface.GetGenericArguments()[0]).Aggregate((c, n) => c + "," + n);
585+
if (listItemProjection != "...")
586+
{
587+
result.Add($"{fieldRef}[]{{{listItemProjection}}}");
588+
}
570589
}
571590
else
572591
{
573592
// Object Case: Recursively add projection list for class types
574-
result.Add($"{name}{{{GetPropertyProjectionList(prop.PropertyType).Aggregate((c, n) => c + "," + n)}}}");
593+
result.Add($"{fieldRef}{{{GetPropertyProjectionList(prop.PropertyType).Aggregate((c, n) => c + "," + n)}}}");
575594
}
576595
}
577596
}
@@ -583,17 +602,22 @@ protected static List<string> GetPropertyProjectionList(Type type)
583602
/// Generates a projection for included / joined types using reflection.
584603
/// Supports Lists and also nested objects of type SanityReference<> or IEnumerable<SanityReference>
585604
/// </summary>
586-
/// <param name="fieldName"></param>
605+
/// <param name="sourceName"></param>
587606
/// <param name="propertyType"></param>
588607
/// <returns></returns>
589-
public static string GetJoinProjection(string fieldName, Type propertyType)
608+
public static string GetJoinProjection(string sourceName, string targetName, Type propertyType)
590609
{
591610
string projection = "";
611+
var fieldRef = sourceName;
612+
if (sourceName != targetName && !string.IsNullOrEmpty(targetName))
613+
{
614+
fieldRef = $"\"{targetName}\": {sourceName}";
615+
}
592616

593617
// String or primative
594618
if (propertyType == typeof(string) || propertyType.IsPrimitive)
595619
{
596-
return fieldName;
620+
return fieldRef;
597621
}
598622

599623
var isSanityReferenceType = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(SanityReference<>);
@@ -602,7 +626,7 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
602626
// CASE 1: SanityReference<T>
603627
var fields = GetPropertyProjectionList(propertyType.GetGenericArguments()[0]);
604628
var fieldList = fields.Aggregate((c, n) => c + "," + n);
605-
projection = $"{fieldName}->{{ {fieldList} }}";
629+
projection = $"{fieldRef}->{{ {fieldList} }}";
606630
}
607631
else
608632
{
@@ -614,7 +638,7 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
614638
var elementType = listOfSanityReferenceType.GetGenericArguments()[0].GetGenericArguments()[0];
615639
var fields = GetPropertyProjectionList(elementType);
616640
var fieldList = fields.Aggregate((c, n) => c + "," + n);
617-
projection = $"{fieldName}[]->{{ {fieldList} }}";
641+
projection = $"{fieldRef}[]->{{ {fieldList} }}";
618642
}
619643
else
620644
{
@@ -630,7 +654,7 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
630654

631655
// Nested Reference
632656
var fieldList = fields.Select(f => f.StartsWith("asset") ? $"asset->{(nestedFields.Count > 0 ? ("{" + nestedFields.Aggregate((a, b) => a + "," + b) + "}") : "")}" : f).Aggregate((c, n) => c + "," + n);
633-
projection = $"{fieldName}{{ {fieldList} }}";
657+
projection = $"{fieldRef}{{ {fieldList} }}";
634658
}
635659
else
636660
{
@@ -646,7 +670,7 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
646670

647671
// Nested Reference
648672
var fieldList = fields.Select(f => f == propertyName ? $"{propertyName}->{(nestedFields.Count > 0 ? ("{" + nestedFields.Aggregate((a, b) => a + "," + b) + "}") : "")}" : f).Aggregate((c, n) => c + "," + n);
649-
projection = $"{fieldName}{{ {fieldList} }}";
673+
projection = $"{fieldRef}{{ {fieldList} }}";
650674

651675
}
652676
else
@@ -665,7 +689,7 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
665689

666690
// Nested Reference
667691
var fieldList = fields.Select(f => f == propertyName ? $"{propertyName}[]->{(nestedFields.Count > 0 ? ("{" + nestedFields.Aggregate((a, b) => a + "," + b) + "}") : "")}" : f).Aggregate((c, n) => c + "," + n);
668-
projection = $"{fieldName}{{ {fieldList} }}";
692+
projection = $"{fieldRef}{{ {fieldList} }}";
669693

670694
}
671695
else
@@ -682,7 +706,7 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
682706

683707
// Nested Reference
684708
var fieldList = fields.Select(f => f.StartsWith("asset") ? $"asset->{{ ... }}" : f).Aggregate((c, n) => c + "," + n);
685-
projection = $"{fieldName}[] {{ {fieldList} }}";
709+
projection = $"{fieldRef}[] {{ {fieldList} }}";
686710
}
687711
}
688712
}
@@ -703,12 +727,12 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
703727
{
704728
// Other strongly typed includes
705729
var fieldList = fields.Aggregate((c, n) => c + "," + n);
706-
projection = $"{fieldName}[]->{{ {fieldList} }}";
730+
projection = $"{fieldRef}[]->{{ {fieldList} }}";
707731
}
708732
else
709733
{
710734
// "object" without any fields defined
711-
projection = $"{fieldName}[]->";
735+
projection = $"{fieldRef}[]->";
712736
}
713737
}
714738
else
@@ -718,12 +742,12 @@ public static string GetJoinProjection(string fieldName, Type propertyType)
718742
{
719743
// Other strongly typed includes
720744
var fieldList = fields.Aggregate((c, n) => c + "," + n);
721-
projection = $"{fieldName}->{{ {fieldList} }}";
745+
projection = $"{fieldRef}->{{ {fieldList} }}";
722746
}
723747
else
724748
{
725749
// "object" without any fields defined
726-
projection = $"{fieldName}->{{ ... }}";
750+
projection = $"{fieldRef}->{{ ... }}";
727751
}
728752
}
729753
}
@@ -791,10 +815,12 @@ public virtual string Build(bool includeProjections)
791815
var includedProps = properties.Where(p => p.GetCustomAttributes<IncludeAttribute>(true).FirstOrDefault() != null).ToList();
792816
foreach (var prop in includedProps)
793817
{
794-
var name = (prop.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() as JsonPropertyAttribute)?.PropertyName ?? prop.Name.ToCamelCase();
795-
if (!Includes.ContainsKey(name))
818+
var includeAttr = prop.GetCustomAttributes<IncludeAttribute>(true).FirstOrDefault();
819+
var targetName = (prop.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() as JsonPropertyAttribute)?.PropertyName ?? prop.Name.ToCamelCase();
820+
var sourceName = !string.IsNullOrEmpty(includeAttr.FieldName) ? includeAttr.FieldName : targetName;
821+
if (!Includes.ContainsKey(targetName))
796822
{
797-
Includes.Add(name, GetJoinProjection(name, prop.PropertyType));
823+
Includes.Add(targetName, GetJoinProjection(sourceName, targetName, prop.PropertyType));
798824
}
799825
}
800826
}
@@ -878,6 +904,8 @@ private string ExpandIncludesInProjection(string projection, Dictionary<string,
878904
var jObjectInclude = JsonConvert.DeserializeObject(jsonInclude) as JObject;
879905

880906
var pathParts = includeKey
907+
.Replace("\":", GroqTokens["\":"])
908+
.Replace("\"", GroqTokens["\""])
881909
.Replace("[]", GroqTokens["[]"])
882910
.Replace("->", ".")
883911
.TrimEnd('.').Split('.');
@@ -951,20 +979,23 @@ private string ExpandIncludesInProjection(string projection, Dictionary<string,
951979

952980
private Dictionary<string, string> GroqTokens = new Dictionary<string, string>
953981
{
982+
{ "\"", "VVV" },
983+
{ "\":", "WWW" },
954984
{ "...", "XXX" },
955985
{ "->", "YYY" },
956-
{ "[]", "ZZZ" }
957-
986+
{ "[]", "ZZZ" },
958987
};
959988

960989
private string GroqToJson(string groq)
961990
{
962991
var json = groq
963992
.Replace(" ", "")
993+
.Replace("\":", GroqTokens["\":"])
994+
.Replace("\"", GroqTokens["\""])
964995
.Replace("{", ":{")
965996
.Replace("...", GroqTokens["..."])
966997
.Replace("->", GroqTokens["->"])
967-
.Replace("[]", GroqTokens["[]"])
998+
.Replace("[]", GroqTokens["[]"])
968999
.TrimStart(':');
9691000

9701001
// Replace variable names with valid json (e.g. convert myField to "myField":true)
@@ -993,7 +1024,9 @@ private string JsonToGroq(string json)
9931024
.Replace(":{", "{")
9941025
.Replace(GroqTokens["[]"], "[]")
9951026
.Replace(":true", "")
996-
.Replace("\"", "");
1027+
.Replace("\"", "")
1028+
.Replace(GroqTokens["\":"], "\":")
1029+
.Replace(GroqTokens["\""], "\"");
9971030
}
9981031
}
9991032

src/Sanity.Linq/Sanity.Linq.csproj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ This file is part of Sanity LINQ (https://github.com/oslofjord/sanity-linq).
2626
<Authors>Oslofjord Operations AS</Authors>
2727
<Company>Oslofjord Operations AS</Company>
2828
<Product>Sanity LINQ</Product>
29-
<Version>1.2.3</Version>
29+
<Version>1.3.0</Version>
3030
<Description>Strongly-typed .Net Client for Sanity CMS (https://sanity.io)</Description>
3131
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
3232
<Copyright>2019 Oslofjord Operations AS</Copyright>
@@ -35,11 +35,11 @@ This file is part of Sanity LINQ (https://github.com/oslofjord/sanity-linq).
3535
<RepositoryType>git</RepositoryType>
3636
<PackageTags>sanity cms dotnet linq client groq</PackageTags>
3737
<PackageLicenseUrl>https://raw.githubusercontent.com/oslofjord/sanity-linq/master/LICENSE</PackageLicenseUrl>
38-
<AssemblyVersion>1.2.3.0</AssemblyVersion>
38+
<AssemblyVersion>1.3.0.0</AssemblyVersion>
3939
<PackageId>Sanity.Linq</PackageId>
4040
<AssemblyName>Sanity.Linq</AssemblyName>
4141
<RootNamespace>Sanity.Linq</RootNamespace>
42-
<FileVersion>1.2.3.0</FileVersion>
42+
<FileVersion>1.3.0.0</FileVersion>
4343
<PackageReleaseNotes>1.0 - Sanity Linq library
4444
1.1 - BlockContent library
4545
1.1.1 - Improvements BlockContent
@@ -49,7 +49,8 @@ This file is part of Sanity LINQ (https://github.com/oslofjord/sanity-linq).
4949
1.1.6 - Support for localized arrays
5050
1.1.8 - Updated documentation
5151
1.2.0 - Support for nested Includes / projections
52-
1.2.1 - Extended tests and improved support for fluid/Linq includes</PackageReleaseNotes>
52+
1.2.1 - Extended tests and improved support for fluid/Linq includes
53+
1.3.0 - Support for field aliases when joining / including</PackageReleaseNotes>
5354
</PropertyGroup>
5455

5556
<ItemGroup>

src/Sanity.Linq/SanityDocumentSet.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,16 @@ public SanityDocumentSet<TDoc> Include<TProperty>(Expression<Func<TDoc, TPropert
117117
var includeMethod = typeof(SanityDocumentSetExtensions).GetMethod("Include").MakeGenericMethod(typeof(TDoc), typeof(TProperty));
118118
var exp = Expression.Call(null, includeMethod, Expression, property);
119119
Expression = exp;
120+
return this;
121+
}
122+
123+
public SanityDocumentSet<TDoc> Include<TProperty>(Expression<Func<TDoc, TProperty>> property, string targetName)
124+
{
125+
var includeMethod = typeof(SanityDocumentSetExtensions).GetMethod("Include").MakeGenericMethod(typeof(TDoc), typeof(TProperty), typeof(string));
126+
var exp = Expression.Call(null, includeMethod, Expression, property, Expression.Constant(targetName));
127+
Expression = exp;
120128
return this;
121-
129+
122130
}
123131

124132
public IEnumerator<TDoc> GetEnumerator()

0 commit comments

Comments
 (0)