diff --git a/test/EFCore.Cosmos.Tests/Query/CosmosAdvancedTranslationTest.cs b/test/EFCore.Cosmos.Tests/Query/CosmosAdvancedTranslationTest.cs new file mode 100644 index 00000000000..464e66f6019 --- /dev/null +++ b/test/EFCore.Cosmos.Tests/Query/CosmosAdvancedTranslationTest.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Azure.Cosmos; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query; + +public class CosmosAdvancedTranslationTest +{ + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Math_functions_with_client_evaluation(bool async) + { + await using var testDatabase = CosmosTestStore.Create("MathTest"); + var options = new DbContextOptionsBuilder() + .UseCosmos( + testDatabase.ConnectionUri, + testDatabase.AuthToken, + testDatabase.Name) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + try + { + await context.AddRangeAsync( + new NumericEntity { Id = "1", Value = 1.5 }, + new NumericEntity { Id = "2", Value = 2.7 }, + new NumericEntity { Id = "3", Value = 3.2 }); + await context.SaveChangesAsync(); + + // Test Log2 function which requires client evaluation + var query = context.Numbers + .Where(e => Math.Log2(e.Value) > 1.0); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + // Test complex math expressions + var complexQuery = context.Numbers + .Where(e => Math.Round(Math.Pow(e.Value, 2), 2) > 5.0); + + exception = await Assert.ThrowsAsync( + async () => await (async ? complexQuery.ToListAsync() : Task.FromResult(complexQuery.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + } + finally + { + await testDatabase.DisposeAsync(); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task String_operations_with_client_evaluation(bool async) + { + await using var testDatabase = CosmosTestStore.Create("StringTest"); + var options = new DbContextOptionsBuilder() + .UseCosmos( + testDatabase.ConnectionUri, + testDatabase.AuthToken, + testDatabase.Name) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + try + { + await context.AddRangeAsync( + new StringEntity { Id = "1", Text = "Hello" }, + new StringEntity { Id = "2", Text = "World" }); + await context.SaveChangesAsync(); + + // Test string.IsNullOrEmpty + var query = context.Strings + .Where(e => !string.IsNullOrEmpty(e.Text)); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + // Test complex string comparisons + var complexQuery = context.Strings + .Where(e => e.Text.CompareTo("Test") > 0); + + exception = await Assert.ThrowsAsync( + async () => await (async ? complexQuery.ToListAsync() : Task.FromResult(complexQuery.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + } + finally + { + await testDatabase.DisposeAsync(); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Regex_operations_with_different_options(bool async) + { + await using var testDatabase = CosmosTestStore.Create("RegexTest"); + var options = new DbContextOptionsBuilder() + .UseCosmos( + testDatabase.ConnectionUri, + testDatabase.AuthToken, + testDatabase.Name) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + try + { + await context.AddRangeAsync( + new StringEntity { Id = "1", Text = "Test123" }, + new StringEntity { Id = "2", Text = "123Test" }); + await context.SaveChangesAsync(); + + // Test regex with RightToLeft option (not supported) + var query = context.Strings + .Where(e => System.Text.RegularExpressions.Regex.IsMatch( + e.Text, "^Test", System.Text.RegularExpressions.RegexOptions.RightToLeft)); + + await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + // Test regex with combined options + var complexQuery = context.Strings + .Where(e => System.Text.RegularExpressions.Regex.IsMatch( + e.Text, + "^test", + System.Text.RegularExpressions.RegexOptions.IgnoreCase | + System.Text.RegularExpressions.RegexOptions.Multiline)); + + // This should work as these options are supported + var results = async + ? await complexQuery.ToListAsync() + : complexQuery.ToList(); + + Assert.Single(results); + } + finally + { + await testDatabase.DisposeAsync(); + } + } + + private class TestContext : DbContext + { + public TestContext(DbContextOptions options) : base(options) + { + } + + public DbSet Numbers { get; set; } + public DbSet Strings { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasPartitionKey(e => e.Id); + + modelBuilder.Entity() + .HasPartitionKey(e => e.Id); + } + } + + private class NumericEntity + { + public string Id { get; set; } + public double Value { get; set; } + } + + private class StringEntity + { + public string Id { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/test/EFCore.Cosmos.Tests/Query/CosmosCollectionNavigationTest.cs b/test/EFCore.Cosmos.Tests/Query/CosmosCollectionNavigationTest.cs new file mode 100644 index 00000000000..a253da48f91 --- /dev/null +++ b/test/EFCore.Cosmos.Tests/Query/CosmosCollectionNavigationTest.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Azure.Cosmos; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query; + +public class CosmosCollectionNavigationTest +{ + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Collection_navigation_with_skip_take(bool async) + { + await using var testDatabase = CosmosTestStore.Create("CollectionTest"); + var options = new DbContextOptionsBuilder() + .UseCosmos( + testDatabase.ConnectionUri, + testDatabase.AuthToken, + testDatabase.Name) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + try + { + var order = new Order + { + Id = "1", + Items = new List + { + new() { Id = "i1", Quantity = 5 }, + new() { Id = "i2", Quantity = 10 }, + new() { Id = "i3", Quantity = 15 } + } + }; + + await context.Orders.AddAsync(order); + await context.SaveChangesAsync(); + + // Test Skip/Take in subquery + var query = context.Orders + .Select(o => new + { + OrderId = o.Id, + TopItems = o.Items.Skip(1).Take(1) + }); + + // Should throw as Skip/Take not supported in subqueries + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("Limit/Offset not supported in subqueries", exception.Message); + } + finally + { + await testDatabase.DisposeAsync(); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Collection_aggregation_in_projection(bool async) + { + await using var testDatabase = CosmosTestStore.Create("AggregationTest"); + var options = new DbContextOptionsBuilder() + .UseCosmos( + testDatabase.ConnectionUri, + testDatabase.AuthToken, + testDatabase.Name) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + try + { + var orders = new[] + { + new Order + { + Id = "1", + Items = new List + { + new() { Id = "i1", Quantity = 5 }, + new() { Id = "i2", Quantity = 10 } + } + }, + new Order + { + Id = "2", + Items = new List + { + new() { Id = "i3", Quantity = 15 } + } + } + }; + + await context.Orders.AddRangeAsync(orders); + await context.SaveChangesAsync(); + + // Test aggregation in projection + var query = context.Orders + .Select(o => new + { + OrderId = o.Id, + TotalItems = o.Items.Count, + AverageQuantity = o.Items.Average(i => i.Quantity) + }); + + // Should throw as complex aggregations require client evaluation + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + // Test nested aggregation in where clause + var complexQuery = context.Orders + .Where(o => o.Items.Sum(i => i.Quantity) > + context.Orders.Average(x => x.Items.Sum(i => i.Quantity))); + + exception = await Assert.ThrowsAsync( + async () => await (async ? complexQuery.ToListAsync() : Task.FromResult(complexQuery.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + } + finally + { + await testDatabase.DisposeAsync(); + } + } + + private class TestContext : DbContext + { + public TestContext(DbContextOptions options) : base(options) + { + } + + public DbSet Orders { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasPartitionKey(e => e.Id) + .OwnsMany(e => e.Items); + } + } + + private class Order + { + public string Id { get; set; } + public List Items { get; set; } + } + + private class OrderItem + { + public string Id { get; set; } + public int Quantity { get; set; } + } +} \ No newline at end of file diff --git a/test/EFCore.Cosmos.Tests/Query/CosmosCollectionQueryTest.cs b/test/EFCore.Cosmos.Tests/Query/CosmosCollectionQueryTest.cs new file mode 100644 index 00000000000..04adf1e9668 --- /dev/null +++ b/test/EFCore.Cosmos.Tests/Query/CosmosCollectionQueryTest.cs @@ -0,0 +1,203 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Azure.Cosmos; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query; + +public class CosmosCollectionQueryTest : IAsyncLifetime +{ + private CosmosTestStore _testStore; + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; + protected ILoggerFactory ListLoggerFactory { get; } + + public CosmosCollectionQueryTest() + { + ListLoggerFactory = new TestSqlLoggerFactory(); + } + + public async Task InitializeAsync() + { + _testStore = await CosmosTestStore.CreateInitializedAsync("CollectionQueryTest"); + } + + public async Task DisposeAsync() + { + await _testStore.DisposeAsync(); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Collection_aggregation_in_subquery(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + await using var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + var orders = new[] + { + new Order + { + Id = "1", + Items = new List + { + new() { Id = "i1", Quantity = 5 }, + new() { Id = "i2", Quantity = 10 } + } + }, + new Order + { + Id = "2", + Items = new List + { + new() { Id = "i3", Quantity = 15 } + } + } + }; + + await context.Orders.AddRangeAsync(orders); + await context.SaveChangesAsync(); + + // Test aggregation in projection with correlated subquery + var query = context.Orders + .Where(o => o.Items.Average(i => i.Quantity) > + context.Orders.SelectMany(x => x.Items).Average(i => i.Quantity)); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE c +FROM root c +WHERE ((c["Discriminator"] = "Order") AND (AVG(c["Items"].Quantity) > AVG(ARRAY(SELECT VALUE i.Quantity FROM root o CROSS JOIN i IN o.Items)))) +""" + }); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Collection_complex_grouping_with_aggregates(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + await using var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + var orders = new[] + { + new Order + { + Id = "1", + CustomerId = "C1", + Items = new List + { + new() { Id = "i1", ProductId = "P1", Quantity = 5 }, + new() { Id = "i2", ProductId = "P2", Quantity = 10 } + } + }, + new Order + { + Id = "2", + CustomerId = "C2", + Items = new List + { + new() { Id = "i3", ProductId = "P1", Quantity = 15 } + } + } + }; + + await context.Orders.AddRangeAsync(orders); + await context.SaveChangesAsync(); + + // Test grouping by navigation property with multiple aggregates + var query = context.Orders + .SelectMany(o => o.Items) + .GroupBy(i => i.ProductId) + .Select(g => new + { + ProductId = g.Key, + TotalQuantity = g.Sum(i => i.Quantity), + AverageQuantity = g.Average(i => i.Quantity), + OrderCount = g.Count() + }); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE { + "ProductId": g.key, + "TotalQuantity": g.sum, + "AverageQuantity": g.avg, + "OrderCount": g.count +} +FROM ( + SELECT + i["ProductId"] AS key, + SUM(i["Quantity"]) AS sum, + AVG(i["Quantity"]) AS avg, + COUNT(1) AS count + FROM root c + CROSS JOIN i IN c.Items + WHERE (c["Discriminator"] = "Order") + GROUP BY i["ProductId"] +) AS g +""" + }); + } + + private class TestContext : DbContext + { + public TestContext(DbContextOptions options) : base(options) + { + } + + public DbSet Orders { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasPartitionKey(e => e.Id) + .OwnsMany(o => o.Items); + } + } + + private class Order + { + public string Id { get; set; } + public string CustomerId { get; set; } + public List Items { get; set; } + } + + private class OrderItem + { + public string Id { get; set; } + public string ProductId { get; set; } + public int Quantity { get; set; } + } +} \ No newline at end of file diff --git a/test/EFCore.Cosmos.Tests/Query/CosmosQueryTranslationTest.cs b/test/EFCore.Cosmos.Tests/Query/CosmosQueryTranslationTest.cs new file mode 100644 index 00000000000..f85117d52f9 --- /dev/null +++ b/test/EFCore.Cosmos.Tests/Query/CosmosQueryTranslationTest.cs @@ -0,0 +1,170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query; + +public class CosmosQueryTranslationTest : IAsyncLifetime +{ + private CosmosTestStore _testStore; + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; + protected ILoggerFactory ListLoggerFactory { get; } + + public CosmosQueryTranslationTest() + { + ListLoggerFactory = new TestSqlLoggerFactory(); + } + + public async Task InitializeAsync() + { + _testStore = await CosmosTestStore.CreateInitializedAsync("QueryTranslationTest"); + } + + public async Task DisposeAsync() + { + await _testStore.DisposeAsync(); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Subquery_with_client_evaluation(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + await context.AddRangeAsync( + new TestEntity { Id = "1", Value = 10 }, + new TestEntity { Id = "2", Value = 20 }, + new TestEntity { Id = "3", Value = 30 }); + await context.SaveChangesAsync(); + + var query = context.Entities + .Where(e => e.Value > context.Entities.Average(x => x.Value)); + + // Should throw with specific error about client evaluation + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE c +FROM root c +WHERE ((c["Discriminator"] = "TestEntity") AND (c["Value"] > 20)) +""" + }); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Complex_navigation_with_skip_take(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + var parent = new ParentEntity + { + Id = "1", + Children = new List + { + new() { Id = "c1", Value = 100 }, + new() { Id = "c2", Value = 200 }, + new() { Id = "c3", Value = 300 } + } + }; + + await context.Parents.AddAsync(parent); + await context.SaveChangesAsync(); + + // Test Skip/Take in subquery with owned type + var query = context.Parents + .Select(p => new + { + ParentId = p.Id, + TopChildren = p.Children.OrderBy(c => c.Value).Skip(1).Take(1) + }); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("Limit/Offset not supported in subqueries", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE { + "ParentId" : c["id"], + "TopChildren" : ( + SELECT VALUE c + FROM c IN p["Children"] + ORDER BY c["Value"] + OFFSET 1 LIMIT 1 + ) +} +FROM root c +WHERE (c["Discriminator"] = "ParentEntity") +""" + }); + } + + private class TestContext : DbContext + { + public TestContext(DbContextOptions options) : base(options) + { + } + + public DbSet Entities { get; set; } + public DbSet Parents { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasPartitionKey(e => e.Id); + + modelBuilder.Entity() + .HasPartitionKey(e => e.Id) + .OwnsMany(p => p.Children); + } + } + + private class TestEntity + { + public string Id { get; set; } + public int Value { get; set; } + } + + private class ParentEntity + { + public string Id { get; set; } + public List Children { get; set; } + } + + private class ChildEntity + { + public string Id { get; set; } + public int Value { get; set; } + } +} \ No newline at end of file diff --git a/test/EFCore.Cosmos.Tests/Query/CosmosTranslationsTest.cs b/test/EFCore.Cosmos.Tests/Query/CosmosTranslationsTest.cs new file mode 100644 index 00000000000..0daec968297 --- /dev/null +++ b/test/EFCore.Cosmos.Tests/Query/CosmosTranslationsTest.cs @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Azure.Cosmos; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query; + +public class CosmosTranslationsTest : IAsyncLifetime +{ + private CosmosTestStore _testStore; + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; + protected ILoggerFactory ListLoggerFactory { get; } + + public CosmosTranslationsTest() + { + ListLoggerFactory = new TestSqlLoggerFactory(); + } + + public async Task InitializeAsync() + { + _testStore = await CosmosTestStore.CreateInitializedAsync("TranslationTest"); + } + + public async Task DisposeAsync() + { + await _testStore.DisposeAsync(); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task Math_pow_with_round(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + await using var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + await context.AddRangeAsync( + new NumericEntity { Id = "1", Value = 1.5 }, + new NumericEntity { Id = "2", Value = 2.7 }, + new NumericEntity { Id = "3", Value = 3.2 }); + await context.SaveChangesAsync(); + + // Test POW function with ROUND + var query = context.Numbers + .Where(e => Math.Round(Math.Pow(e.Value, 2), 2) > 5.0); + + // Should throw with specific error about client evaluation + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE c +FROM root c +WHERE ((c["Discriminator"] = "NumericEntity") AND (ROUND(POW(c["Value"], 2.0), 2) > 5.0)) +""" + }); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task String_isnullorempty_with_concat(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + await using var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + await context.AddRangeAsync( + new StringEntity { Id = "1", Text = "Hello" }, + new StringEntity { Id = "2", Text = "World" }); + await context.SaveChangesAsync(); + + // Test string.IsNullOrEmpty with string concatenation + var query = context.Strings + .Where(e => !string.IsNullOrEmpty(e.Text + "_suffix")); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE c +FROM root c +WHERE ((c["Discriminator"] = "StringEntity") AND (IS_NULL(CONCAT(c["Text"], "_suffix")) = false)) +""" + }); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public async Task String_comparison_with_ordinal_ignorecase(bool async) + { + var options = new DbContextOptionsBuilder() + .UseCosmos( + _testStore.ConnectionUri, + _testStore.AuthToken, + _testStore.Name) + .UseLoggerFactory(ListLoggerFactory) + .Options; + + await using var context = new TestContext(options); + await context.Database.EnsureCreatedAsync(); + + await context.AddRangeAsync( + new StringEntity { Id = "1", Text = "Test" }, + new StringEntity { Id = "2", Text = "test" }); + await context.SaveChangesAsync(); + + // Test string comparison with StringComparison.OrdinalIgnoreCase + var query = context.Strings + .Where(e => e.Text.Equals("test", StringComparison.OrdinalIgnoreCase)); + + var exception = await Assert.ThrowsAsync( + async () => await (async ? query.ToListAsync() : Task.FromResult(query.ToList()))); + + Assert.Contains("The LINQ expression", exception.Message); + + TestSqlLoggerFactory.AssertBaseline(new[] + { + """ +SELECT VALUE c +FROM root c +WHERE ((c["Discriminator"] = "StringEntity") AND StringEquals(c["Text"], "test", true)) +""" + }); + } + + private class TestContext : DbContext + { + public TestContext(DbContextOptions options) : base(options) + { + } + + public DbSet Numbers { get; set; } + public DbSet Strings { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasPartitionKey(e => e.Id); + + modelBuilder.Entity() + .HasPartitionKey(e => e.Id); + } + } + + private class NumericEntity + { + public string Id { get; set; } + public double Value { get; set; } + } + + private class StringEntity + { + public string Id { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file