Skip to content
Merged
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
3 changes: 2 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,8 @@
bool typesAreSameAndImplementCorrectInterface = false;
if (left.Type == right.Type)
{
var interfaces = left.Type.GetInterfaces().Where(x => x.GetTypeInfo().IsGenericType);
var typeToCheck = TypeHelper.GetNonNullableType(left.Type);
var interfaces = typeToCheck.GetInterfaces().Where(x => x.GetTypeInfo().IsGenericType);
if (isEquality)
{
typesAreSameAndImplementCorrectInterface = interfaces.Any(x => x.GetGenericTypeDefinition() == typeof(IEquatable<>));
Expand Down Expand Up @@ -1504,7 +1505,7 @@
{
if (!propertyNames.Add(propName!))
{
throw ParseError(exprPos, Res.DuplicateIdentifier, propName);

Check warning on line 1508 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Windows: Build and Tests

Possible null reference argument for parameter 'args' in 'Exception ExpressionParser.ParseError(int pos, string format, params object[] args)'.
}

properties.Add(new DynamicProperty(propName!, expr.Type));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#if !NET452
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Reflection;
using FluentAssertions;
using NodaTime;
using NodaTime.Text;
Expand Down Expand Up @@ -158,20 +155,82 @@ public void FilterByNullableLocalDate_WithDynamicExpressionParser_CompareWithNul
result.Should().HaveCount(numberOfEntities);
}

public class LocalDateConverter : TypeConverter
private class EntityWithInstant
{
public Instant Timestamp { get; set; }
public Instant? TimestampNullable { get; set; }
}

[Theory]
[InlineData(">", 1)]
[InlineData(">=", 2)]
[InlineData("<", 1)]
[InlineData("<=", 2)]
[InlineData("==", 1)]
[InlineData("!=", 2)]
public void FilterByInstant_WithRelationalOperator(string op, int expectedCount)
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string);
// Arrange
var now = SystemClock.Instance.GetCurrentInstant();
var data = new List<EntityWithInstant>
{
new EntityWithInstant { Timestamp = now - Duration.FromHours(1) },
new EntityWithInstant { Timestamp = now },
new EntityWithInstant { Timestamp = now + Duration.FromHours(1) }
}.AsQueryable();

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
// Act
var result = data.Where($"Timestamp {op} @0", now).ToList();

// Assert
result.Should().HaveCount(expectedCount);
}

[Theory]
[InlineData(">", 1)]
[InlineData(">=", 2)]
[InlineData("<", 1)]
[InlineData("<=", 2)]
[InlineData("==", 1)]
[InlineData("!=", 3)] // null != now evaluates to true in C# nullable semantics
public void FilterByNullableInstant_WithRelationalOperator(string op, int expectedCount)
{
// Arrange
var now = SystemClock.Instance.GetCurrentInstant();
var data = new List<EntityWithInstant>
{
var result = LocalDatePattern.Iso.Parse(value as string);
new EntityWithInstant { TimestampNullable = now - Duration.FromHours(1) },
new EntityWithInstant { TimestampNullable = now },
new EntityWithInstant { TimestampNullable = now + Duration.FromHours(1) },
new EntityWithInstant { TimestampNullable = null }
}.AsQueryable();

// Act
var result = data.Where($"TimestampNullable {op} @0", now).ToList();

return result.Success
? result.Value
: throw new FormatException(value?.ToString());
// Assert - null values are excluded from comparison results
result.Should().HaveCount(expectedCount);
}

public class LocalDateConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => sourceType == typeof(string);

public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
var result = Convert(value);
return result.Success ? result.Value : throw new FormatException(value?.ToString());
}

protected ParseResult<LocalDate> Convert(object value) => LocalDatePattern.Iso.Parse(value as string);
private static ParseResult<LocalDate> Convert(object value)
{
if (value is string stringValue)
{
return LocalDatePattern.Iso.Parse(stringValue);
}

return ParseResult<LocalDate>.ForException(() => new FormatException(value?.ToString()));
}
}
}
}
Expand Down
Loading