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
47 changes: 47 additions & 0 deletions src/LinqTests/Bugs/Bug_4282_is_one_of_against_string_list.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Marten;
using Marten.Testing.Harness;
using Shouldly;

namespace LinqTests.Bugs;

public class Bug_4282_is_one_of_against_string_list: OneOffConfigurationsContext
{
[Fact]
public async Task can_query_string_list_with_is_one_of_against_runtime_list()
{
var doc1 = new Issue4282Target { RelatedIds = ["related-1", "related-2"] };
var doc2 = new Issue4282Target { RelatedIds = ["related-3"] };
var doc3 = new Issue4282Target { RelatedIds = ["related-4", "related-5"] };

using var session = theStore.LightweightSession();
session.Store(doc1, doc2, doc3);
await session.SaveChangesAsync();

IList<string> relatedIds = ["related-2", "related-4", "unknown-related"];

var ids = await session.Query<Issue4282Target>()
.Where(x => x.RelatedIds.IsOneOf(relatedIds))
.OrderBy(x => x.Id)
.Select(x => x.Id)
.ToListAsync();

ids.ShouldHaveTheSameElementsAs(doc1.Id, doc3.Id);

var notIds = await session.Query<Issue4282Target>()
.Where(x => !x.RelatedIds.IsOneOf(relatedIds))
.Select(x => x.Id)
.ToListAsync();

notIds.ShouldHaveTheSameElementsAs(doc2.Id);
}

public class Issue4282Target
{
public Guid Id { get; set; }
public List<string> RelatedIds { get; set; } = [];
}
}
59 changes: 59 additions & 0 deletions src/Marten/Linq/Parsing/Methods/CollectionIsOneOfFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#nullable enable
using System;
using System.Collections;
using System.Linq;
using Marten.Linq.Members;
using NpgsqlTypes;
using Weasel.Postgresql;
using Weasel.Postgresql.SqlGeneration;

namespace Marten.Linq.Parsing.Methods;

internal class CollectionIsOneOfFilter: ISqlFragment
{
private readonly ICollectionMember _collectionMember;
private readonly object _values;

public CollectionIsOneOfFilter(ICollectionMember collectionMember, object values)
{
_collectionMember = collectionMember;
_values = normalizeValues(values, collectionMember.ElementType);
}

public void Apply(ICommandBuilder builder)
{
builder.Append(_collectionMember.ArrayLocator);
builder.Append(" && ");
builder.AppendParameter(_values, dbTypeFor(_collectionMember.ElementType));
}

private static object normalizeValues(object values, Type elementType)
{
if (values is Array { Length: 1 } array && array.GetType().GetElementType() != elementType)
{
values = array.GetValue(0)!;
}

if (values is not string && values is IEnumerable enumerable)
{
var raw = enumerable.Cast<object>().ToArray();
var typed = Array.CreateInstance(elementType, raw.Length);

for (var i = 0; i < raw.Length; i++)
{
typed.SetValue(raw[i], i);
}

return typed;
}

return values;
}

private static NpgsqlDbType dbTypeFor(Type elementType)
{
return NpgsqlDbType.Array | (elementType == typeof(string)
? NpgsqlDbType.Varchar
: PostgresqlProvider.Instance.ToParameterType(elementType));
}
}
6 changes: 5 additions & 1 deletion src/Marten/Linq/Parsing/Methods/IsOneOf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public ISqlFragment Parse(IQueryableMemberCollection memberCollection, IReadOnly
var locator = queryableMember.TypedLocator;
var values = expression.Arguments[1].ReduceToConstant().Value!;

if (queryableMember is ICollectionMember collectionMember)
{
return new CollectionIsOneOfFilter(collectionMember, values);
}

if (queryableMember.MemberType.IsEnum)
{
return new EnumIsOneOfWhereFragment(values, options.Serializer().EnumStorage, locator);
Expand All @@ -44,7 +49,6 @@ public ISqlFragment Parse(IQueryableMemberCollection memberCollection, IReadOnly

return new IsOneOfFilter(queryableMember, new CommandParameter(values));
}

}

internal class IsOneOfFilter: ISqlFragment
Expand Down
Loading