Skip to content

Commit 0e3c4b4

Browse files
authored
Fixing issues with how selects work with Complex types (#363)
* Fixing issues with how selects work with Complex types * revving version
1 parent dac4774 commit 0e3c4b4

19 files changed

+327
-55
lines changed

src/Redis.OM/Aggregation/AggregationPredicates/QueryPredicate.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ protected override void ValidateAndPushOperand(Expression expression, Stack<stri
7272
var val = ExpressionParserUtilities.GetOperandStringForQueryArgs(binaryExpression.Right);
7373
stack.Push(BuildQueryPredicate(binaryExpression.NodeType, memberExpression.Member, System.Linq.Expressions.Expression.Constant(val)));
7474
}
75-
7675
}
7776
else if (expression is ConstantExpression c
7877
&& c.Value.ToString() == "*")

src/Redis.OM/Aggregation/RedisAggregation.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public RedisAggregation(string indexName)
3131
/// <summary>
3232
/// Gets or sets the query predicate.
3333
/// </summary>
34-
public List<QueryPredicate> Queries { get; set; } = new();
34+
public List<QueryPredicate> Queries { get; set; } = new ();
35+
3536
/// <summary>
3637
/// Gets or sets the limit.
3738
/// </summary>
@@ -56,12 +57,14 @@ public string[] Serialize()
5657
{
5758
queries.AddRange(query.Serialize());
5859
}
60+
5961
ret.AddRange(new[] { string.Join(" ", queries) });
6062
}
6163
else
6264
{
6365
ret.Add("*");
6466
}
67+
6568
foreach (var predicate in Predicates)
6669
{
6770
ret.AddRange(predicate.Serialize());

src/Redis.OM/Common/ExpressionTranslator.cs

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ public static RedisAggregation BuildAggregationFromExpression(Expression express
172172
/// <param name="expression">The expression.</param>
173173
/// <param name="type">The root type.</param>
174174
/// <param name="mainBooleanExpression">The primary boolean expression to build the filter from.</param>
175+
/// <param name="rootType">The root type for the expression.</param>
175176
/// <returns>A Redis query.</returns>
176177
/// <exception cref="InvalidOperationException">Thrown if type is missing indexing.</exception>
177-
internal static RedisQuery BuildQueryFromExpression(Expression expression, Type type, Expression? mainBooleanExpression)
178+
internal static RedisQuery BuildQueryFromExpression(Expression expression, Type type, Expression? mainBooleanExpression, Type rootType)
178179
{
179180
var attr = type.GetCustomAttribute<DocumentAttribute>();
180181
if (attr == null)
@@ -206,7 +207,7 @@ internal static RedisQuery BuildQueryFromExpression(Expression expression, Type
206207
query.SortBy = TranslateOrderByMethod(exp, false);
207208
break;
208209
case "Select":
209-
query.Return = TranslateSelectMethod(exp);
210+
query.Return = TranslateSelectMethod(exp, rootType, attr);
210211
break;
211212
case "Take":
212213
query.Limit ??= new SearchLimit { Offset = 0 };
@@ -558,16 +559,109 @@ private static void TranslateAndPushTwoArgumentReductionPredicate(MethodCallExpr
558559

559560
private static int TranslateSkip(MethodCallExpression exp) => (int)((ConstantExpression)exp.Arguments[1]).Value;
560561

561-
private static ReturnFields TranslateSelectMethod(MethodCallExpression expression)
562+
private static string AliasOrPath(Type t, DocumentAttribute attr, MemberExpression expression)
563+
{
564+
if (attr.StorageType == StorageType.Json)
565+
{
566+
var innerMember = expression.Expression as MemberExpression;
567+
if (innerMember != null)
568+
{
569+
Expression innerExpression = innerMember;
570+
var pathStack = new Stack<string>();
571+
pathStack.Push(expression.Member.Name);
572+
while (innerMember != null)
573+
{
574+
pathStack.Push(innerMember.Member.Name);
575+
innerMember = innerMember.Expression as MemberExpression;
576+
}
577+
578+
return $"$.{string.Join(".", pathStack)}";
579+
}
580+
581+
if (expression.Member.DeclaringType != null && RedisSchemaField.IsComplexType(expression.Type))
582+
{
583+
return $"$.{expression.Member.Name}"; // this can't have been aliased so return a path to it.
584+
}
585+
586+
var searchField = expression.Member.GetCustomAttributes(typeof(SearchFieldAttribute)).FirstOrDefault();
587+
if (searchField != default)
588+
{
589+
return expression.Member.Name;
590+
}
591+
592+
return $"$.{expression.Member.Name}";
593+
}
594+
else
595+
{
596+
return expression.Member.Name;
597+
}
598+
}
599+
600+
private static ReturnFields TranslateSelectMethod(MethodCallExpression expression, Type t, DocumentAttribute attr)
562601
{
563602
var predicate = (UnaryExpression)expression.Arguments[1];
564603
var lambda = (LambdaExpression)predicate.Operand;
565604

566605
if (lambda.Body is MemberExpression member)
567606
{
568-
var properties = new[] { member.Member.Name };
607+
var properties = new[] { AliasOrPath(t, attr, member) };
569608
return new ReturnFields(properties);
570609
}
610+
611+
if (lambda.Body is MemberInitExpression memberInitExpression)
612+
{
613+
var returnFields = new List<ReturnField>();
614+
foreach (var binding in memberInitExpression.Bindings)
615+
{
616+
if (binding is MemberAssignment assignment)
617+
{
618+
if (assignment.Expression is MemberExpression assignmentExpression)
619+
{
620+
var path = AliasOrPath(t, attr, assignmentExpression);
621+
if (assignmentExpression.Member.Name == binding.Member.Name)
622+
{
623+
returnFields.Add(new (path));
624+
}
625+
else
626+
{
627+
returnFields.Add(new (path, binding.Member.Name));
628+
}
629+
}
630+
}
631+
}
632+
633+
return new ReturnFields(returnFields);
634+
}
635+
636+
if (lambda.Body is NewExpression newExpression)
637+
{
638+
var returnFields = new List<ReturnField>();
639+
if (newExpression.Members.Count != newExpression.Arguments.Count())
640+
{
641+
throw new ArgumentException(
642+
"Could not parse Select predicate because of the shape of the new expresssion");
643+
}
644+
645+
for (var i = 0; i < newExpression.Arguments.Count; i++)
646+
{
647+
var newExpressionArg = newExpression.Arguments[i];
648+
var memberInfo = newExpression.Members[i];
649+
if (newExpressionArg is MemberExpression newExpressionMember)
650+
{
651+
var path = AliasOrPath(t, attr, newExpressionMember);
652+
if (newExpressionMember.Member.Name == memberInfo.Name)
653+
{
654+
returnFields.Add(new ReturnField(path, memberInfo.Name));
655+
}
656+
else
657+
{
658+
returnFields.Add(new ReturnField(path, memberInfo.Name));
659+
}
660+
}
661+
}
662+
663+
return new ReturnFields(returnFields);
664+
}
571665
else
572666
{
573667
var properties = lambda.ReturnType.GetProperties().Select(x => x.Name);

src/Redis.OM/Modeling/JsonDiff.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using Newtonsoft.Json;
2-
using System.Web;
1+
using System.Web;
32
using Newtonsoft.Json.Linq;
43

54
namespace Redis.OM.Modeling

src/Redis.OM/Modeling/RedisSchemaField.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace Redis.OM.Modeling
1212
/// </summary>
1313
internal static class RedisSchemaField
1414
{
15+
/// <summary>
16+
/// Checks the type to see if it's a complex type that cannot be used as a scalar in RediSearch.
17+
/// </summary>
18+
/// <param name="type">The Type to check.</param>
19+
/// <returns>Whether not we consider the type to be complex.</returns>
1520
internal static bool IsComplexType(Type type)
1621
{
1722
return !TypeDeterminationUtilities.IsNumeric(type)

src/Redis.OM/Redis.OM.csproj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
<RootNamespace>Redis.OM</RootNamespace>
77
<Nullable>enable</Nullable>
88
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
9-
<PackageVersion>0.5.0</PackageVersion>
10-
<Version>0.5.0</Version>
11-
<PackageReleaseNotes>https://github.com/redis/redis-om-dotnet/releases/tag/v0.5.0</PackageReleaseNotes>
9+
<PackageVersion>0.5.1</PackageVersion>
10+
<Version>0.5.1</Version>
11+
<PackageReleaseNotes>https://github.com/redis/redis-om-dotnet/releases/tag/v0.5.1</PackageReleaseNotes>
1212
<Description>Object Mapping and More for Redis</Description>
1313
<Title>Redis OM</Title>
1414
<Authors>Steve Lorello</Authors>
@@ -21,6 +21,7 @@
2121
<RepositoryType>Github</RepositoryType>
2222
<PackageTags>redis redisearch indexing databases</PackageTags>
2323
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
24+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
2425
</PropertyGroup>
2526

2627
<ItemGroup>

src/Redis.OM/RedisObjectHandler.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ internal static T FromHashSet<T>(IDictionary<string, string> hash)
5151
{
5252
asJson = hash["$"];
5353
}
54+
else if (hash.Keys.Count > 0 && hash.Keys.All(x => x.StartsWith("$")))
55+
{
56+
asJson = hash.Values.First();
57+
}
5458
else
5559
{
5660
asJson = SendToJson(hash, typeof(T));
@@ -500,6 +504,12 @@ private static string SendToJson(IDictionary<string, string> hash, Type t)
500504
ret += SendToJson(entries, type);
501505
ret += ",";
502506
}
507+
else if (hash.ContainsKey(propertyName))
508+
{
509+
ret += $"\"{propertyName}\":";
510+
ret += hash[propertyName];
511+
ret += ",";
512+
}
503513
}
504514
}
505515

src/Redis.OM/Scripts.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ local second_op
155155
/// <summary>
156156
/// The scripts.
157157
/// </summary>
158-
internal static readonly Dictionary<string, string> ScriptCollection = new()
158+
internal static readonly Dictionary<string, string> ScriptCollection = new ()
159159
{
160160
{ nameof(JsonDiffResolution), JsonDiffResolution },
161161
{ nameof(HashDiffResolution), HashDiffResolution },
@@ -170,6 +170,6 @@ local second_op
170170
/// <summary>
171171
/// Gets or sets collection of SHAs.
172172
/// </summary>
173-
internal static ConcurrentDictionary<string, string> ShaCollection { get; set; } = new();
173+
internal static ConcurrentDictionary<string, string> ShaCollection { get; set; } = new ();
174174
}
175175
}

src/Redis.OM/SearchExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ public static IRedisCollection<TR> Select<T, TR>(this IRedisCollection<T> source
116116
null,
117117
GetMethodInfo(Select, source, expression),
118118
new[] { source.Expression, Expression.Quote(expression) });
119-
return new RedisCollection<TR>((RedisQueryProvider)source.Provider, exp, source.StateManager, null, source.SaveState, source.ChunkSize);
119+
var res = new RedisCollection<TR>((RedisQueryProvider)source.Provider, exp, source.StateManager, null, source.SaveState, source.ChunkSize);
120+
res.RootType = typeof(T);
121+
return res;
120122
}
121123

122124
/// <summary>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Redis.OM.Searching.Query
2+
{
3+
/// <summary>
4+
/// Represents a return field with a name and an optional alias.
5+
/// </summary>
6+
public struct ReturnField
7+
{
8+
/// <summary>
9+
/// The name of the return field.
10+
/// </summary>
11+
public string Name;
12+
13+
/// <summary>
14+
/// An optional alias for the return field.
15+
/// </summary>
16+
public string? Alias;
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="ReturnField"/> struct with the specified name and alias.
20+
/// </summary>
21+
/// <param name="name">The name of the return field.</param>
22+
/// <param name="alias">An optional alias for the return field.</param>
23+
public ReturnField(string name, string alias)
24+
{
25+
Name = name;
26+
Alias = alias;
27+
}
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="ReturnField"/> struct with the specified name.
31+
/// </summary>
32+
/// <param name="name">The name of the return field.</param>
33+
public ReturnField(string name)
34+
{
35+
Name = name;
36+
Alias = null;
37+
}
38+
}
39+
}

src/Redis.OM/Searching/Query/ReturnFields.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34

45
namespace Redis.OM.Searching.Query
@@ -11,13 +12,22 @@ public class ReturnFields : QueryOption
1112
/// <summary>
1213
/// The fields to bring back.
1314
/// </summary>
14-
private readonly IEnumerable<string> _fields;
15+
private readonly IEnumerable<ReturnField> _fields;
1516

1617
/// <summary>
1718
/// Initializes a new instance of the <see cref="ReturnFields"/> class.
1819
/// </summary>
1920
/// <param name="fields">the fields to return.</param>
2021
public ReturnFields(IEnumerable<string> fields)
22+
{
23+
_fields = fields.Select(x => new ReturnField(x));
24+
}
25+
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="ReturnFields"/> class.
28+
/// </summary>
29+
/// <param name="fields">the fields to return.</param>
30+
public ReturnFields(IEnumerable<ReturnField> fields)
2131
{
2232
_fields = fields;
2333
}
@@ -27,10 +37,15 @@ internal override IEnumerable<string> SerializeArgs
2737
{
2838
get
2939
{
30-
var ret = new List<string> { "RETURN", _fields.Count().ToString() };
40+
var ret = new List<string> { "RETURN", (_fields.Count() + (_fields.Count(x => x.Alias != null) * 2)).ToString() };
3141
foreach (var field in _fields)
3242
{
33-
ret.Add($"{field}");
43+
ret.Add($"{field.Name}");
44+
if (field.Alias != null)
45+
{
46+
ret.Add("AS");
47+
ret.Add(field.Alias);
48+
}
3449
}
3550

3651
return ret.ToArray();

0 commit comments

Comments
 (0)