Skip to content

Resolve LINQ expression translation for ==/!= null #538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 31 additions & 26 deletions src/Redis.OM/Common/ExpressionParserUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -590,11 +590,11 @@ private static string ParseFormatMethod(MethodCallExpression exp)

var matches = Regex.Matches(formatString, pattern);
args.AddRange(from Match? match in matches
select match.Value.Substring(1, match.Length - 2)
select match.Value.Substring(1, match.Length - 2)
into subStr
select int.Parse(subStr)
select int.Parse(subStr)
into matchIndex
select formatArgs[matchIndex]);
select formatArgs[matchIndex]);
sb.Append(string.Join(",", args));
sb.Append(")");
return sb.ToString();
Expand Down Expand Up @@ -642,37 +642,37 @@ private static string ParseSplitMethod(MethodCallExpression exp)
switch (arg)
{
case MemberExpression { Expression: ConstantExpression constExp } member:
{
var innerArgList = new List<string>();
if (member.Type == typeof(char[]))
{
var charArr = (char[])GetValue(member.Member, constExp.Value);
innerArgList.AddRange(charArr.Select(c => c.ToString()));
}
else if (member.Type == typeof(string[]))
{
var stringArr = (string[])GetValue(member.Member, constExp.Value);
innerArgList.AddRange(stringArr);
}
var innerArgList = new List<string>();
if (member.Type == typeof(char[]))
{
var charArr = (char[])GetValue(member.Member, constExp.Value);
innerArgList.AddRange(charArr.Select(c => c.ToString()));
}
else if (member.Type == typeof(string[]))
{
var stringArr = (string[])GetValue(member.Member, constExp.Value);
innerArgList.AddRange(stringArr);
}

args.Add($"\"{string.Join(",", innerArgList)}\"");
break;
}
args.Add($"\"{string.Join(",", innerArgList)}\"");
break;
}

case NewArrayExpression arrayExpression:
{
var innerArgList = new List<string>();
foreach (var item in arrayExpression.Expressions)
{
if (item is ConstantExpression constant)
var innerArgList = new List<string>();
foreach (var item in arrayExpression.Expressions)
{
innerArgList.Add(GetConstantStringForArgs(constant));
if (item is ConstantExpression constant)
{
innerArgList.Add(GetConstantStringForArgs(constant));
}
}
}

args.Add($"\"{string.Join(",", innerArgList)}\"");
break;
}
args.Add($"\"{string.Join(",", innerArgList)}\"");
break;
}
}
}

Expand Down Expand Up @@ -1038,6 +1038,11 @@ private static string TranslateAnyForEmbeddedObjects(MethodCallExpression exp, L

private static string ValueToString(object value)
{
if (value is null)
{
return "null";
}

Type valueType = value.GetType();

if (valueType == typeof(double) || Nullable.GetUnderlyingType(valueType) == typeof(double))
Expand Down
143 changes: 74 additions & 69 deletions src/Redis.OM/Common/ExpressionTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,79 +196,79 @@ internal static RedisQuery BuildQueryFromExpression(Expression expression, Type
switch (expression)
{
case MethodCallExpression methodExpression:
{
var expressions = new List<MethodCallExpression> { methodExpression };
while (methodExpression.Arguments[0] is MethodCallExpression innerExpression)
{
expressions.Add(innerExpression);
methodExpression = innerExpression;
}
var expressions = new List<MethodCallExpression> { methodExpression };
while (methodExpression.Arguments[0] is MethodCallExpression innerExpression)
{
expressions.Add(innerExpression);
methodExpression = innerExpression;
}

foreach (var exp in expressions)
{
switch (exp.Method.Name)
foreach (var exp in expressions)
{
case "OrderBy":
query.SortBy = TranslateOrderByMethod(exp, true);
break;
case "OrderByDescending":
query.SortBy = TranslateOrderByMethod(exp, false);
break;
case "Select":
query.Return = TranslateSelectMethod(exp, rootType, attr);
break;
case "Take":
query.Limit ??= new SearchLimit { Offset = 0 };
query.Limit.Number = TranslateTake(exp);
break;
case "Skip":
query.Limit ??= new SearchLimit { Number = 100 };
query.Limit.Offset = TranslateSkip(exp);
break;
case "First":
case "Any":
case "FirstOrDefault":
query.Limit ??= new SearchLimit { Offset = 0 };
query.Limit.Number = 1;
break;
case "GeoFilter":
query.GeoFilter = ExpressionParserUtilities.TranslateGeoFilter(exp);
break;
case "Where":
// Combine Where clause with existing query
var whereClause = TranslateWhereMethod(exp, parameters, ref dialect);
query.QueryText = query.QueryText == "*" ?
whereClause :
$"({whereClause} {query.QueryText})";
query.Dialect = dialect;
break;
case "NearestNeighbors":
query.NearestNeighbors = ParseNearestNeighborsFromExpression(exp);
break;
case "Raw":
// Get the current raw query
var currentRawQuery = ((ConstantExpression)exp.Arguments[1]).Value.ToString();

// If we've seen a raw query before, combine them
if (rawQuery != null)
{
rawQuery = $"({rawQuery} {currentRawQuery})";
}
else
{
rawQuery = currentRawQuery;
}

// Set the query text appropriately
query.QueryText = query.QueryText == "*" ?
rawQuery :
$"({query.QueryText} {rawQuery})";
break;
switch (exp.Method.Name)
{
case "OrderBy":
query.SortBy = TranslateOrderByMethod(exp, true);
break;
case "OrderByDescending":
query.SortBy = TranslateOrderByMethod(exp, false);
break;
case "Select":
query.Return = TranslateSelectMethod(exp, rootType, attr);
break;
case "Take":
query.Limit ??= new SearchLimit { Offset = 0 };
query.Limit.Number = TranslateTake(exp);
break;
case "Skip":
query.Limit ??= new SearchLimit { Number = 100 };
query.Limit.Offset = TranslateSkip(exp);
break;
case "First":
case "Any":
case "FirstOrDefault":
query.Limit ??= new SearchLimit { Offset = 0 };
query.Limit.Number = 1;
break;
case "GeoFilter":
query.GeoFilter = ExpressionParserUtilities.TranslateGeoFilter(exp);
break;
case "Where":
// Combine Where clause with existing query
var whereClause = TranslateWhereMethod(exp, parameters, ref dialect);
query.QueryText = query.QueryText == "*" ?
whereClause :
$"({whereClause} {query.QueryText})";
query.Dialect = dialect;
break;
case "NearestNeighbors":
query.NearestNeighbors = ParseNearestNeighborsFromExpression(exp);
break;
case "Raw":
// Get the current raw query
var currentRawQuery = ((ConstantExpression)exp.Arguments[1]).Value.ToString();

// If we've seen a raw query before, combine them
if (rawQuery != null)
{
rawQuery = $"({rawQuery} {currentRawQuery})";
}
else
{
rawQuery = currentRawQuery;
}

// Set the query text appropriately
query.QueryText = query.QueryText == "*" ?
rawQuery :
$"({query.QueryText} {rawQuery})";
break;
}
}
}

break;
}
break;
}

case LambdaExpression lambda:
query.QueryText = BuildQueryFromExpression(lambda.Body, parameters, ref dialect);
Expand Down Expand Up @@ -415,7 +415,12 @@ internal static string TranslateBinaryExpression(BinaryExpression binExpression,
if (rightResolvesToNull)
{
dialectNeeded |= 1 << 1;
return $"(ismissing({leftContent}))";
return binExpression.NodeType switch
{
ExpressionType.Equal => $"(ismissing({leftContent}))",
ExpressionType.NotEqual => $"-(ismissing({leftContent}))",
_ => throw new ArgumentException($"The expression node type {binExpression.NodeType} is not supported"),
};
}

var rightContent = ExpressionParserUtilities.GetOperandStringForQueryArgs(binExpression.Right, parameters, ref dialectNeeded, treatBooleanMemberAsUnary: true);
Expand Down