Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for IsNullOrEmpty and IsNullOrWhiteSpace string operators in Linq queries #2823

Merged
merged 2 commits into from
Nov 28, 2023
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
21 changes: 18 additions & 3 deletions docs/diagnostics.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public interface IMartenSessionLogger
public void OnBeforeExecute(NpgsqlCommand command); public void OnBeforeExecute(NpgsqlCommand command);
} }
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/Marten/IMartenLogger.cs#L10-L66' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_imartenlogger' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/Marten/IMartenLogger.cs#L11-L67' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_imartenlogger' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


To apply these logging abstractions, you can either plug your own `IMartenLogger` into the `StoreOptions` object and allow that default logger to create the individual session loggers: To apply these logging abstractions, you can either plug your own `IMartenLogger` into the `StoreOptions` object and allow that default logger to create the individual session loggers:
Expand Down Expand Up @@ -314,7 +314,22 @@ public class ConsoleMartenLogger: IMartenLogger, IMartenSessionLogger
{ {
Console.WriteLine(command.CommandText); Console.WriteLine(command.CommandText);
foreach (var p in command.Parameters.OfType<NpgsqlParameter>()) foreach (var p in command.Parameters.OfType<NpgsqlParameter>())
Console.WriteLine($" {p.ParameterName}: {p.Value}"); Console.WriteLine($" {p.ParameterName}: {GetParameterValue(p)}");
}

private static object? GetParameterValue(NpgsqlParameter p)
{
if (p.Value is IList enumerable)
{
var result = "";
for (var i = 0; i < Math.Min(enumerable.Count, 5); i++)
{
result += $"[{i}] {enumerable[i]}; ";
}
if (enumerable.Count > 5) result += $" + {enumerable.Count - 5} more";
return result;
}
return p.Value;
} }


public void LogFailure(NpgsqlCommand command, Exception ex) public void LogFailure(NpgsqlCommand command, Exception ex)
Expand Down Expand Up @@ -344,7 +359,7 @@ public class ConsoleMartenLogger: IMartenLogger, IMartenSessionLogger
} }
} }
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/Marten/IMartenLogger.cs#L68-L120' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_consolemartenlogger' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/Marten/IMartenLogger.cs#L69-L136' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_consolemartenlogger' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


## Accessing Diagnostics ## Accessing Diagnostics
Expand Down
8 changes: 4 additions & 4 deletions docs/documents/querying/linq/child-collections.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var results = theSession
.Where(x => x.Children.Any(_ => _.Number == 6 && _.Double == -1)) .Where(x => x.Children.Any(_ => _.Number == 6 && _.Double == -1))
.ToArray(); .ToArray();
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L95-L100' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_any-query-through-child-collection-with-and' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L130-L137' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_any-query-through-child-collection-with-and' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


Finally, you can query for child collections that do **not** contain a value: Finally, you can query for child collections that do **not** contain a value:
Expand Down Expand Up @@ -112,7 +112,7 @@ public void query_against_string_array()
.Select(x => x.Id).ShouldHaveTheSameElementsAs(doc1.Id, doc2.Id); .Select(x => x.Id).ShouldHaveTheSameElementsAs(doc1.Id, doc2.Id);
} }
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L424-L441' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_against_string_array' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L457-L475' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_against_string_array' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


Marten also allows you to query over IEnumerables using the Any method for equality (similar to Contains): Marten also allows you to query over IEnumerables using the Any method for equality (similar to Contains):
Expand Down Expand Up @@ -142,7 +142,7 @@ public void query_against_number_list_with_any()
.Count(x => x.Numbers.Any()).ShouldBe(3); .Count(x => x.Numbers.Any()).ShouldBe(3);
} }
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L530-L553' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_any_string_array' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L573-L597' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_any_string_array' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


As of 1.2, you can also query against the `Count()` or `Length` of a child collection with the normal comparison As of 1.2, you can also query against the `Count()` or `Length` of a child collection with the normal comparison
Expand Down Expand Up @@ -170,7 +170,7 @@ public void query_against_number_list_with_count_method()
.Single(x => x.Numbers.Count() == 4).Id.ShouldBe(doc3.Id); .Single(x => x.Numbers.Count() == 4).Id.ShouldBe(doc3.Id);
} }
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L555-L575' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_against_number_list_with_count_method' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/ChildCollections/query_against_child_collections.cs#L599-L620' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_query_against_number_list_with_count_method' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


## IsOneOf ## IsOneOf
Expand Down
2 changes: 1 addition & 1 deletion docs/documents/querying/linq/strings.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ A shorthand for case-insensitive string matching is provided through `EqualsIgno
query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("abc")).Id.ShouldBe(user1.Id); query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("abc")).Id.ShouldBe(user1.Id);
query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("aBc")).Id.ShouldBe(user1.Id); query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("aBc")).Id.ShouldBe(user1.Id);
``` ```
<sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/Acceptance/string_filtering.cs#L89-L92' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_sample-linq-equalsignorecase' title='Start of snippet'>anchor</a></sup> <sup><a href='https://github.com/JasperFx/marten/blob/master/src/LinqTests/Acceptance/string_filtering.cs#L222-L227' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_sample-linq-equalsignorecase' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet --> <!-- endSnippet -->


This defaults to `String.Equals` with `StringComparison.CurrentCultureIgnoreCase` as comparison type. This defaults to `String.Equals` with `StringComparison.CurrentCultureIgnoreCase` as comparison type.
156 changes: 147 additions & 9 deletions src/LinqTests/Acceptance/string_filtering.cs
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,14 +12,27 @@ public class string_filtering: IntegrationContext
{ {
protected override Task fixtureSetup() protected override Task fixtureSetup()
{ {
var entry = new User() { FirstName = "Beeblebrox" }; var entry = new User { FirstName = "Beeblebrox", Nickname = "" };
var entry2 = new User() { FirstName = "Bee" }; var entry2 = new User { FirstName = "Bee", Nickname = " " };
var entry3 = new User() { FirstName = "Zaphod" }; var entry3 = new User { FirstName = "Zaphod", Nickname = "Zaph" };
var entry4 = new User() { FirstName = "Zap" }; var entry4 = new User { FirstName = "Zap", Nickname = null };


return theStore.BulkInsertAsync(new[] { entry, entry2, entry3, entry4 }); return theStore.BulkInsertAsync(new[] { entry, entry2, entry3, entry4 });
} }


[Theory]
[InlineData("zap", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("Zap", StringComparison.CurrentCulture, 1)]
[InlineData("zap", StringComparison.CurrentCulture, 0)]
public void CanQueryByEquals(string search, StringComparison comparison, int expectedCount)
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => x.FirstName.Equals(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => x.FirstName.Equals(search, comparison)));
}

[Theory] [Theory]
[InlineData("zap", StringComparison.OrdinalIgnoreCase, 3)] [InlineData("zap", StringComparison.OrdinalIgnoreCase, 3)]
[InlineData("Zap", StringComparison.CurrentCulture, 3)] [InlineData("Zap", StringComparison.CurrentCulture, 3)]
Expand All @@ -28,7 +41,21 @@ public void CanQueryByNotEquals(string search, StringComparison comparison, int
{ {
using var s = theStore.QuerySession(); using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !x.FirstName.Equals(search, comparison)).ToList(); var fromDb = s.Query<User>().Where(x => !x.FirstName.Equals(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => !x.FirstName.Equals(search, comparison)));
}

[Theory]
[InlineData("zap", StringComparison.OrdinalIgnoreCase, 2)]
[InlineData("zap", StringComparison.CurrentCulture, 0)]
public void CanQueryByContains(string search, StringComparison comparison, int expectedCount)
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => x.FirstName.Contains(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count); Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => x.FirstName.Contains(search, comparison)));
} }


[Theory] [Theory]
Expand All @@ -38,7 +65,21 @@ public void CanQueryByNotContains(string search, StringComparison comparison, in
{ {
using var s = theStore.QuerySession(); using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !x.FirstName.Contains(search, comparison)).ToList(); var fromDb = s.Query<User>().Where(x => !x.FirstName.Contains(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count); Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => !x.FirstName.Contains(search, comparison)));
}

[Theory]
[InlineData("zap", StringComparison.OrdinalIgnoreCase, 2)]
[InlineData("zap", StringComparison.CurrentCulture, 0)]
public void CanQueryByStartsWith(string search, StringComparison comparison, int expectedCount)
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => x.FirstName.StartsWith(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => x.FirstName.StartsWith(search, comparison)));
} }


[Theory] [Theory]
Expand All @@ -48,7 +89,22 @@ public void CanQueryByNotStartsWith(string search, StringComparison comparison,
{ {
using var s = theStore.QuerySession(); using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !x.FirstName.StartsWith(search, comparison)).ToList(); var fromDb = s.Query<User>().Where(x => !x.FirstName.StartsWith(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count); Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => !x.FirstName.StartsWith(search, comparison)));
}

[Theory]
[InlineData("hod", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("HOD", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("Hod", StringComparison.CurrentCulture, 0)]
public void CanQueryByEndsWith(string search, StringComparison comparison, int expectedCount)
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => x.FirstName.EndsWith(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => x.FirstName.EndsWith(search, comparison)));
} }


[Theory] [Theory]
Expand All @@ -59,17 +115,94 @@ public void CanQueryByNotEndsWith(string search, StringComparison comparison, in
{ {
using var s = theStore.QuerySession(); using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !x.FirstName.EndsWith(search, comparison)).ToList(); var fromDb = s.Query<User>().Where(x => !x.FirstName.EndsWith(search, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count); Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x => !x.FirstName.EndsWith(search, comparison)));
}

[Fact]
public void CanQueryByIsNullOrEmpty()
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => string.IsNullOrEmpty(x.Nickname)).ToList();

Assert.Equal(2, fromDb.Count);
Assert.True(fromDb.All(x => string.IsNullOrEmpty(x.Nickname)));
}

[Fact]
public void CanQueryByNotIsNullOrEmpty()
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !string.IsNullOrEmpty(x.Nickname)).ToList();

Assert.Equal(2, fromDb.Count);
Assert.True(fromDb.All(x => !string.IsNullOrEmpty(x.Nickname)));
}

[Fact]
public void CanQueryByIsNullOrWhiteSpace()
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => string.IsNullOrWhiteSpace(x.Nickname)).ToList();

Assert.Equal(3, fromDb.Count);
Assert.True(fromDb.All(x => string.IsNullOrWhiteSpace(x.Nickname)));
}

[Fact]
public void CanQueryByNotIsNullOrWhiteSpace()
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !string.IsNullOrWhiteSpace(x.Nickname)).ToList();

Assert.Single(fromDb);
Assert.True(fromDb.All(x => !string.IsNullOrWhiteSpace(x.Nickname)));
} }


[Theory] [Theory]
[InlineData("zap", "hod", StringComparison.OrdinalIgnoreCase, 1)] [InlineData("zap", "hod", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("zap", "hod", StringComparison.CurrentCulture, 0)] [InlineData("zap", "hod", StringComparison.CurrentCulture, 0)]
public void CanMixContainsAndNotContains(string contains, string notContains, StringComparison comparison, int expectedCount) public void CanMixContainsAndNotContains(string contains, string notContains, StringComparison comparison,
int expectedCount)
{ {
using var s = theStore.QuerySession(); using var s = theStore.QuerySession();
var fromDb = s.Query<User>().Where(x => !x.FirstName.Contains(notContains, comparison) && x.FirstName.Contains(contains, comparison)).ToList(); var fromDb = s.Query<User>().Where(x =>
!x.FirstName.Contains(notContains, comparison) && x.FirstName.Contains(contains, comparison)).ToList();

Assert.Equal(expectedCount, fromDb.Count);
Assert.True(fromDb.All(x =>
!x.FirstName.Contains(notContains, comparison) && x.FirstName.Contains(contains, comparison)));
}

[Theory]
[InlineData("hod", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("HOD", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("Hod", StringComparison.CurrentCulture, 2)]
public void CanMixNotEndsWithWithNotIsNullOrEmpty(string search, StringComparison comparison,
int expectedCount)
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>()
.Where(x => !x.FirstName.EndsWith(search, comparison) && !string.IsNullOrEmpty(x.Nickname)).ToList();

Assert.Equal(expectedCount, fromDb.Count); Assert.Equal(expectedCount, fromDb.Count);
Assert.True(
fromDb.All(x => !x.FirstName.EndsWith(search, comparison) && !string.IsNullOrEmpty(x.Nickname)));
}

[Theory]
[InlineData("zap", StringComparison.OrdinalIgnoreCase, 1)]
[InlineData("zap", StringComparison.CurrentCulture, 0)]
public void CanMixStartsWithAndIsNullOrWhiteSpace(string search, StringComparison comparison, int expectedCount)
{
using var s = theStore.QuerySession();
var fromDb = s.Query<User>()
.Where(x => x.FirstName.StartsWith(search, comparison) && string.IsNullOrWhiteSpace(x.Nickname)).ToList();

Assert.Equal(expectedCount, fromDb.Count);
Assert.True(
fromDb.All(x => x.FirstName.StartsWith(search, comparison) && string.IsNullOrWhiteSpace(x.Nickname)));
} }


[Fact] [Fact]
Expand All @@ -87,9 +220,12 @@ public void can_search_case_insensitive()
using (var query = theStore.QuerySession()) using (var query = theStore.QuerySession())
{ {
#region sample_sample-linq-EqualsIgnoreCase #region sample_sample-linq-EqualsIgnoreCase

query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("abc")).Id.ShouldBe(user1.Id); query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("abc")).Id.ShouldBe(user1.Id);
query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("aBc")).Id.ShouldBe(user1.Id); query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("aBc")).Id.ShouldBe(user1.Id);

#endregion #endregion

query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("def")).Id.ShouldBe(user2.Id); query.Query<User>().Single(x => x.UserName.EqualsIgnoreCase("def")).Id.ShouldBe(user2.Id);


query.Query<User>().Any(x => x.UserName.EqualsIgnoreCase("abcd")).ShouldBeFalse(); query.Query<User>().Any(x => x.UserName.EqualsIgnoreCase("abcd")).ShouldBeFalse();
Expand All @@ -109,7 +245,8 @@ public void can_search_case_insensitive_with_StringComparison()


using (var query = theStore.QuerySession()) using (var query = theStore.QuerySession())
{ {
query.Query<User>().Single(x => x.UserName.Equals("test_user", StringComparison.InvariantCultureIgnoreCase)).Id.ShouldBe(user.Id); query.Query<User>().Single(x => x.UserName.Equals("test_user", StringComparison.InvariantCultureIgnoreCase))
.Id.ShouldBe(user.Id);
} }
} }


Expand All @@ -126,12 +263,13 @@ public void can_search_string_with_back_slash_case_insensitive_with_StringCompar


using (var query = theStore.QuerySession()) using (var query = theStore.QuerySession())
{ {
query.Query<User>().Single(x => x.UserName.Equals(@"domain\test_user", StringComparison.InvariantCultureIgnoreCase)).Id.ShouldBe(user.Id); query.Query<User>()
.Single(x => x.UserName.Equals(@"domain\test_user", StringComparison.InvariantCultureIgnoreCase)).Id
.ShouldBe(user.Id);
} }
} }





public string_filtering(DefaultStoreFixture fixture): base(fixture) public string_filtering(DefaultStoreFixture fixture): base(fixture)
{ {
} }
Expand Down
Loading
Loading