Azure-cosmos-dotnet-v3: LINQ use's default serializer for parameters

Created on 23 Jul 2019  路  4Comments  路  Source: Azure/azure-cosmos-dotnet-v3

The LINQ query generation should use the user specified serializer for the parameters when generating the query. This will ensure that any custom conversion are honored for the parameters. This was fixed for query text via PR 576

The other option is for LINQ to support parameterized quiries, and then the parameter handling can be done by the existing query logic.

LINQ bug

Most helpful comment

Just ran into this too.

Evil.

All 4 comments

Just ran (if I understood the issue correct) into the same problem. Here is an example, that illustrates my case. It uses a custom date time converter to write just year, month and day into the JSON. If you try to make a Where() clause on that field you'll get nothing, cause the default serializer is used instead of the specified custom one:

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace ConsoleApp
{
    public static class Program
    {
        private const string EndPoint = "https://<domain>.documents.azure.com:443/";
        private const string AuthKey = "<key>";

        public async static Task Main()
        {
            var options = new CosmosClientOptions
            {
                SerializerOptions = new CosmosSerializationOptions
                {
                    PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
                }
            };

            var random = new Random();
            var today = DateTime.UtcNow.Date;

            using (var client = new CosmosClient(EndPoint, AuthKey, options))
            {
                var databaseResponse = await client.CreateDatabaseIfNotExistsAsync("MyContainer");
                var database = databaseResponse.Database;
                var containerResponse = await database.CreateContainerIfNotExistsAsync(
                    new ContainerProperties
                    {
                        Id = "MyStatistics",
                        PartitionKeyPath = "/dayOfEntry"
                    });
                var container = containerResponse.Container;
                var scores = Enumerable.Range(1, 10)
                    .Select(_ => new Player { Timestamp = today.AddDays(random.NextDouble()) });

                foreach (var entry in scores)
                {
                    var _ = await container.CreateItemAsync(entry);
                }

                var query = container
                    .GetItemLinqQueryable<Player>(allowSynchronousQueryExecution: true)
                    .Where(entry => entry.DayOfEntry == today);

                // Expected Query:
                // {{"query":"SELECT VALUE root FROM root WHERE (root[\"dayOfEntry\"] = \"2019-12-10\") "}}
                // Current Query:
                // {{"query":"SELECT VALUE root FROM root WHERE (root[\"dayOfEntry\"] = \"2019-12-10T01:00:00+01:00\") "}}
                var iterator = query.ToFeedIterator();

                while (iterator.HasMoreResults)
                {
                    var response = await iterator.ReadNextAsync();

                    Console.WriteLine($"Found {response.Count} items for {today:yyyy-MM-dd}.");

                    foreach (var item in response)
                    {
                        Console.WriteLine($"   {item.Timestamp}");
                    }
                }
            }
        }
    }

    public class CustomDateTimeConverter : IsoDateTimeConverter
    {
        public CustomDateTimeConverter(string dateTimeFormat)
        {
            DateTimeFormat = dateTimeFormat;
        }
    }

    public class Player
    {
        private const string DayOfEntryFormat = "yyyy-MM-dd";

        public Guid Id { get; set; } = Guid.NewGuid();
        [JsonConverter(typeof(CustomDateTimeConverter), DayOfEntryFormat)]
        public DateTime DayOfEntry => Timestamp.Date;
        public DateTime Timestamp { get; set; }
    }
}

My current workaround would be to never use a custom serializer or add a note, that this field can't be used in LINQ queries as a filter. Hope this can and will be fixed in a future release.

Just ran into this too.

Evil.

A hacky workaround...

Example: We use a converter to serialize long <--> string...

q.Where(x => x.SomeLongValue == 4) fails to produce the correct SQL

q.Where(x => (string)(object)x.SomeLongValue == "4") will work.

It requires knowledge of exactly which fields will use a converter, and knowledge of how exactly the converter will work (ie 4 becomes "4"). But it works.

This was an issue in previous versions of the sdk as well. We are in the process of migrating to V3 and were hoping that the improved support for custom serializers would make our usage of Noda Time work in linq queries.

Was this page helpful?
0 / 5 - 0 ratings