Skip to content

Commit aebfbcd

Browse files
committed
Merge branch 'master' into stef-NET10
2 parents e7f53f4 + 3aa76d9 commit aebfbcd

File tree

16 files changed

+321
-44
lines changed

16 files changed

+321
-44
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# v1.6.10 (08 November 2025)
2+
- [#953](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/953) - Fixed adding Enum and integer [bug] contributed by [StefH](https://github.com/StefH)
3+
- [#954](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/954) - Fix ExpressionHelper.TryConvertTypes to generate correct Convert in case left or right is null [bug] contributed by [StefH](https://github.com/StefH)
4+
- [#951](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/951) - Parsing error adding numeric constant to enum value [bug]
5+
- [#952](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/952) - Json: How to handle not existing member [bug]
6+
7+
# v1.6.9 (11 October 2025)
8+
- [#950](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/950) - DynamicExpressionParser - Handle indexed properties with any number of indices in expression [bug] contributed by [thibault-reigner](https://github.com/thibault-reigner)
9+
110
# v1.6.8 (28 September 2025)
211
- [#946](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/946) - Fix GroupByMany using composite key and normal key [bug] contributed by [StefH](https://github.com/StefH)
312
- [#948](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/948) - Add IndexerName attribute to DynamicClass to fix naming issues with Item [bug] contributed by [StefH](https://github.com/StefH)

Generate-ReleaseNotes.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
rem https://github.com/StefH/GitHubReleaseNotes
22

3-
SET version=v1.6.8
3+
SET version=v1.6.10
44

55
GitHubReleaseNotes --output CHANGELOG.md --exclude-labels known_issue out_of_scope not_planned invalid question documentation wontfix environment duplicate --language en --version %version% --token %GH_TOKEN%

src/System.Linq.Dynamic.Core.NewtonsoftJson/NewtonsoftJsonExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ public static JArray Where(this JArray source, NewtonsoftJsonParsingConfig confi
821821

822822
if (source.Count == 0)
823823
{
824-
return new JArray();
824+
return [];
825825
}
826826

827827
var queryable = ToQueryable(source, config);
@@ -848,7 +848,8 @@ public static JArray Where(this JArray source, NewtonsoftJsonParsingConfig confi
848848
private static JArray ToJArray(Func<IQueryable> func)
849849
{
850850
var array = new JArray();
851-
foreach (var dynamicElement in func())
851+
var funcResult = func();
852+
foreach (var dynamicElement in funcResult)
852853
{
853854
var element = dynamicElement switch
854855
{

src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,12 @@ public bool ExpressionQualifiesForNullPropagation(Expression? expression)
361361
public Expression GenerateDefaultExpression(Type type)
362362
{
363363
#if NET35
364-
return Expression.Constant(Activator.CreateInstance(type));
364+
if (type.IsValueType)
365+
{
366+
return Expression.Constant(Activator.CreateInstance(type), type);
367+
}
368+
369+
return Expression.Constant(null, type);
365370
#else
366371
return Expression.Default(type);
367372
#endif
@@ -388,11 +393,19 @@ public bool TryConvertTypes(ref Expression left, ref Expression right)
388393

389394
if (left.Type == typeof(object))
390395
{
391-
left = Expression.Convert(left, right.Type);
396+
left = Expression.Condition(
397+
Expression.Equal(left, Expression.Constant(null, typeof(object))),
398+
GenerateDefaultExpression(right.Type),
399+
Expression.Convert(left, right.Type)
400+
);
392401
}
393402
else if (right.Type == typeof(object))
394403
{
395-
right = Expression.Convert(right, left.Type);
404+
right = Expression.Condition(
405+
Expression.Equal(right, Expression.Constant(null, typeof(object))),
406+
GenerateDefaultExpression(left.Type),
407+
Expression.Convert(right, left.Type)
408+
);
396409
}
397410

398411
return true;

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2353,20 +2353,22 @@ private Expression ParseElementAccess(Expression expr)
23532353
switch (_methodFinder.FindIndexer(expr.Type, args, out var mb))
23542354
{
23552355
case 0:
2356-
throw ParseError(errorPos, Res.NoApplicableIndexer,
2357-
TypeHelper.GetTypeName(expr.Type));
2356+
throw ParseError(errorPos, Res.NoApplicableIndexer, TypeHelper.GetTypeName(expr.Type), args.Length);
23582357

23592358
case 1:
23602359
var indexMethod = (MethodInfo)mb!;
2361-
var indexParameterType = indexMethod.GetParameters().First().ParameterType;
2360+
var indexMethodArguments = indexMethod.GetParameters();
23622361

2363-
var indexArgumentExpression = args[0]; // Indexer only has 1 parameter, so we can use args[0] here
2364-
if (indexParameterType != indexArgumentExpression.Type)
2362+
var indexArgumentExpressions = new Expression[args.Length];
2363+
for (var i = 0; i < indexMethodArguments.Length; ++i)
23652364
{
2366-
indexArgumentExpression = Expression.Convert(indexArgumentExpression, indexParameterType);
2365+
var indexParameterType = indexMethodArguments[i].ParameterType;
2366+
indexArgumentExpressions[i] = indexParameterType != args[i].Type
2367+
? Expression.Convert(args[i], indexParameterType)
2368+
: args[i];
23672369
}
23682370

2369-
return Expression.Call(expr, indexMethod, indexArgumentExpression);
2371+
return Expression.Call(expr, indexMethod, indexArgumentExpressions);
23702372

23712373
default:
23722374
throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, TypeHelper.GetTypeName(expr.Type));

src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ public ExpressionPromoter(ParsingConfig config)
122122

123123
if (TypeHelper.IsCompatibleWith(returnType, type))
124124
{
125-
if (type == typeof(decimal) && TypeHelper.IsEnumType(sourceExpression.Type))
125+
if (TypeHelper.TypesAreEqual(type, typeof(decimal)) && TypeHelper.IsEnumType(sourceExpression.Type))
126126
{
127127
return Expression.Convert(Expression.Convert(sourceExpression, Enum.GetUnderlyingType(sourceExpression.Type)), type);
128128
}

src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@ internal interface IExpressionHelper
5252
/// <summary>
5353
/// If the types are different (and not null), try to convert the object type to other type.
5454
/// </summary>
55-
public bool TryConvertTypes(ref Expression left, ref Expression right);
55+
bool TryConvertTypes(ref Expression left, ref Expression right);
5656
}

src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,20 @@ public static bool IsCompatibleWith(Type source, Type target)
8383
return target.IsAssignableFrom(source);
8484
}
8585

86-
Type st = GetNonNullableType(source);
87-
Type tt = GetNonNullableType(target);
86+
var sourceType = GetNonNullableType(source);
87+
var targetType = GetNonNullableType(target);
8888

89-
if (st != source && tt == target)
89+
if (sourceType != source && targetType == target)
9090
{
9191
return false;
9292
}
9393

94-
TypeCode sc = st.GetTypeInfo().IsEnum ? TypeCode.Int64 : Type.GetTypeCode(st);
95-
TypeCode tc = tt.GetTypeInfo().IsEnum ? TypeCode.Int64 : Type.GetTypeCode(tt);
96-
switch (sc)
94+
var sourceTypeCode = sourceType.GetTypeInfo().IsEnum ? TypeCode.Int32 : Type.GetTypeCode(sourceType);
95+
var targetTypeCode = targetType.GetTypeInfo().IsEnum ? TypeCode.Int32 : Type.GetTypeCode(targetType);
96+
switch (sourceTypeCode)
9797
{
9898
case TypeCode.SByte:
99-
switch (tc)
99+
switch (targetTypeCode)
100100
{
101101
case TypeCode.SByte:
102102
case TypeCode.Int16:
@@ -110,7 +110,7 @@ public static bool IsCompatibleWith(Type source, Type target)
110110
break;
111111

112112
case TypeCode.Byte:
113-
switch (tc)
113+
switch (targetTypeCode)
114114
{
115115
case TypeCode.Byte:
116116
case TypeCode.Int16:
@@ -127,7 +127,7 @@ public static bool IsCompatibleWith(Type source, Type target)
127127
break;
128128

129129
case TypeCode.Int16:
130-
switch (tc)
130+
switch (targetTypeCode)
131131
{
132132
case TypeCode.Int16:
133133
case TypeCode.Int32:
@@ -140,7 +140,7 @@ public static bool IsCompatibleWith(Type source, Type target)
140140
break;
141141

142142
case TypeCode.UInt16:
143-
switch (tc)
143+
switch (targetTypeCode)
144144
{
145145
case TypeCode.UInt16:
146146
case TypeCode.Int32:
@@ -155,7 +155,7 @@ public static bool IsCompatibleWith(Type source, Type target)
155155
break;
156156

157157
case TypeCode.Int32:
158-
switch (tc)
158+
switch (targetTypeCode)
159159
{
160160
case TypeCode.Int32:
161161
case TypeCode.Int64:
@@ -167,7 +167,7 @@ public static bool IsCompatibleWith(Type source, Type target)
167167
break;
168168

169169
case TypeCode.UInt32:
170-
switch (tc)
170+
switch (targetTypeCode)
171171
{
172172
case TypeCode.UInt32:
173173
case TypeCode.Int64:
@@ -180,7 +180,7 @@ public static bool IsCompatibleWith(Type source, Type target)
180180
break;
181181

182182
case TypeCode.Int64:
183-
switch (tc)
183+
switch (targetTypeCode)
184184
{
185185
case TypeCode.Int64:
186186
case TypeCode.Single:
@@ -191,7 +191,7 @@ public static bool IsCompatibleWith(Type source, Type target)
191191
break;
192192

193193
case TypeCode.UInt64:
194-
switch (tc)
194+
switch (targetTypeCode)
195195
{
196196
case TypeCode.UInt64:
197197
case TypeCode.Single:
@@ -202,7 +202,7 @@ public static bool IsCompatibleWith(Type source, Type target)
202202
break;
203203

204204
case TypeCode.Single:
205-
switch (tc)
205+
switch (targetTypeCode)
206206
{
207207
case TypeCode.Single:
208208
case TypeCode.Double:
@@ -211,7 +211,7 @@ public static bool IsCompatibleWith(Type source, Type target)
211211
break;
212212

213213
default:
214-
if (st == tt)
214+
if (sourceType == targetType)
215215
{
216216
return true;
217217
}
@@ -471,6 +471,11 @@ public static Type GetUnderlyingType(Type type)
471471
return type;
472472
}
473473

474+
public static bool TypesAreEqual(Type type, Type typeToCheck)
475+
{
476+
return GetNullableType(type) == GetNullableType(typeToCheck);
477+
}
478+
474479
public static IList<Type> GetSelfAndBaseTypes(Type type, bool excludeObject = false)
475480
{
476481
if (type.GetTypeInfo().IsInterface)

src/System.Linq.Dynamic.Core/Res.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ internal static class Res
5555
public const string MissingAsClause = "Expression is missing an 'as' clause";
5656
public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";
5757
public const string NewOperatorIsNotAllowed = "Using the new operator is not allowed via the ParsingConfig.";
58-
public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'";
58+
public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}' with {1} parameters";
5959
public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'";
6060
public const string NoItInScope = "No 'it' is in scope";
6161
public const string NoMatchingConstructor = "No matching constructor in type '{0}'";

test/System.Linq.Dynamic.Core.NewtonsoftJson.Tests/NewtonsoftJsonTests.cs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using FluentAssertions;
1+
using System.Linq.Dynamic.Core.NewtonsoftJson.Config;
2+
using FluentAssertions;
23
using Newtonsoft.Json.Linq;
34
using Xunit;
45

@@ -506,4 +507,62 @@ public void Where_With_Select()
506507
var first = result.First();
507508
first.Value<string>().Should().Be("Doe");
508509
}
510+
511+
//[Fact]
512+
//public void Where_OptionalProperty()
513+
//{
514+
// // Arrange
515+
// var config = new NewtonsoftJsonParsingConfig
516+
// {
517+
// ConvertObjectToSupportComparison = true
518+
// };
519+
// var array =
520+
// """
521+
// [
522+
// {
523+
// "Name": "John",
524+
// "Age": 30
525+
// },
526+
// {
527+
// "Name": "Doe"
528+
// }
529+
// ]
530+
// """;
531+
532+
// // Act
533+
// var result = JArray.Parse(array).Where(config, "Age > 30").Select("Name");
534+
535+
// // Assert
536+
// result.Should().HaveCount(1);
537+
// var first = result.First();
538+
// first.Value<string>().Should().Be("John");
539+
//}
540+
541+
[Theory]
542+
[InlineData("notExisting == true")]
543+
[InlineData("notExisting == \"true\"")]
544+
[InlineData("notExisting == 1")]
545+
[InlineData("notExisting == \"1\"")]
546+
[InlineData("notExisting == \"something\"")]
547+
[InlineData("notExisting > 1")]
548+
[InlineData("true == notExisting")]
549+
[InlineData("\"true\" == notExisting")]
550+
[InlineData("1 == notExisting")]
551+
[InlineData("\"1\" == notExisting")]
552+
[InlineData("\"something\" == notExisting")]
553+
[InlineData("1 < notExisting")]
554+
public void Where_NonExistingMember_EmptyResult(string predicate)
555+
{
556+
// Arrange
557+
var config = new NewtonsoftJsonParsingConfig
558+
{
559+
ConvertObjectToSupportComparison = true
560+
};
561+
562+
// Act
563+
var result = _source.Where(config, predicate);
564+
565+
// Assert
566+
result.Should().BeEmpty();
567+
}
509568
}

0 commit comments

Comments
 (0)