diff --git a/src/DocumentDbTests/Indexes/computed_indexes.cs b/src/DocumentDbTests/Indexes/computed_indexes.cs index 0168bf76b2..e2ce57284f 100644 --- a/src/DocumentDbTests/Indexes/computed_indexes.cs +++ b/src/DocumentDbTests/Indexes/computed_indexes.cs @@ -194,6 +194,7 @@ index definition was executed in postgresql. StoreOptions(_ => { + _.RegisterFSharpOptionValueTypes(); var columns = new Expression>[] { x => x.FSharpDateOption, diff --git a/src/LinqTests/Acceptance/where_clauses_fsharp.cs b/src/LinqTests/Acceptance/where_clauses_fsharp.cs index 9498390f97..48f76a6808 100644 --- a/src/LinqTests/Acceptance/where_clauses_fsharp.cs +++ b/src/LinqTests/Acceptance/where_clauses_fsharp.cs @@ -22,6 +22,9 @@ static where_clauses_fsharp() @where(x => x.FSharpIntOption == FSharpOption.Some(300)); @where(x => x.FSharpStringOption == FSharpOption.Some("My String")); @where(x => x.FSharpLongOption == FSharpOption.Some(5_000_000)); + @where(x => x.FSharpTimeOnlyOption == FSharpOption.Some(new TimeOnly(5,10))); + @where(x => x.FSharpDateTimeOffsetOption == FSharpOption.Some(DateTimeOffset.UtcNow)); + @where(x => x.FSharpDateOnlyOption == FSharpOption.Some(new DateOnly(2008, 10, 10))); //Comparing options is not a valid syntax in C#, we therefore define these expressions in F# @where(FSharpTypes.greaterThanWithFsharpDateOption); diff --git a/src/Marten/Linq/Members/DateTimeOffsetMember.cs b/src/Marten/Linq/Members/DateTimeOffsetMember.cs index 7feb99b001..2c4027b0b8 100644 --- a/src/Marten/Linq/Members/DateTimeOffsetMember.cs +++ b/src/Marten/Linq/Members/DateTimeOffsetMember.cs @@ -2,6 +2,7 @@ using System; using System.Linq.Expressions; using System.Reflection; +using Marten.Linq.Parsing; using Marten.Linq.SqlGeneration.Filters; using Marten.Util; using Weasel.Postgresql.SqlGeneration; @@ -23,12 +24,13 @@ public override string SelectorForDuplication(string pgType) public override ISqlFragment CreateComparison(string op, ConstantExpression constant) { - if (constant.Value == null) + var unwrappedValue = constant.UnwrapValue(); + if (unwrappedValue == null) { return op == "=" ? new IsNullFilter(this) : new IsNotNullFilter(this); } - var value = (DateTimeOffset)constant.Value; + var value = (DateTimeOffset)unwrappedValue; var def = new CommandParameter(value.ToUniversalTime()); return new MemberComparisonFilter(this, def, op); diff --git a/src/Marten/Linq/Members/QueryableMember.cs b/src/Marten/Linq/Members/QueryableMember.cs index 50395df8b4..c5c716fb82 100644 --- a/src/Marten/Linq/Members/QueryableMember.cs +++ b/src/Marten/Linq/Members/QueryableMember.cs @@ -143,12 +143,13 @@ public virtual string SelectorForDuplication(string pgType) public virtual ISqlFragment CreateComparison(string op, ConstantExpression constant) { - if (constant.Value == null) + var unwrappedValue = constant.UnwrapValue(); + if (unwrappedValue == null) { return op == "=" ? new IsNullFilter(this) : new IsNotNullFilter(this); } - var def = new CommandParameter(constant); + var def = new CommandParameter(Expression.Constant(unwrappedValue)); return new MemberComparisonFilter(this, def, op); } } diff --git a/src/Marten/Linq/Parsing/ConstantExpressionExtensions.cs b/src/Marten/Linq/Parsing/ConstantExpressionExtensions.cs new file mode 100644 index 0000000000..c448653641 --- /dev/null +++ b/src/Marten/Linq/Parsing/ConstantExpressionExtensions.cs @@ -0,0 +1,35 @@ +#nullable enable +using System.Linq.Expressions; +using Microsoft.FSharp.Core; + +namespace Marten.Linq.Parsing; + +public static class ConstantExpressionExtensions +{ + public static object? UnwrapValue(this ConstantExpression constant) + { + var value = constant.Value; + + // If the value is null, it is either a C# null or FSharpOption.None. + // In either case, there is nothing to unwrap. + if (value == null) + { + return null; + } + + var valueType = value.GetType(); + + // Check if it is an F# Option + // Note: FSharpOption is a class, not a struct. + if (valueType.IsGenericType && + valueType.GetGenericTypeDefinition() == typeof(FSharpOption<>)) + { + // If we are here, 'value' is not null, so it MUST be 'Some'. + // We can safely just grab the 'Value' property. + var valueProp = valueType.GetProperty("Value"); + return valueProp?.GetValue(value); + } + + return value; + } +} diff --git a/src/Marten/StoreOptions.MemberFactory.cs b/src/Marten/StoreOptions.MemberFactory.cs index ff08fbf8e7..33e687d4bb 100644 --- a/src/Marten/StoreOptions.MemberFactory.cs +++ b/src/Marten/StoreOptions.MemberFactory.cs @@ -152,6 +152,14 @@ public bool TryResolve(IQueryableMember parent, StoreOptions options, MemberInfo return false; } + if (valueType.OuterType.IsGenericType + && valueType.OuterType.GetGenericTypeDefinition() == typeof(FSharpOption<>) + && isSpecialFSharpOptionDateType(valueType.SimpleType)) + { + member = null; + return false; + } + Type baseType; if (valueType.OuterType.IsGenericType && valueType.OuterType.GetGenericTypeDefinition() == typeof(FSharpOption<>)) { @@ -170,4 +178,12 @@ public bool TryResolve(IQueryableMember parent, StoreOptions options, MemberInfo return true; } + + private static bool isSpecialFSharpOptionDateType(Type simpleType) + { + return simpleType == typeof(DateTime) + || simpleType == typeof(DateTimeOffset) + || simpleType == typeof(DateOnly) + || simpleType == typeof(TimeOnly); + } }