diff --git a/src/FSharpTypes/FSharpTypes.fsproj b/src/FSharpTypes/FSharpTypes.fsproj
index 186eabb173..2edfa524b2 100644
--- a/src/FSharpTypes/FSharpTypes.fsproj
+++ b/src/FSharpTypes/FSharpTypes.fsproj
@@ -1,13 +1,17 @@
- net8.0
true
8.0
+ net8.0;net7.0;net9.0
+
+
+
+
diff --git a/src/FSharpTypes/Library.fs b/src/FSharpTypes/Library.fs
index 498c8ab017..24822290ab 100644
--- a/src/FSharpTypes/Library.fs
+++ b/src/FSharpTypes/Library.fs
@@ -1,6 +1,9 @@
module FSharpTypes
open System
+open System.Linq.Expressions
+open Marten.Testing.Documents
+open Microsoft.FSharp.Linq.RuntimeHelpers
type OrderId = Id of Guid
@@ -14,3 +17,24 @@ type RecordTypeOrderId = { Part1: string; Part2: string }
type ArbitraryClass() =
member this.Value = "ok"
+
+let rec stripFSharpFunc (expression: Expression) =
+ match expression with
+ | :? MethodCallExpression as callExpression when callExpression.Method.Name = "ToFSharpFunc" ->
+ stripFSharpFunc callExpression.Arguments.[0]
+ | _ -> expression
+
+let toLinqExpression expr =
+ expr
+ |> LeafExpressionConverter.QuotationToExpression
+ |> stripFSharpFunc
+ |> unbox>>
+let greaterThanWithFsharpDateOption =
+ <@ fun (o1: Target) -> o1.FSharpDateTimeOffsetOption >= Some DateTimeOffset.UtcNow @> |> toLinqExpression
+let lesserThanWithFsharpDateOption = <@ (fun (o1: Target) -> o1.FSharpDateTimeOffsetOption <= Some DateTimeOffset.UtcNow ) @> |> toLinqExpression
+let greaterThanWithFsharpDecimalOption = <@ (fun (o1: Target) -> o1.FSharpDecimalOption >= Some 5m ) @> |> toLinqExpression
+let lesserThanWithFsharpDecimalOption = <@ (fun (o1: Target) -> o1.FSharpDecimalOption <= Some 5m ) @> |> toLinqExpression
+let greaterThanWithFsharpStringOption = <@ (fun (o1: Target) -> o1.FSharpStringOption >= Some "MyString" ) @> |> toLinqExpression
+let lesserThanWithFsharpStringOption = <@ (fun (o1: Target) -> o1.FSharpStringOption <= Some "MyString" ) @> |> toLinqExpression
+
+
diff --git a/src/LinqTests/Acceptance/Support/DefaultQueryFixture.cs b/src/LinqTests/Acceptance/Support/DefaultQueryFixture.cs
index 72a9c95554..8c07a9eb6f 100644
--- a/src/LinqTests/Acceptance/Support/DefaultQueryFixture.cs
+++ b/src/LinqTests/Acceptance/Support/DefaultQueryFixture.cs
@@ -10,17 +10,31 @@ public class DefaultQueryFixture: TargetSchemaFixture
{
public DefaultQueryFixture()
{
- Store = ProvisionStore("linq_querying", options =>
+ Store = ProvisionStore("linq_querying");
+
+ DuplicatedFieldStore = ProvisionStore("duplicate_fields", o =>
+ {
+ o.Schema.For()
+ .Duplicate(x => x.Number)
+ .Duplicate(x => x.Long)
+ .Duplicate(x => x.String)
+ .Duplicate(x => x.Date)
+ .Duplicate(x => x.Double)
+ .Duplicate(x => x.Flag)
+ .Duplicate(x => x.Color)
+ .Duplicate(x => x.NumberArray);
+ });
+
+ FSharpFriendlyStore = ProvisionStore("linq_querying", options =>
{
options.RegisterFSharpOptionValueTypes();
var serializerOptions = JsonFSharpOptions.Default().WithUnwrapOption().ToJsonSerializerOptions();
options.UseSystemTextJsonForSerialization(serializerOptions);
});
-
- DuplicatedFieldStore = ProvisionStore("duplicate_fields", o =>
+ FSharpFriendlyStoreWithDuplicatedField = ProvisionStore("duplicate_fields", options =>
{
- o.Schema.For()
+ options.Schema.For()
.Duplicate(x => x.Number)
.Duplicate(x => x.Long)
.Duplicate(x => x.String)
@@ -30,9 +44,9 @@ public DefaultQueryFixture()
.Duplicate(x => x.Color)
.Duplicate(x => x.NumberArray);
- o.RegisterFSharpOptionValueTypes();
+ options.RegisterFSharpOptionValueTypes();
var serializerOptions = JsonFSharpOptions.Default().WithUnwrapOption().ToJsonSerializerOptions();
- o.UseSystemTextJsonForSerialization(serializerOptions);
+ options.UseSystemTextJsonForSerialization(serializerOptions);
});
SystemTextJsonStore = ProvisionStore("stj_linq", o =>
@@ -45,5 +59,19 @@ public DefaultQueryFixture()
public DocumentStore DuplicatedFieldStore { get; set; }
+ public DocumentStore FSharpFriendlyStore { get; set; }
+ public DocumentStore FSharpFriendlyStoreWithDuplicatedField { get; set; }
+
public DocumentStore Store { get; set; }
}
+
+public static class DefaultQueryFixtureExtensions
+{
+ public static void UseFSharp(this DocumentStore store)
+ {
+ var o = store.Options;
+ o.RegisterFSharpOptionValueTypes();
+ var serializerOptions = JsonFSharpOptions.Default().WithUnwrapOption().ToJsonSerializerOptions();
+ o.UseSystemTextJsonForSerialization(serializerOptions);
+ }
+}
diff --git a/src/LinqTests/Acceptance/where_clauses.cs b/src/LinqTests/Acceptance/where_clauses.cs
index 4ae1179efb..39af8cb436 100644
--- a/src/LinqTests/Acceptance/where_clauses.cs
+++ b/src/LinqTests/Acceptance/where_clauses.cs
@@ -141,13 +141,6 @@ static where_clauses()
@where(x => !x.Flag == true);
@where(x => !x.Flag == false);
- @where(x => x.FSharpBoolOption == FSharpOption.Some(true));
- @where(x => x.FSharpBoolOption == FSharpOption.Some(false));
- @where(x => x.FSharpDateOption == FSharpOption.Some(DateTime.Now));
- @where(x => x.FSharpIntOption == FSharpOption.Some(300));
- @where(x => x.FSharpStringOption == FSharpOption.Some("My String"));
- @where(x => x.FSharpLongOption == FSharpOption.Some(5_000_000));
-
// Comparing multiple fields
@where(x => x.Number == x.AnotherNumber);
@where(x => x.Number < x.AnotherNumber);
@@ -198,3 +191,4 @@ public Task with_duplicated_fields(string description)
return assertTestCase(description, Fixture.DuplicatedFieldStore);
}
}
+
diff --git a/src/LinqTests/Acceptance/where_clauses_fsharp.cs b/src/LinqTests/Acceptance/where_clauses_fsharp.cs
new file mode 100644
index 0000000000..00f8b73d6b
--- /dev/null
+++ b/src/LinqTests/Acceptance/where_clauses_fsharp.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Threading.Tasks;
+using LinqTests.Acceptance.Support;
+using Microsoft.FSharp.Core;
+using Xunit.Abstractions;
+
+namespace LinqTests.Acceptance;
+
+public class where_clauses_fsharp: LinqTestContext
+{
+ public where_clauses_fsharp(DefaultQueryFixture fixture, ITestOutputHelper output) : base(fixture)
+ {
+ TestOutput = output;
+ }
+
+ static where_clauses_fsharp()
+ {
+
+ @where(x => x.FSharpBoolOption == FSharpOption.Some(true));
+ @where(x => x.FSharpBoolOption == FSharpOption.Some(false));
+ @where(x => x.FSharpDateOption == FSharpOption.Some(DateTime.Now));
+ @where(x => x.FSharpIntOption == FSharpOption.Some(300));
+ @where(x => x.FSharpStringOption == FSharpOption.Some("My String"));
+ @where(x => x.FSharpLongOption == FSharpOption.Some(5_000_000));
+
+ //Comparing options is not a valid syntax in C#, we therefore define these expressions in F#
+ @where(FSharpTypes.greaterThanWithFsharpDateOption);
+ @where(FSharpTypes.lesserThanWithFsharpDateOption);
+ @where(FSharpTypes.greaterThanWithFsharpStringOption);
+ @where(FSharpTypes.lesserThanWithFsharpStringOption);
+ @where(FSharpTypes.greaterThanWithFsharpDecimalOption);
+ @where(FSharpTypes.lesserThanWithFsharpDecimalOption);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetDescriptions))]
+ public Task run_query(string description)
+ {
+ return assertTestCase(description, Fixture.FSharpFriendlyStore);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetDescriptions))]
+ public Task with_duplicated_fields(string description)
+ {
+ return assertTestCase(description, Fixture.FSharpFriendlyStoreWithDuplicatedField);
+ }
+}
diff --git a/src/LinqTests/LinqTests.csproj b/src/LinqTests/LinqTests.csproj
index 3905b6de91..e5e5fced57 100644
--- a/src/LinqTests/LinqTests.csproj
+++ b/src/LinqTests/LinqTests.csproj
@@ -6,6 +6,8 @@
+
+
@@ -56,9 +58,6 @@
Documents\StringDoc.cs
-
- Documents\Target.cs
-
Documents\TargetIntId.cs
diff --git a/src/Marten.Testing/Documents/Target.cs b/src/Marten.Testing/Documents/Target.cs
deleted file mode 100644
index 98bea40d9d..0000000000
--- a/src/Marten.Testing/Documents/Target.cs
+++ /dev/null
@@ -1,255 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json.Serialization;
-using JasperFx.Core;
-using Microsoft.FSharp.Core;
-
-#nullable enable
-namespace Marten.Testing.Documents;
-
-public enum Colors
-{
- Red,
- Blue,
- Green,
- Purple,
- Yellow,
- Orange
-}
-
-public class Target
-{
- private static readonly Random _random = new Random(67);
-
- private static readonly string[] _strings =
- {
- "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Violet", "Pink", "Gray", "Black"
- };
-
- private static readonly string[] _otherStrings =
- {
- "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"
- };
-
- public static IEnumerable GenerateRandomData(int number)
- {
- var i = 0;
- while (i < number)
- {
- yield return Random(true);
-
- i++;
- }
- }
-
- public static Target Random(bool deep = false)
- {
- var target = new Target();
- target.String = _strings[_random.Next(0, 10)];
- target.FSharpGuidOption = new FSharpOption(Guid.NewGuid());
- target.FSharpIntOption = new FSharpOption(_random.Next(0, 10));
- target.FSharpDateOption = new FSharpOption(DateTime.Now);
- target.FSharpDecimalOption = new FSharpOption(_random.Next(0, 10));
- target.FSharpLongOption = new FSharpOption(_random.Next(0, 10));
- target.FSharpStringOption = new FSharpOption(_strings[_random.Next(0, 10)]);
-
- target.PaddedString = " " + target.String + " ";
- target.AnotherString = _otherStrings[_random.Next(0, 10)];
- target.Number = _random.Next();
- target.AnotherNumber = _random.Next();
- target.OtherGuid = Guid.NewGuid();
-
- target.Flag = _random.Next(0, 10) > 5;
-
- target.Float = float.Parse(_random.NextDouble().ToString());
-
- target.NumberArray = _random.Next(0, 10) > 8
- ? new[] { _random.Next(0, 10), _random.Next(0, 10), _random.Next(0, 10) }
- : Array.Empty();
-
- target.NumberArray = target.NumberArray.Distinct().ToArray();
-
- switch (_random.Next(0, 2))
- {
- case 0:
- target.Color = Colors.Blue;
- break;
-
- case 1:
- target.Color = Colors.Green;
- break;
-
- default:
- target.Color = Colors.Red;
- break;
- }
-
- var value = _random.Next(0, 100);
- if (value > 10) target.NullableNumber = value;
-
- if (value > 20)
- {
- var list = new List();
- for (int i = 0; i < 5; i++)
- {
- list.Add(_strings[_random.Next(0, 10)]);
- }
-
- target.StringArray = list.Distinct().ToArray();
- }
-
- target.Long = 100 * _random.Next();
- target.Double = _random.NextDouble();
- target.Long = _random.Next() * 10000;
-
- target.HowLong = TimeSpan.FromSeconds(target.Long);
-
- target.Date = DateTime.Today.AddDays(_random.Next(-10000, 10000));
-
- if (value > 15)
- {
- target.NullableDateOffset = DateTimeOffset.Now.Subtract(_random.Next(-60, 60).Seconds());
- }
-
- if (deep)
- {
- target.Inner = Random();
-
- var number = _random.Next(1, 10);
- target.Children = new Target[number];
- for (var i = 0; i < number; i++)
- {
- target.Children[i] = Random();
- }
-
- target.StringDict = Enumerable.Range(0, _random.Next(0, 10)).ToDictionary(i => $"key{i}", i => $"value{i}");
- target.String = _strings[_random.Next(0, 10)];
- target.OtherGuid = Guid.NewGuid();
- }
-
- return target;
- }
-
- public string PaddedString { get; set; }
-
- public Target()
- {
- Id = Guid.NewGuid();
- StringDict = new Dictionary();
- StringList = new List();
- GuidDict = new Dictionary();
- }
-
- public Guid Id { get; set; }
-
- public int Number { get; set; }
-
- public int AnotherNumber { get; set; }
-
- public long Long { get; set; }
- public string String { get; set; }
-
- public FSharpOption FSharpGuidOption { get; set; }
- public FSharpOption FSharpIntOption { get; set; }
- public FSharpOption FSharpBoolOption { get; set; }
- public FSharpOption FSharpLongOption { get; set; }
- public FSharpOption FSharpDecimalOption { get; set; }
- public FSharpOption FSharpStringOption { get; set; }
- public FSharpOption FSharpDateOption { get; set; }
-
- public string AnotherString { get; set; }
-
- public string[] StringArray { get; set; }
-
- public Guid OtherGuid { get; set; }
-
- public Target Inner { get; set; }
-
- public Colors Color { get; set; }
-
- public Colors? NullableEnum { get; set; }
-
- public bool Flag { get; set; }
-
- [JsonInclude] // this is needed to make System.Text.Json happy
- public string StringField;
-
- public double Double { get; set; }
- public decimal Decimal { get; set; }
- public DateTime Date { get; set; }
- public DateTimeOffset DateOffset { get; set; }
- public DateTimeOffset? NullableDateOffset { get; set; }
-
- [JsonInclude] // this is needed to make System.Text.Json happy
- public float Float;
-
- public int[] NumberArray { get; set; }
-
- public string[] TagsArray { get; set; }
-
- public HashSet TagsHashSet { get; set; }
-
- public Target[] Children { get; set; }
-
- public int? NullableNumber { get; set; }
- public DateTime? NullableDateTime { get; set; }
- public bool? NullableBoolean { get; set; }
- public Colors? NullableColor { get; set; }
-
- public string? NullableString { get; set; }
-
- public IDictionary StringDict { get; set; }
- public Dictionary GuidDict { get; set; }
-
- public Guid UserId { get; set; }
-
- public List StringList { get; set; }
-
- public Guid[] GuidArray { get; set; }
-
- public TimeSpan HowLong { get; set; }
-}
-
-public class Address
-{
- public Address()
- {
- }
-
- public Address(string text)
- {
- var parts = text.ToDelimitedArray();
- Address1 = parts[0];
- City = parts[1];
- StateOrProvince = parts[2];
- }
-
- public string Address1 { get; set; }
- public string Address2 { get; set; }
- public string City { get; set; }
- public string StateOrProvince { get; set; }
- public string Country { get; set; }
- public string PostalCode { get; set; }
-
- public bool Primary { get; set; }
- public string Street { get; set; }
- public string HouseNumber { get; set; }
-}
-
-public class Squad
-{
- public string Id { get; set; }
-}
-
-public class BasketballTeam: Squad
-{
-}
-
-public class FootballTeam: Squad
-{
-}
-
-public class BaseballTeam: Squad
-{
-}
diff --git a/src/Marten.sln b/src/Marten.sln
index 0a0ec93f4c..08c77f71a9 100644
--- a/src/Marten.sln
+++ b/src/Marten.sln
@@ -109,6 +109,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventAppenderPerfTester", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressTests", "StressTests\StressTests.csproj", "{C9D33381-3AD3-4005-B854-F04F10EA837F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinqTestsTypes", "LinqTestsTypes\LinqTestsTypes.csproj", "{0E4B7C9D-07D8-4F3D-8A42-C369B8D6ED84}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -259,6 +261,10 @@ Global
{C9D33381-3AD3-4005-B854-F04F10EA837F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9D33381-3AD3-4005-B854-F04F10EA837F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9D33381-3AD3-4005-B854-F04F10EA837F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0E4B7C9D-07D8-4F3D-8A42-C369B8D6ED84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0E4B7C9D-07D8-4F3D-8A42-C369B8D6ED84}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0E4B7C9D-07D8-4F3D-8A42-C369B8D6ED84}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0E4B7C9D-07D8-4F3D-8A42-C369B8D6ED84}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -297,6 +303,8 @@ Global
{7F2B0CE1-1365-4AE2-9823-B6DBB1A42C5F} = {91D87D73-EC07-4067-8A64-26A2E4F6BC83}
{29E06861-11C7-4917-BA91-162D15538029} = {91D87D73-EC07-4067-8A64-26A2E4F6BC83}
{C9D33381-3AD3-4005-B854-F04F10EA837F} = {91D87D73-EC07-4067-8A64-26A2E4F6BC83}
+ {B1F935FC-55DC-418B-A5DC-6049A5C06871} = {91D87D73-EC07-4067-8A64-26A2E4F6BC83}
+ {0E4B7C9D-07D8-4F3D-8A42-C369B8D6ED84} = {91D87D73-EC07-4067-8A64-26A2E4F6BC83}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {52B7158F-0A24-47D9-9CF7-3FA94170041A}
diff --git a/src/Marten/StoreOptions.Identity.cs b/src/Marten/StoreOptions.Identity.cs
index badafab924..ac55123924 100644
--- a/src/Marten/StoreOptions.Identity.cs
+++ b/src/Marten/StoreOptions.Identity.cs
@@ -165,6 +165,7 @@ public void RegisterFSharpOptionValueTypes()
RegisterValueType(typeof(FSharpOption));
RegisterValueType(typeof(FSharpOption));
RegisterValueType(typeof(FSharpOption));
+ RegisterValueType(typeof(FSharpOption));
}
internal List ValueTypes { get; } = new();