Closed j82w closed 9 months ago
You can supply linqSerializerOptions
param and set its PropertyNamingPolicy
to CamelCase
. But since you set the custom serializer you cannot do it globally unfortunately, you would have to supply it with each invocation (that works for me).
With linqSerializerOptions
, projection in LinQ with upper case seems not working for me:
.Select(x => new { A = 55, b = 1.5, id = x.Id })
Note the value of A
and id
PropertyNamingPolicy = CamelCase
default
Hi all, we've merged what we hope will be a solution to this issue by allowing the custom serializer to be used for LINQ translations. This also unblocks having System.Text.Json attributes applied in LINQ queries.
We plan to release this as part of the next preview package, but I also wanted to take this time to confirm these changes meet your needs before finalizing them as part of our public API. If you try the changes out, please follow up in the thread if this update addresses your concerns. If it doesn't, please let us know why.
To use your own custom serializer for LINQ translations you must implement CosmosLinqSerializer
and set it as a custom serializer on the CosmosClient
. There is a sample of this for serialization with System.Text.Json in CosmosLinqSerializer.cs.
@Maya-Painter, that is good news!
When is this preview package available on NuGet?
If we register a custom serializer in CosmosClientOptions will this also be implemented by the CosmosLinqSerializer?
For example if we do it like this:
Custom serializator class:
public sealed class CosmosSystemTextJsonSerializer : CosmosSerializer, CosmosLinqSerializer
{
private readonly JsonObjectSerializer _systemTextJsonSerializer;
public CosmosSystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
{
_systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
}
public override T FromStream<T>(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
using (stream)
{
if (stream.CanSeek && stream.Length == 0)
{
return default;
}
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)stream;
}
return (T)_systemTextJsonSerializer.Deserialize(stream, typeof(T), default);
}
}
public override Stream ToStream<T>(T input)
{
var streamPayload = new MemoryStream();
_systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default);
streamPayload.Position = 0;
return streamPayload;
}
}
and then register it like so for example:
CosmosClientOptions cosmosClientOptions = new()
{
...
Serializer = new CosmosSystemTextJsonSerializer(new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters =
{
new JsonStringEnumConverter()
}
})
};
@machie27 CosmosLinqSerializer is inaccessible due to its protection level, its internal class and can't be inherited. Can u pls mention if there is any solution to this?
Hi @v-mghabin,
Sorry I've mentioned this in another request as well and got a response there from @Maya-Painter. This isn't the way it should be implemented indeed. Link to which I'm referring to: link
please note that in this example I also convert the membernames by setting the policy naming explicitly. See my question here as well: link
I've an example here:
public class CosmosSystemTextJsonSerializer : CosmosLinqSerializer
{
private readonly JsonObjectSerializer _systemTextJsonSerializer;
public CosmosSystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions)
{
_systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions);
}
public override T FromStream<T>(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
using (stream)
{
if (stream.CanSeek && stream.Length == 0)
{
return default;
}
if (typeof(Stream).IsAssignableFrom(typeof(T)))
{
return (T)(object)stream;
}
return (T)_systemTextJsonSerializer.Deserialize(stream, typeof(T), default);
}
}
public override string SerializeMemberName(MemberInfo memberInfo)
{
var jsonPropertyNameAttribute = memberInfo.GetCustomAttribute<JsonPropertyNameAttribute>(true);
string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name)
? jsonPropertyNameAttribute.Name
: memberInfo.Name;
return JsonNamingPolicy.CamelCase.ConvertName(memberName);
}
public override Stream ToStream<T>(T input)
{
var streamPayload = new MemoryStream();
_systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default);
streamPayload.Position = 0;
return streamPayload;
}
}
If you follow this sample and use the System.Text.Json as the default serializer LINQ will not translate the attribute so the incorrect query will be generated. This will cause the query to return no or incorrect results.
Follow the sample listed here: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs
Update the class like the sample:
Try to do a LINQ query:
The query will execute but it will return no results because it will be translated as 'PoolId' instead of the expected 'poolId'.
It seems the following code needs to be updated to handle the new System.Text.Json types: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/cee34ce3cf41dabb2a073f64a289edb2e922177a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs#L30