From 009468555dd1b9455049a2cc6df3b606366c769a Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 18 Oct 2023 15:13:23 -0700 Subject: [PATCH 01/48] initial commit --- .../src/Linq/ExpressionToSQL.cs | 89 ++++++++++++++++--- .../src/Linq/SQLTranslator.cs | 1 - Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs | 27 +++--- .../Resource/Container/ContainerCore.Items.cs | 7 +- .../Serializer/CosmosLinqSerializerOptions.cs | 9 ++ .../src/Serializer/CosmosSerializationUtil.cs | 2 +- ...ionBaselineTest.ValidateSQLTranslation.xml | 2 +- ...erBaseline.TestMemberInitializerDotNet.xml | 78 ++++++++-------- ...e.TestMemberInitializerMultiSerializer.xml | 6 +- ...seline.TestMemberInitializerNewtonsoft.xml | 6 +- .../Linq/LinqTestsCommon.cs | 7 +- ...TranslationWithCustomSerializerBaseline.cs | 17 +++- .../Linq/CosmosLinqJsonConverterTests.cs | 61 +++++++------ 13 files changed, 210 insertions(+), 102 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index fef136d8d2..7f825341d7 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -10,15 +10,19 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; + using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.CosmosElements.Numbers; using Microsoft.Azure.Cosmos.Spatial; using Microsoft.Azure.Cosmos.SqlObjects; using Microsoft.Azure.Documents; using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; using static Microsoft.Azure.Cosmos.Linq.FromParameterBindings; // ReSharper disable UnusedParameter.Local @@ -169,7 +173,7 @@ private static Collection TranslateInput(ConstantExpression inputExpression, Tra } /// - /// Get a paramter name to be binded to the a collection from the next lambda. + /// Get a parameter name to be binded to the a collection from the next lambda. /// It's merely for readability purpose. If that is not possible, use a default /// parameter name. /// @@ -189,7 +193,7 @@ private static string GetBindingParameterName(TranslationContext context) } } - if (parameterName == null) parameterName = ExpressionToSql.DefaultParameterName; + parameterName ??= ExpressionToSql.DefaultParameterName; return parameterName; } @@ -524,10 +528,16 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit // so we check both attributes and apply the same precedence rules // JsonConverterAttribute doesn't allow duplicates so it's safe to // use FirstOrDefault() - CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(JsonConverterAttribute)).FirstOrDefault(); - CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().Where(ca => ca.AttributeType == typeof(JsonConverterAttribute)).FirstOrDefault(); + CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); + CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute; + if (converterAttribute == null) + { + CustomAttributeData memberAttributeDotNet = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)).FirstOrDefault(); + converterAttribute = memberAttributeDotNet; + } + if (converterAttribute != null) { Debug.Assert(converterAttribute.ConstructorArguments.Count > 0); @@ -538,14 +548,21 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit // Enum if (memberType.IsEnum()) { - Number64 number64 = ((SqlNumberLiteral)right.Literal).Value; - if (number64.IsDouble) + try { - value = Enum.ToObject(memberType, Number64.ToDouble(number64)); + Number64 number64 = ((SqlNumberLiteral)right.Literal).Value; + if (number64.IsDouble) + { + value = Enum.ToObject(memberType, Number64.ToDouble(number64)); + } + else + { + value = Enum.ToObject(memberType, Number64.ToLong(number64)); + } } - else + catch { - value = Enum.ToObject(memberType, Number64.ToLong(number64)); + value = ((SqlStringLiteral)right.Literal).Value; } } @@ -562,7 +579,18 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit if (converterType.GetConstructor(Type.EmptyTypes) != null) { - serializedValue = JsonConvert.SerializeObject(value, (JsonConverter)Activator.CreateInstance(converterType)); + if (converterAttribute.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)) + { + // Handle dotnet + JsonSerializerOptions options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + serializedValue = System.Text.Json.JsonSerializer.Serialize(value, options); + } + else + { + // handle newtonsoft + serializedValue = JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType)); + } } else { @@ -770,6 +798,47 @@ public static SqlScalarExpression VisitConstant(ConstantExpression inputExpressi return SqlArrayCreateScalarExpression.Create(arrayItems.ToImmutableArray()); } + // DataAttribute serialization + if (inputExpression.Type.CustomAttributes != null && inputExpression.Type.CustomAttributes.Count() > 0) + { + return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); + } + + // if ANY property is serialized with newtonsoft serializer, we serialize newtonsoft properties + PropertyInfo[] propInfo = inputExpression.Value.GetType().GetProperties(); + bool hasCustomAttributesNewtonsoft = propInfo.Any(p => p.GetCustomAttributes().Any(a => a.GetType() == typeof(JsonPropertyAttribute))); + + if (hasCustomAttributesNewtonsoft) + { + if (context.linqSerializerOptions != null && context.linqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + { + JsonSerializerSettings serializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value, serializerSettings)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); + } + + return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); + } + + if (context.linqSerializerOptions?.CustomCosmosSerializer != null) + { + StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); + + // Use the user serializer for the parameter values so custom conversions are correctly handled + using (Stream stream = context.linqSerializerOptions.CustomCosmosSerializer.ToStream(inputExpression.Value)) + { + using (StreamReader streamReader = new StreamReader(stream)) + { + string propertyValue = streamReader.ReadToEnd(); + writer.Write(propertyValue); + return CosmosElement.Parse(writer.ToString()).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); + } + } + } + return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); } diff --git a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs index 8648019219..c0bd6d38a0 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.Azure.Cosmos.Query.Core; - using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.SqlObjects; /// diff --git a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs index 7c8e62d69c..4171be0812 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; - using Microsoft.Azure.Cosmos.Serializer; + using System.Text.Json.Serialization; using Microsoft.Azure.Documents; using Newtonsoft.Json; @@ -27,7 +27,7 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali string memberName = null; // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. - JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); + Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) { return null; @@ -42,21 +42,26 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali } else { - DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); - if (dataContractAttribute != null) + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + if (jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name)) { - DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute(true); - if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name)) + memberName = jsonPropertyNameAttribute.Name; + } + else + { + DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); + if (dataContractAttribute != null) { - memberName = dataMemberAttribute.Name; + DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute(true); + if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name)) + { + memberName = dataMemberAttribute.Name; + } } } } - if (memberName == null) - { - memberName = memberInfo.Name; - } + memberName ??= memberInfo.Name; if (linqSerializerOptions != null) { diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index fd9ad03b53..1a17f43f02 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -500,11 +500,14 @@ public override IOrderedQueryable GetItemLinqQueryable( { requestOptions ??= new QueryRequestOptions(); - if (linqSerializerOptions == null && this.ClientContext.ClientOptions.SerializerOptions != null) + if (linqSerializerOptions == null && this.ClientContext.ClientOptions != null) { linqSerializerOptions = new CosmosLinqSerializerOptions { - PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions.PropertyNamingPolicy + PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions != null + ? this.ClientContext.ClientOptions.SerializerOptions.PropertyNamingPolicy + : CosmosPropertyNamingPolicy.Default, + CustomCosmosSerializer = this.ClientContext.ClientOptions.Serializer }; } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index 3b215a9cc8..b5dd9f3a12 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -18,6 +18,15 @@ public CosmosLinqSerializerOptions() this.PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default; } + /// + /// Gets or sets the user defined customer serializer. If no customer serializer was defined, + /// then the value is set to the default value + /// + /// + /// The default value is null + /// + internal CosmosSerializer CustomCosmosSerializer { get; set; } + /// /// Gets or sets whether the naming policy used to convert a string-based name to another format, /// such as a camel-casing format. diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index b2e0838248..26024c2f3a 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -4,7 +4,6 @@ namespace Microsoft.Azure.Cosmos { - using Microsoft.Azure.Cosmos.Serializer; using Newtonsoft.Json.Serialization; internal static class CosmosSerializationUtil @@ -18,6 +17,7 @@ internal static string ToCamelCase(string name) internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOptions options, string name) { + //mayapainter: not being applied on top of dotnet? if (options != null && options.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { return CosmosSerializationUtil.ToCamelCase(name); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml index d22afb37f9..4366c080a5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml @@ -278,7 +278,7 @@ FROM root]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml index b381e2b651..0b02c7897f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml @@ -8,7 +8,7 @@ +WHERE (root["numberValueDotNet"] = 1)]]> "{\"numberValueDotNet\": 1, \"stringValueDotNet\": \"1\", \"id\": \"1-False\", \"Pk\": \"Test\"}", "{\"numberValueDotNet\": 2, \"stringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> - + @@ -29,7 +32,7 @@ WHERE (root["numericField"] = 1)]]> +WHERE (root = {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null})]]> "{\"numberValueDotNet\": 2, \"stringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> @@ -75,7 +78,7 @@ FROM root]]> 1) ? {"NumericField": 1, "StringField": "1", "id": null, "Pk": null} : {"NumericField": 1, "StringField": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numberValueDotNet"] > 1) ? {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null} : {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> "{\"numberValueDotNet\": 2, \"stringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> @@ -104,7 +107,7 @@ FROM root]]> +WHERE (root = {"numberValueDotNet": root["numberValueDotNet"], "stringValueDotNet": root["stringValueDotNet"]})]]> +WHERE (root["numberValueDotNet"] = 1)]]> "{\"numberValueDotNet\": 1, \"stringValueDotNet\": \"1\", \"id\": \"1-False\", \"Pk\": \"Test\"}", "{\"numberValueDotNet\": 2, \"stringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> - + @@ -146,7 +152,7 @@ WHERE (root["NumericField"] = 1)]]> +WHERE (root = {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null})]]> "{\"numberValueDotNet\": 2, \"stringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> @@ -192,7 +198,7 @@ FROM root]]> 1) ? {"NumericField": 1, "StringField": "1", "id": null, "Pk": null} : {"NumericField": 1, "StringField": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numberValueDotNet"] > 1) ? {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null} : {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> "{\"numberValueDotNet\": 2, \"stringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> @@ -221,7 +227,7 @@ FROM root]]> +WHERE (root = {"numberValueDotNet": root["numberValueDotNet"], "stringValueDotNet": root["stringValueDotNet"]})]]> +WHERE (root = {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null})]]> 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) FROM root]]> +WHERE (root = {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null})]]> 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) FROM root]]> > GenerateTestCosmosData(Funcnumber of test data to be created /// the target container /// if theCosmosLinqSerializerOption of camelCaseSerialization should be applied - /// a lambda that takes a boolean which indicate where the query should run against CosmosDB or against original data, and return a query results as IQueryable. Also the serialized payload. - public static Func> GenerateSerializationTestCosmosData(Func func, int count, Container container, bool camelCaseSerialization = false) + /// a lambda that takes a boolean which indicate where the query should run against CosmosDB or against original data, and return a query results as IQueryable. + public static Func> GenerateSerializationTestCosmosData(Func func, int count, Container container, CosmosLinqSerializerOptions linqSerializerOptions) { List data = new List(); for (int i = 0; i < count; i++) { - data.Add(func(i, camelCaseSerialization)); + data.Add(func(i, linqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase)); } foreach (T obj in data) @@ -355,7 +355,6 @@ public static Func> GenerateSerializationTestCosmosData(F #endif }; - CosmosLinqSerializerOptions linqSerializerOptions = new CosmosLinqSerializerOptions { PropertyNamingPolicy = camelCaseSerialization ? CosmosPropertyNamingPolicy.CamelCase : CosmosPropertyNamingPolicy.Default }; IOrderedQueryable query = container.GetItemLinqQueryable(allowSynchronousQueryExecution: true, requestOptions: requestOptions, linqSerializerOptions: linqSerializerOptions); IQueryable getQuery(bool useQuery) => useQuery ? query : data.AsQueryable(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 3266809926..f040db13f5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -205,8 +205,19 @@ static T createDataObj(int index, bool camelCase) return obj; } - Func> getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, camelCaseSerialization: true); - Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, camelCaseSerialization: false); + CosmosLinqSerializerOptions linqSerializerOptionsCamelCase = new CosmosLinqSerializerOptions + { + PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, + CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()) + }; + + CosmosLinqSerializerOptions linqSerializerOptionsDefault = new CosmosLinqSerializerOptions + { + CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()) + }; + + Func> getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); + Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsDefault); return (getQueryCamelCase, getQueryDefault); } @@ -248,7 +259,7 @@ private async Task GetInsertedData() return insertedData; } - private class SystemTextJsonSerializer : CosmosSerializer + class SystemTextJsonSerializer : CosmosSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 8e50a21dd0..cba8e44b49 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -37,63 +37,65 @@ public void DateTimeKindIsPreservedTest() [TestMethod] public void EnumIsPreservedAsINTest() { - // Arrange CosmosLinqSerializerOptions options = new() { - //CustomCosmosSerializer = new TestCustomJsonSerializer() + CustomCosmosSerializer = new TestCustomJsonSerializer() }; - // Act TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; - Expression> expr = a => values.Contains(a.Value); - + + Expression> expr = a => values.Contains(a.Value); string sql = SqlTranslator.TranslateExpression(expr.Body, options); - // Assert - // Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); // <- TODO - Desired Behavior with CustomSerializer - Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sql); // <- Actual behavior, with ability to set custom serializor reverted + Expression> exprNewtonsoft = a => values.Contains(a.Value); + string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, options); + + Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); + Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sqlNewtonsoft); } [TestMethod] public void EnumIsPreservedAsEQUALSTest() { - // Arrange CosmosLinqSerializerOptions options = new() { - // CustomCosmosSerializer = new TestCustomJsonSerializer() + CustomCosmosSerializer = new TestCustomJsonSerializer() }; - // Act TestEnum statusValue = TestEnum.One; - Expression> expr = a => a.Value == statusValue; + Expression> expr = a => a.Value == statusValue; string sql = SqlTranslator.TranslateExpression(expr.Body, options); - // Assert - // Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); // <- THIS is the correct value, if we are able to use the custom serializer - Assert.AreEqual("(a[\"Value\"] = 0)", sql); // <- THIS is the current mis-behavior of the SDK + Expression> exprNewtonsoft = a => a.Value == statusValue; + string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, options); + + Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); } [TestMethod] public void EnumIsPreservedAsEXPRESSIONTest() { - // Arrange CosmosLinqSerializerOptions options = new() { - // CustomCosmosSerializer = new TestCustomJsonSerializer() + CustomCosmosSerializer = new TestCustomJsonSerializer() }; - // Act - // Get status constant ConstantExpression status = Expression.Constant(TestEnum.One); // Get member access expression - ParameterExpression arg = Expression.Parameter(typeof(TestEnumNewtonsoftDocument), "a"); + ParameterExpression arg = Expression.Parameter(typeof(TestEnumDocument), "a"); + ParameterExpression argNewtonsoft = Expression.Parameter(typeof(TestEnumNewtonsoftDocument), "a"); // Access the value property MemberExpression docValueExpression = Expression.MakeMemberAccess( arg, + typeof(TestEnumDocument).GetProperty(nameof(TestEnumDocument.Value))! + ); + MemberExpression docValueExpressionNewtonsoft = Expression.MakeMemberAccess( + argNewtonsoft, typeof(TestEnumNewtonsoftDocument).GetProperty(nameof(TestEnumNewtonsoftDocument.Value))! ); @@ -102,15 +104,22 @@ public void EnumIsPreservedAsEXPRESSIONTest() docValueExpression, status ); + BinaryExpression expressionNewtonsoft = Expression.Equal( + docValueExpressionNewtonsoft, + status + ); // Create lambda expression - Expression> lambda = - Expression.Lambda>(expression, arg); - + Expression> lambda = + Expression.Lambda>(expression, arg); string sql = SqlTranslator.TranslateExpression(lambda.Body, options); - // Assert + Expression> lambdaNewtonsoft = + Expression.Lambda>(expressionNewtonsoft, argNewtonsoft); + string sqlNewtonsoft = SqlTranslator.TranslateExpression(lambdaNewtonsoft.Body, options); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); } enum TestEnum @@ -122,7 +131,7 @@ enum TestEnum class TestEnumDocument { - [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] // TODO: Remove this once we have the ability to use custom serializer for LINQ queries + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] public TestEnum Value { get; set; } } @@ -168,8 +177,6 @@ public void TestSystemTextJsonExtensionDataQuery() Expression> expr = a => ((object)a.NetExtensionData["foo"]) == "bar"; string sql = SqlTranslator.TranslateExpression(expr.Body); - // TODO: This is a limitation in the translator. It should be able to handle STJ extension data, if a custom - // JSON serializer is specified. Assert.AreEqual("(a[\"NetExtensionData\"][\"foo\"] = \"bar\")", sql); } From 5ab7959d676f8126363ad4695f8a87f73aeb2f67 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 18 Oct 2023 15:55:45 -0700 Subject: [PATCH 02/48] add more tests --- .../src/Linq/SQLTranslator.cs | 1 + ...tializerNewtonsoftDataMemberSerializer.xml | 236 ++++++++++++++++++ ...rInitializerNewtonsoftDotNetSerializer.xml | 236 ++++++++++++++++++ ...TranslationWithCustomSerializerBaseline.cs | 89 +++++-- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 10 +- 5 files changed, 550 insertions(+), 22 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml diff --git a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs index c0bd6d38a0..8648019219 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.SqlObjects; /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml new file mode 100644 index 0000000000..9ec9db18bb --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml @@ -0,0 +1,236 @@ + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) +FROM root]]> + + + + + + + + (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) +FROM root]]> + + + + + + + + (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml new file mode 100644 index 0000000000..0ac09894ad --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml @@ -0,0 +1,236 @@ + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) +FROM root]]> + + + + + + + + (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) +FROM root]]> + + + + + + + + (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index f040db13f5..08a1a3baae 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -90,8 +90,6 @@ public void TestMemberInitializerDotNet() new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - - // Negative test case: serializing only field name using custom serializer not currently supported new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; @@ -121,8 +119,6 @@ public void TestMemberInitializerNewtonsoft() new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoft() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - - // Negative test case: serializing only field name using custom serializer not currently supported new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoft() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; @@ -152,8 +148,6 @@ public void TestMemberInitializerDataMember() new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDataMember() { NumericField = 1, StringField = "1" } : new DataObjectDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - - // Negative test case: serializing only field name using custom serializer not currently supported new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDataMember() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; @@ -164,28 +158,55 @@ public void TestMemberInitializerDataMember() } [TestMethod] - public void TestMemberInitializerMultiSerializer() + public void TestMemberInitializerNewtonsoftDotNetSerializer() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); string insertedData = this.GetInsertedData().Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; List camelCaseSettingInputs = new List { new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" } : new DataObjectMultiSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNetSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + }; + + inputs.AddRange(camelCaseSettingInputs); + } + + this.ExecuteTestSuite(inputs); + } + + [TestMethod] + public void TestMemberInitializerNewtonsoftDataMemberSerializer() + { + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); - // Negative test case: serializing only field name using custom serializer not currently supported - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectMultiSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + string insertedData = this.GetInsertedData().Result; + + List inputs = new List(); + foreach (bool useCamelCaseSerializer in new bool[] { true, false }) + { + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + + List camelCaseSettingInputs = new List + { + new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMemberSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; inputs.AddRange(camelCaseSettingInputs); @@ -385,7 +406,7 @@ public override string ToString() } } - private class DataObjectMultiSerializer : LinqTestObject + private class DataObjectNewtonsoftDotNetSerializer : LinqTestObject { [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] [JsonPropertyName("numberValueDotNet")] @@ -399,9 +420,39 @@ private class DataObjectMultiSerializer : LinqTestObject public string Pk { get; set; } - public DataObjectMultiSerializer() { } + public DataObjectNewtonsoftDotNetSerializer() { } + + public DataObjectNewtonsoftDotNetSerializer(double numericField, string stringField, string id, string pk) + { + this.NumericField = numericField; + this.StringField = stringField; + this.id = id; + this.Pk = pk; + } + + public override string ToString() + { + return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; + } + } + + private class DataObjectNewtonsoftDataMemberSerializer : LinqTestObject + { + [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] + [DataMember(Name = "NumericFieldDataMember")] + public double NumericField { get; set; } + + [Newtonsoft.Json.JsonProperty(PropertyName = "StringValueNewtonsoft")] + [DataMember(Name = "StringFieldDataMember")] + public string StringField { get; set; } + + public string id { get; set; } + + public string Pk { get; set; } + + public DataObjectNewtonsoftDataMemberSerializer() { } - public DataObjectMultiSerializer(double numericField, string stringField, string id, string pk) + public DataObjectNewtonsoftDataMemberSerializer(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 7ff30b3cee..07b4d62771 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -40,8 +40,9 @@ - + + @@ -263,10 +264,13 @@ PreserveNewest - + PreserveNewest - + + PreserveNewest + + PreserveNewest From 260a6fc72c9dddc662cdae8d714cd8e7dbc8b24e Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 18 Oct 2023 17:12:08 -0700 Subject: [PATCH 03/48] cleanup --- .../src/Linq/ExpressionToSQL.cs | 28 ++++++++----------- Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs | 5 ++-- .../src/Serializer/CosmosSerializationUtil.cs | 1 - 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 7f825341d7..32d05def0e 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -173,7 +173,7 @@ private static Collection TranslateInput(ConstantExpression inputExpression, Tra } /// - /// Get a parameter name to be binded to the a collection from the next lambda. + /// Get a parameter name to be binded to the collection from the next lambda. /// It's merely for readability purpose. If that is not possible, use a default /// parameter name. /// @@ -528,15 +528,9 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit // so we check both attributes and apply the same precedence rules // JsonConverterAttribute doesn't allow duplicates so it's safe to // use FirstOrDefault() - CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); - CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); - - CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute; - if (converterAttribute == null) - { - CustomAttributeData memberAttributeDotNet = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)).FirstOrDefault(); - converterAttribute = memberAttributeDotNet; - } + CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); + converterAttribute ??= memberType.GetsCustomAttributes().Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); + converterAttribute ??= memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)).FirstOrDefault(); if (converterAttribute != null) { @@ -578,17 +572,17 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit string serializedValue; if (converterType.GetConstructor(Type.EmptyTypes) != null) - { + { if (converterAttribute.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)) { - // Handle dotnet + // DotNet serializer JsonSerializerOptions options = new JsonSerializerOptions(); options.Converters.Add(new JsonStringEnumConverter()); serializedValue = System.Text.Json.JsonSerializer.Serialize(value, options); } else { - // handle newtonsoft + // Newtonsoft serializer serializedValue = JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType)); } } @@ -798,15 +792,15 @@ public static SqlScalarExpression VisitConstant(ConstantExpression inputExpressi return SqlArrayCreateScalarExpression.Create(arrayItems.ToImmutableArray()); } - // DataAttribute serialization + // DataMember serialization if (inputExpression.Type.CustomAttributes != null && inputExpression.Type.CustomAttributes.Count() > 0) { return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); } - // if ANY property is serialized with newtonsoft serializer, we serialize newtonsoft properties + // if ANY property is serialized with Newtonsoft serializer, we serialize all with this serializer PropertyInfo[] propInfo = inputExpression.Value.GetType().GetProperties(); - bool hasCustomAttributesNewtonsoft = propInfo.Any(p => p.GetCustomAttributes().Any(a => a.GetType() == typeof(JsonPropertyAttribute))); + bool hasCustomAttributesNewtonsoft = propInfo.Any(property => property.GetCustomAttributes().Any(attribute => attribute.GetType() == typeof(JsonPropertyAttribute))); if (hasCustomAttributesNewtonsoft) { @@ -823,11 +817,11 @@ public static SqlScalarExpression VisitConstant(ConstantExpression inputExpressi return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); } + // Use DotNet custom serializer if provided if (context.linqSerializerOptions?.CustomCosmosSerializer != null) { StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); - // Use the user serializer for the parameter values so custom conversions are correctly handled using (Stream stream = context.linqSerializerOptions.CustomCosmosSerializer.ToStream(inputExpression.Value)) { using (StreamReader streamReader = new StreamReader(stream)) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs index 4171be0812..ee942c494d 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs @@ -33,8 +33,8 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali return null; } - // Json.Net honors JsonPropertyAttribute more than DataMemberAttribute - // So we check for JsonPropertyAttribute first. + // TODO check this + // Precedence is (highest to lowest) : JsonPropertyAttribute, JsonPropertyNameAttribute, DataMemberAttribute JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); if (jsonPropertyAttribute != null && !string.IsNullOrEmpty(jsonPropertyAttribute.PropertyName)) { @@ -63,6 +63,7 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali memberName ??= memberInfo.Name; + // Apply camel casing if specified if (linqSerializerOptions != null) { memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(linqSerializerOptions, memberName); diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index 26024c2f3a..f1ae43eb3a 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -17,7 +17,6 @@ internal static string ToCamelCase(string name) internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOptions options, string name) { - //mayapainter: not being applied on top of dotnet? if (options != null && options.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { return CosmosSerializationUtil.ToCamelCase(name); From 2583c8e3d5134dad780e3404c24dbe60ad8eb49e Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 18 Oct 2023 18:56:45 -0700 Subject: [PATCH 04/48] camel case fix --- .../src/Linq/ExpressionToSQL.cs | 18 +- .../src/Serializer/CosmosSerializationUtil.cs | 7 +- ...seline.TestMemberInitializerDataMember.xml | 6 +- ...e.TestMemberInitializerMultiSerializer.xml | 236 ------------------ ...tializerNewtonsoftDataMemberSerializer.xml | 12 +- ...TranslationWithCustomSerializerBaseline.cs | 1 + 6 files changed, 16 insertions(+), 264 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerMultiSerializer.xml diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 32d05def0e..d32be2d8b4 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -792,29 +792,21 @@ public static SqlScalarExpression VisitConstant(ConstantExpression inputExpressi return SqlArrayCreateScalarExpression.Create(arrayItems.ToImmutableArray()); } - // DataMember serialization - if (inputExpression.Type.CustomAttributes != null && inputExpression.Type.CustomAttributes.Count() > 0) - { - return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); - } + bool hasDataMemberSerialization = inputExpression.Type.CustomAttributes != null && inputExpression.Type.CustomAttributes.Count() > 0; // if ANY property is serialized with Newtonsoft serializer, we serialize all with this serializer PropertyInfo[] propInfo = inputExpression.Value.GetType().GetProperties(); bool hasCustomAttributesNewtonsoft = propInfo.Any(property => property.GetCustomAttributes().Any(attribute => attribute.GetType() == typeof(JsonPropertyAttribute))); - if (hasCustomAttributesNewtonsoft) + if (hasDataMemberSerialization || hasCustomAttributesNewtonsoft) { + JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); if (context.linqSerializerOptions != null && context.linqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { - JsonSerializerSettings serializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - - return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value, serializerSettings)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); + serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); } - return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); + return CosmosElement.Parse(JsonConvert.SerializeObject(inputExpression.Value, serializerSettings)).Accept(CosmosElementToSqlScalarExpressionVisitor.Singleton); } // Use DotNet custom serializer if provided diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index f1ae43eb3a..171ce53f64 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -10,16 +10,11 @@ internal static class CosmosSerializationUtil { private static readonly CamelCaseNamingStrategy camelCaseNamingStrategy = new CamelCaseNamingStrategy(); - internal static string ToCamelCase(string name) - { - return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); - } - internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOptions options, string name) { if (options != null && options.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { - return CosmosSerializationUtil.ToCamelCase(name); + return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); } return name; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml index 6734a125fa..d53c9f35a0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml @@ -29,7 +29,7 @@ WHERE (root["numericFieldDataMember"] = 1)]]> +WHERE (root = {"numericFieldDataMember": 1, "stringFieldDataMember": "1", "id": null, "pk": null})]]> 1) ? {"NumericFieldDataMember": 1, "StringFieldDataMember": "1", "id": null, "Pk": null} : {"NumericFieldDataMember": 1, "StringFieldDataMember": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numericFieldDataMember"] > 1) ? {"numericFieldDataMember": 1, "stringFieldDataMember": "1", "id": null, "pk": null} : {"numericFieldDataMember": 1, "stringFieldDataMember": "1", "id": null, "pk": null}) FROM root]]> - - - - (doc.NumericField == 1))]]> - - - - - - - - - - - (doc == new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> - - - - - - - - - - - new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"})]]> - - - - - - - - - - - IIF((doc.NumericField > 1), new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}, new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> - - - 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) -FROM root]]> - - - - - - - - (doc == new DataObjectMultiSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - - - - - - - - - - - (doc.NumericField == 1))]]> - - - - - - - - - - - (doc == new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> - - - - - - - - - - - new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"})]]> - - - - - - - - - - - IIF((doc.NumericField > 1), new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}, new DataObjectMultiSerializer() {NumericField = 1, StringField = "1"}))]]> - - - 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) -FROM root]]> - - - - - - - - (doc == new DataObjectMultiSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml index 9ec9db18bb..65d646136f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml @@ -29,7 +29,7 @@ WHERE (root["numberValueNewtonsoft"] = 1)]]> +WHERE (root = {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1"})]]> 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) +SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1"} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1"}) FROM root]]> +WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"})]]> 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["NumberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"}) FROM root]]> Date: Wed, 18 Oct 2023 19:20:21 -0700 Subject: [PATCH 05/48] fix precidence and add test --- Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs | 22 +- ...rInitializerDotNetDataMemberSerializer.xml | 236 ++++++++++++++++++ ...TranslationWithCustomSerializerBaseline.cs | 60 +++++ ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 4 + 4 files changed, 311 insertions(+), 11 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml diff --git a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs index ee942c494d..d1303996b0 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs @@ -34,7 +34,7 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali } // TODO check this - // Precedence is (highest to lowest) : JsonPropertyAttribute, JsonPropertyNameAttribute, DataMemberAttribute + // Precedence is (highest to lowest) : JsonPropertyAttribute, DataMemberAttribute, JsonPropertyNameAttribute JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); if (jsonPropertyAttribute != null && !string.IsNullOrEmpty(jsonPropertyAttribute.PropertyName)) { @@ -42,21 +42,21 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali } else { - JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); - if (jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name)) + DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); + if (dataContractAttribute != null) { - memberName = jsonPropertyNameAttribute.Name; + DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute(true); + if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name)) + { + memberName = dataMemberAttribute.Name; + } } else { - DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); - if (dataContractAttribute != null) + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + if (jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name)) { - DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute(true); - if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name)) - { - memberName = dataMemberAttribute.Name; - } + memberName = jsonPropertyNameAttribute.Name; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml new file mode 100644 index 0000000000..8423e42b46 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml @@ -0,0 +1,236 @@ + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"numericFieldDataMember": 1, "stringFieldDataMember": "1"} : {"numericFieldDataMember": 1, "stringFieldDataMember": "1"}) +FROM root]]> + + + + + + + + (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + + + + (doc.NumericField == 1))]]> + + + + + + + + + + + (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + + + + + + + + + new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + + + + + + + + + + + IIF((doc.NumericField > 1), new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + + + 1) ? {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"} : {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"}) +FROM root]]> + + + + + + + + (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 2a7bf6c070..6c012498fb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -215,6 +215,35 @@ public void TestMemberInitializerNewtonsoftDataMemberSerializer() this.ExecuteTestSuite(inputs); } + [TestMethod] + public void TestMemberInitializerDotNetDataMemberSerializer() + { + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + + string insertedData = this.GetInsertedData().Result; + + List inputs = new List(); + foreach (bool useCamelCaseSerializer in new bool[] { true, false }) + { + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + + List camelCaseSettingInputs = new List + { + new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" } : new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMemberSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + }; + + inputs.AddRange(camelCaseSettingInputs); + } + + this.ExecuteTestSuite(inputs); + } + private (Func>, Func>) InsertDataAndGetQueryables() where T : LinqTestObject { static T createDataObj(int index, bool camelCase) @@ -466,5 +495,36 @@ public override string ToString() return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; } } + + [DataContract] + private class DataObjectDotNetDataMemberSerializer : LinqTestObject + { + [DataMember(Name = "NumericFieldDataMember")] + [JsonPropertyName("numberValueDotNet")] + public double NumericField { get; set; } + + [DataMember(Name = "StringFieldDataMember")] + [JsonPropertyName("stringValueDotNet")] + public string StringField { get; set; } + + public string id { get; set; } + + public string Pk { get; set; } + + public DataObjectDotNetDataMemberSerializer() { } + + public DataObjectDotNetDataMemberSerializer(double numericField, string stringField, string id, string pk) + { + this.NumericField = numericField; + this.StringField = stringField; + this.id = id; + this.Pk = pk; + } + + public override string ToString() + { + return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; + } + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 07b4d62771..5c50aa155e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -43,6 +43,7 @@ + @@ -273,6 +274,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest From 6ebafd21806db3799d9eb4529269d21aa07c4bd2 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 18 Oct 2023 19:31:14 -0700 Subject: [PATCH 06/48] cleanup --- Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs | 8 ++++---- Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index d32be2d8b4..2099186459 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -792,13 +792,13 @@ public static SqlScalarExpression VisitConstant(ConstantExpression inputExpressi return SqlArrayCreateScalarExpression.Create(arrayItems.ToImmutableArray()); } - bool hasDataMemberSerialization = inputExpression.Type.CustomAttributes != null && inputExpression.Type.CustomAttributes.Count() > 0; - - // if ANY property is serialized with Newtonsoft serializer, we serialize all with this serializer + // if ANY property is decorated with Newtonsoft JsonPropertyAttribute, we serialize all with this serializer PropertyInfo[] propInfo = inputExpression.Value.GetType().GetProperties(); bool hasCustomAttributesNewtonsoft = propInfo.Any(property => property.GetCustomAttributes().Any(attribute => attribute.GetType() == typeof(JsonPropertyAttribute))); - if (hasDataMemberSerialization || hasCustomAttributesNewtonsoft) + bool hasCustomAttributesDataMember = inputExpression.Type.CustomAttributes != null && inputExpression.Type.CustomAttributes.Count() > 0; + + if (hasCustomAttributesNewtonsoft || hasCustomAttributesDataMember ) { JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); if (context.linqSerializerOptions != null && context.linqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs index d1303996b0..1bac505c2f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs @@ -33,7 +33,6 @@ public static string GetMemberName(this MemberInfo memberInfo, CosmosLinqSeriali return null; } - // TODO check this // Precedence is (highest to lowest) : JsonPropertyAttribute, DataMemberAttribute, JsonPropertyNameAttribute JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); if (jsonPropertyAttribute != null && !string.IsNullOrEmpty(jsonPropertyAttribute.PropertyName)) From 61082f5d53830831c4bb60e2cb2c479137317202 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Thu, 26 Oct 2023 12:45:43 -0700 Subject: [PATCH 07/48] cleanup --- Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 2099186459..b702ec20df 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -528,9 +528,9 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit // so we check both attributes and apply the same precedence rules // JsonConverterAttribute doesn't allow duplicates so it's safe to // use FirstOrDefault() - CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); - converterAttribute ??= memberType.GetsCustomAttributes().Where(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)).FirstOrDefault(); - converterAttribute ??= memberExpression.Member.CustomAttributes.Where(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)).FirstOrDefault(); + CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + converterAttribute ??= memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + converterAttribute ??= memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)); if (converterAttribute != null) { From 3ad6110a10e6cc012486910ca6ab2295e3ed72f0 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 8 Nov 2023 14:31:58 -0800 Subject: [PATCH 08/48] Implement serializers --- .../Linq/DataContractCosmosLinqSerializer.cs | 93 +++++++++++++++++++ .../src/Linq/DefaultCosmosLinqSerializer.cs | 15 ++- .../src/Linq/DotNetCosmosLinqSerializer.cs | 75 +++++++++++++++ .../src/Linq/ExpressionToSQL.cs | 5 - .../src/Linq/ICosmosLinqSerializer.cs | 2 +- .../src/Linq/LinqSerializerType.cs | 31 +++++++ .../Linq/NewtonsoftCosmosLinqSerializer.cs | 90 ++++++++++++++++++ .../src/Linq/TranslationContext.cs | 12 ++- Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs | 2 +- .../Serializer/CosmosLinqSerializerOptions.cs | 27 ++++-- .../LinqAttributeContractBaselineTests.cs | 2 +- ...TranslationWithCustomSerializerBaseline.cs | 21 +++-- .../Linq/CosmosLinqJsonConverterTests.cs | 49 ++++++++-- 13 files changed, 385 insertions(+), 39 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs create mode 100644 Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs create mode 100644 Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs create mode 100644 Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs new file mode 100644 index 0000000000..de5601cf57 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs @@ -0,0 +1,93 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Runtime.Serialization; + using Microsoft.Azure.Documents; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + internal class DataContractCosmosLinqSerializer : ICosmosLinqSerializer + { + private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + + public DataContractCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + { + this.LinqSerializerOptions = linqSerializerOptions; + } + + public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) + { + CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + return converterAttribute != null; + } + + public string Serialize(object value, MemberExpression memberExpression, Type memberType) + { + CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute; + + Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "At least one constructor argument exists."); + Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value; + + string serializedValue = converterType.GetConstructor(Type.EmptyTypes) != null + ? JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType)) + : JsonConvert.SerializeObject(value); + + return serializedValue; + } + + public string SerializeScalarExpression(ConstantExpression inputExpression) + { + if (this.LinqSerializerOptions != null && this.LinqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + { + JsonSerializerSettings serializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + return JsonConvert.SerializeObject(inputExpression.Value, serializerSettings); + } + + return JsonConvert.SerializeObject(inputExpression.Value); + } + + public string SerializeMemberName(MemberInfo memberInfo) + { + string memberName = null; + + // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. + Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); + if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) + { + return null; + } + + DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); + if (dataContractAttribute != null) + { + DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute(true); + if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name)) + { + memberName = dataMemberAttribute.Name; + } + } + + memberName ??= memberInfo.Name; + + if (this.LinqSerializerOptions != null) + { + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); + } + + return memberName; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs index 024c33fab0..e11873e0ac 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs @@ -14,6 +14,13 @@ namespace Microsoft.Azure.Cosmos.Linq internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializer { + private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + + public DefaultCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + { + this.LinqSerializerOptions = linqSerializerOptions; + } + public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) { // There are two ways to specify a custom attribute @@ -63,7 +70,7 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) return JsonConvert.SerializeObject(inputExpression.Value); } - public string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOptions linqSerializerOptions = null) + public string SerializeMemberName(MemberInfo memberInfo) { string memberName = null; @@ -71,7 +78,7 @@ public string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOpt Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) { - return null; + return String.Empty; // todo check this } // Json.Net honors JsonPropertyAttribute more than DataMemberAttribute @@ -99,9 +106,9 @@ public string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOpt memberName = memberInfo.Name; } - if (linqSerializerOptions != null) + if (this.LinqSerializerOptions != null) { - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(linqSerializerOptions, memberName); + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); } return memberName; diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs new file mode 100644 index 0000000000..ee1f2e3d43 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -0,0 +1,75 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; + using Newtonsoft.Json; + + internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer + { + private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + + public DotNetCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + { + this.LinqSerializerOptions = linqSerializerOptions; + } + + public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) + { + CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)); + return converterAttribute != null; + } + + public string Serialize(object value, MemberExpression memberExpression, Type memberType) + { + //check this + JsonSerializerOptions options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + return System.Text.Json.JsonSerializer.Serialize(value, options); + } + + public string SerializeScalarExpression(ConstantExpression inputExpression) + { + if (this.LinqSerializerOptions.CustomCosmosSerializer != null) + { + StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); + + using (Stream stream = this.LinqSerializerOptions.CustomCosmosSerializer.ToStream(inputExpression.Value)) + { + using (StreamReader streamReader = new StreamReader(stream)) + { + string propertyValue = streamReader.ReadToEnd(); + writer.Write(propertyValue); + return writer.ToString(); + } + } + } + + return JsonConvert.SerializeObject(inputExpression.Value); //todo: fix this, throw error if custom serializer is null? + } + + public string SerializeMemberName(MemberInfo memberInfo) + { + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + + string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name) + ? jsonPropertyNameAttribute.Name + : memberInfo.Name; + + if (this.LinqSerializerOptions != null) + { + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); + } + + return memberName; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 49f235637a..172e6e2acb 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -10,18 +10,13 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; - using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; - using System.Text.Json; - using System.Text.Json.Serialization; using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.Spatial; using Microsoft.Azure.Cosmos.SqlObjects; using Microsoft.Azure.Documents; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; using static Microsoft.Azure.Cosmos.Linq.FromParameterBindings; // ReSharper disable UnusedParameter.Local diff --git a/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs index f31490832d..c4265cb990 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs @@ -28,6 +28,6 @@ internal interface ICosmosLinqSerializer /// /// Serializes a member name with LINQ serializer options applied. /// - string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOptions linqSerializerOptions = null); + string SerializeMemberName(MemberInfo memberInfo); } } diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs new file mode 100644 index 0000000000..a8aafc02ca --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -0,0 +1,31 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Linq +{ + /// + /// Serializer to be used for LINQ query translations + /// + public enum LinqSerializerType + { + /// + /// TODO + /// + Default, + + /// + /// TODO + /// + Newtonsoft, + + /// + /// TODO + /// + DataContract, + + /// + /// TODO + /// + DotNet, + } +} diff --git a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs new file mode 100644 index 0000000000..9dc1c435ea --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Linq +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Microsoft.Azure.Documents; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + internal class NewtonsoftCosmosLinqSerializer : ICosmosLinqSerializer + { + private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + + public NewtonsoftCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + { + this.LinqSerializerOptions = linqSerializerOptions; + } + + public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) + { + CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + + return memberAttribute != null || typeAttribute != null; + } + + public string Serialize(object value, MemberExpression memberExpression, Type memberType) + { + CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); + CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute; + + Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "At least one constructor argument exists."); + Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value; + + string serializedValue = converterType.GetConstructor(Type.EmptyTypes) != null + ? JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType)) + : JsonConvert.SerializeObject(value); + + return serializedValue; + } + + public string SerializeScalarExpression(ConstantExpression inputExpression) + { + if (this.LinqSerializerOptions != null && this.LinqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + { + JsonSerializerSettings serializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + return JsonConvert.SerializeObject(inputExpression.Value, serializerSettings); + } + + return JsonConvert.SerializeObject(inputExpression.Value); + } + + public string SerializeMemberName(MemberInfo memberInfo) + { + string memberName = null; + + // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. + Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); + if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) + { + return String.Empty; + } + + JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); + if (jsonPropertyAttribute != null && !string.IsNullOrEmpty(jsonPropertyAttribute.PropertyName)) + { + memberName = jsonPropertyAttribute.PropertyName; + } + + memberName ??= memberInfo.Name; + + if (this.LinqSerializerOptions != null) + { + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); + } + + return memberName; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index f5d53bd1e7..aaa013b5b6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -84,7 +84,17 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.LinqSerializerOptions = linqSerializerOptions; this.Parameters = parameters; this.MemberNames = new MemberNames(linqSerializerOptions); - this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(); + + this.CosmosLinqSerializer = linqSerializerOptions != null + ? linqSerializerOptions.LinqSerializerType switch + { + LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions), + LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions), + LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions), + LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions), + _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") // todo check this + } + : new DefaultCosmosLinqSerializer(linqSerializerOptions); } public Expression LookupSubstitution(ParameterExpression parameter) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs index e11c145c71..dbe6809017 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs @@ -21,7 +21,7 @@ public static Type GetElementType(Type type) public static string GetMemberName(this MemberInfo memberInfo, TranslationContext context) { - return context.CosmosLinqSerializer.SerializeMemberName(memberInfo, context.LinqSerializerOptions); + return context.CosmosLinqSerializer.SerializeMemberName(memberInfo); } private static Type GetElementType(Type type, HashSet visitedSet) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index b5dd9f3a12..a041bedeec 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -4,6 +4,8 @@ namespace Microsoft.Azure.Cosmos { + using Microsoft.Azure.Cosmos.Linq; + /// /// This class provides a way to configure Linq Serialization Properties /// @@ -16,24 +18,33 @@ public sealed class CosmosLinqSerializerOptions public CosmosLinqSerializerOptions() { this.PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default; + this.LinqSerializerType = LinqSerializerType.Default; } /// - /// Gets or sets the user defined customer serializer. If no customer serializer was defined, - /// then the value is set to the default value + /// Gets or sets whether the naming policy used to convert a string-based name to another format, + /// such as a camel-casing format. /// /// - /// The default value is null + /// The default value is CosmosPropertyNamingPolicy.Default /// - internal CosmosSerializer CustomCosmosSerializer { get; set; } + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } /// - /// Gets or sets whether the naming policy used to convert a string-based name to another format, - /// such as a camel-casing format. + /// TODO /// /// - /// The default value is CosmosPropertyNamingPolicy.Default + /// The default value is LinqSerializerType.Default /// - public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + public LinqSerializerType LinqSerializerType { get; set; } + + /// + /// Gets or sets the user defined customer serializer. If no customer serializer was defined, + /// then the value is set to the default value + /// + /// + /// The default value is null + /// + internal CosmosSerializer CustomCosmosSerializer { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs index 5ea03bf0b6..b252907925 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs @@ -172,7 +172,7 @@ public Datum2(string jsonProperty, string dataMember, string defaultMember, stri [TestMethod] public void TestAttributePriority() { - ICosmosLinqSerializer cosmosLinqSerializer = new DefaultCosmosLinqSerializer(); + ICosmosLinqSerializer cosmosLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosLinqSerializerOptions()); Assert.AreEqual("jsonProperty", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("JsonProperty").First())); Assert.AreEqual("dataMember", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("DataMember").First())); Assert.AreEqual("Default", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("Default").First())); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 6c012498fb..5c6de7d2a0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -16,6 +16,7 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using System.Threading.Tasks; using BaselineTest; using global::Azure.Core.Serialization; + using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -74,7 +75,7 @@ public void TestMemberInitializerDotNet() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.DotNet); string insertedData = this.GetInsertedData().Result; @@ -104,7 +105,7 @@ public void TestMemberInitializerNewtonsoft() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Newtonsoft); string insertedData = this.GetInsertedData().Result; @@ -133,7 +134,7 @@ public void TestMemberInitializerDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.DataContract); string insertedData = this.GetInsertedData().Result; @@ -162,7 +163,7 @@ public void TestMemberInitializerNewtonsoftDotNetSerializer() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Newtonsoft); string insertedData = this.GetInsertedData().Result; @@ -191,7 +192,7 @@ public void TestMemberInitializerNewtonsoftDataMemberSerializer() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Newtonsoft); string insertedData = this.GetInsertedData().Result; @@ -220,7 +221,7 @@ public void TestMemberInitializerDotNetDataMemberSerializer() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.DataContract); string insertedData = this.GetInsertedData().Result; @@ -244,7 +245,7 @@ public void TestMemberInitializerDotNetDataMemberSerializer() this.ExecuteTestSuite(inputs); } - private (Func>, Func>) InsertDataAndGetQueryables() where T : LinqTestObject + private (Func>, Func>) InsertDataAndGetQueryables(LinqSerializerType linqSerializerType) where T : LinqTestObject { static T createDataObj(int index, bool camelCase) { @@ -258,12 +259,14 @@ static T createDataObj(int index, bool camelCase) CosmosLinqSerializerOptions linqSerializerOptionsCamelCase = new CosmosLinqSerializerOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, - CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()) + CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()), + LinqSerializerType = linqSerializerType }; CosmosLinqSerializerOptions linqSerializerOptionsDefault = new CosmosLinqSerializerOptions { - CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()) + CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()), + LinqSerializerType = linqSerializerType }; Func> getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index cba8e44b49..97f5854c00 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -39,7 +39,14 @@ public void EnumIsPreservedAsINTest() { CosmosLinqSerializerOptions options = new() { - CustomCosmosSerializer = new TestCustomJsonSerializer() + CustomCosmosSerializer = new TestCustomJsonSerializer(), + LinqSerializerType = LinqSerializerType.DotNet + }; + + CosmosLinqSerializerOptions newtonsoftOptions = new() + { + CustomCosmosSerializer = new TestCustomJsonSerializer(), + LinqSerializerType = LinqSerializerType.Newtonsoft }; TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; @@ -48,7 +55,7 @@ public void EnumIsPreservedAsINTest() string sql = SqlTranslator.TranslateExpression(expr.Body, options); Expression> exprNewtonsoft = a => values.Contains(a.Value); - string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, options); + string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sqlNewtonsoft); @@ -59,7 +66,14 @@ public void EnumIsPreservedAsEQUALSTest() { CosmosLinqSerializerOptions options = new() { - CustomCosmosSerializer = new TestCustomJsonSerializer() + CustomCosmosSerializer = new TestCustomJsonSerializer(), + LinqSerializerType = LinqSerializerType.DotNet + }; + + CosmosLinqSerializerOptions newtonsoftOptions = new() + { + CustomCosmosSerializer = new TestCustomJsonSerializer(), + LinqSerializerType = LinqSerializerType.Newtonsoft }; TestEnum statusValue = TestEnum.One; @@ -68,7 +82,7 @@ public void EnumIsPreservedAsEQUALSTest() string sql = SqlTranslator.TranslateExpression(expr.Body, options); Expression> exprNewtonsoft = a => a.Value == statusValue; - string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, options); + string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); @@ -79,7 +93,14 @@ public void EnumIsPreservedAsEXPRESSIONTest() { CosmosLinqSerializerOptions options = new() { - CustomCosmosSerializer = new TestCustomJsonSerializer() + CustomCosmosSerializer = new TestCustomJsonSerializer(), + LinqSerializerType = LinqSerializerType.DotNet + }; + + CosmosLinqSerializerOptions newtonsoftOptions = new() + { + CustomCosmosSerializer = new TestCustomJsonSerializer(), + LinqSerializerType = LinqSerializerType.Newtonsoft }; // Get status constant @@ -116,7 +137,7 @@ public void EnumIsPreservedAsEXPRESSIONTest() Expression> lambdaNewtonsoft = Expression.Lambda>(expressionNewtonsoft, argNewtonsoft); - string sqlNewtonsoft = SqlTranslator.TranslateExpression(lambdaNewtonsoft.Body, options); + string sqlNewtonsoft = SqlTranslator.TranslateExpression(lambdaNewtonsoft.Body, newtonsoftOptions); Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); @@ -131,7 +152,7 @@ enum TestEnum class TestEnumDocument { - [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] // TODO: Remove this once we have the ability to use custom serializer for LINQ queries public TestEnum Value { get; set; } } @@ -165,8 +186,13 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s [TestMethod] public void TestNewtonsoftExtensionDataQuery() { + CosmosLinqSerializerOptions newtonsoftOptions = new() + { + LinqSerializerType = LinqSerializerType.Newtonsoft + }; + Expression> expr = a => (string)a.NewtonsoftExtensionData["foo"] == "bar"; - string sql = SqlTranslator.TranslateExpression(expr.Body); + string sql = SqlTranslator.TranslateExpression(expr.Body, newtonsoftOptions); Assert.AreEqual("(a[\"foo\"] = \"bar\")", sql); } @@ -174,8 +200,13 @@ public void TestNewtonsoftExtensionDataQuery() [TestMethod] public void TestSystemTextJsonExtensionDataQuery() { + CosmosLinqSerializerOptions dotNetOptions = new() + { + LinqSerializerType = LinqSerializerType.DotNet + }; + Expression> expr = a => ((object)a.NetExtensionData["foo"]) == "bar"; - string sql = SqlTranslator.TranslateExpression(expr.Body); + string sql = SqlTranslator.TranslateExpression(expr.Body, dotNetOptions); Assert.AreEqual("(a[\"NetExtensionData\"][\"foo\"] = \"bar\")", sql); } From a0da2c320b51293bf40f165e984cd6c92017a93c Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 8 Nov 2023 15:17:52 -0800 Subject: [PATCH 09/48] clenaup and bugfix --- .../Linq/DataContractCosmosLinqSerializer.cs | 19 ++----------------- .../src/Linq/DefaultCosmosLinqSerializer.cs | 2 +- .../Linq/NewtonsoftCosmosLinqSerializer.cs | 2 +- .../Linq/CosmosLinqJsonConverterTests.cs | 2 +- 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs index de5601cf57..e0da0570bf 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs @@ -4,12 +4,9 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; - using System.Diagnostics; - using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; - using Microsoft.Azure.Documents; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -24,24 +21,12 @@ public DataContractCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializ public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) { - CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - return converterAttribute != null; + return false; } public string Serialize(object value, MemberExpression memberExpression, Type memberType) { - CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute; - - Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "At least one constructor argument exists."); - Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value; - - string serializedValue = converterType.GetConstructor(Type.EmptyTypes) != null - ? JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType)) - : JsonConvert.SerializeObject(value); - - return serializedValue; + throw new InvalidOperationException($"{nameof(DefaultCosmosLinqSerializer)} Assert! - should not reach this function."); } public string SerializeScalarExpression(ConstantExpression inputExpression) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs index e11873e0ac..a1957d6b05 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs @@ -78,7 +78,7 @@ public string SerializeMemberName(MemberInfo memberInfo) Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) { - return String.Empty; // todo check this + return null; } // Json.Net honors JsonPropertyAttribute more than DataMemberAttribute diff --git a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs index 9dc1c435ea..b3d228e85a 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs @@ -68,7 +68,7 @@ public string SerializeMemberName(MemberInfo memberInfo) Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) { - return String.Empty; + return null; } JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 97f5854c00..853ebfc5f3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -58,7 +58,7 @@ public void EnumIsPreservedAsINTest() string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); - Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sqlNewtonsoft); + Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlNewtonsoft); //todo mayapainter check this } [TestMethod] From 656c2781d143e493a652957b239cb2d0ac51e2a5 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 8 Nov 2023 16:42:35 -0800 Subject: [PATCH 10/48] cleanup constructors --- .../Linq/DataContractCosmosLinqSerializer.cs | 13 +++----- .../src/Linq/DefaultCosmosLinqSerializer.cs | 16 +++------- .../src/Linq/DotNetCosmosLinqSerializer.cs | 31 ++++++++++++------- .../Linq/NewtonsoftCosmosLinqSerializer.cs | 13 +++----- .../src/Linq/TranslationContext.cs | 10 +++--- .../src/Serializer/CosmosSerializationUtil.cs | 10 ++++++ .../LinqAttributeContractBaselineTests.cs | 2 +- 7 files changed, 51 insertions(+), 44 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs index e0da0570bf..257687b3bc 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs @@ -12,11 +12,11 @@ namespace Microsoft.Azure.Cosmos.Linq internal class DataContractCosmosLinqSerializer : ICosmosLinqSerializer { - private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; - public DataContractCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + public DataContractCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) { - this.LinqSerializerOptions = linqSerializerOptions; + this.PropertyNamingPolicy = propertyNamingPolicy; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -31,7 +31,7 @@ public string Serialize(object value, MemberExpression memberExpression, Type me public string SerializeScalarExpression(ConstantExpression inputExpression) { - if (this.LinqSerializerOptions != null && this.LinqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + if (this.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { JsonSerializerSettings serializerSettings = new JsonSerializerSettings { @@ -67,10 +67,7 @@ public string SerializeMemberName(MemberInfo memberInfo) memberName ??= memberInfo.Name; - if (this.LinqSerializerOptions != null) - { - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); - } + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs index a1957d6b05..2e463515ce 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs @@ -14,11 +14,11 @@ namespace Microsoft.Azure.Cosmos.Linq internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializer { - private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; - public DefaultCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + public DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) { - this.LinqSerializerOptions = linqSerializerOptions; + this.PropertyNamingPolicy = propertyNamingPolicy; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -101,15 +101,9 @@ public string SerializeMemberName(MemberInfo memberInfo) } } - if (memberName == null) - { - memberName = memberInfo.Name; - } + memberName ??= memberInfo.Name; - if (this.LinqSerializerOptions != null) - { - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); - } + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index ee1f2e3d43..f1556f0165 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; + using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -15,11 +16,13 @@ namespace Microsoft.Azure.Cosmos.Linq internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer { - private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + private readonly CosmosSerializer CustomCosmosSerializer; - public DotNetCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; + public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, CosmosPropertyNamingPolicy propertyNamingPolicy) { - this.LinqSerializerOptions = linqSerializerOptions; + this.CustomCosmosSerializer = customCosmosSerializer; + this.PropertyNamingPolicy = propertyNamingPolicy; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -30,19 +33,28 @@ public bool RequiresCustomSerialization(MemberExpression memberExpression, Type public string Serialize(object value, MemberExpression memberExpression, Type memberType) { - //check this JsonSerializerOptions options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); + + CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)); + + Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "No constructor arguments exist"); + Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value; + + if (converterType == typeof(JsonStringEnumConverter)) + { + options.Converters.Add(new JsonStringEnumConverter()); + } + return System.Text.Json.JsonSerializer.Serialize(value, options); } public string SerializeScalarExpression(ConstantExpression inputExpression) { - if (this.LinqSerializerOptions.CustomCosmosSerializer != null) + if (this.CustomCosmosSerializer != null) { StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); - using (Stream stream = this.LinqSerializerOptions.CustomCosmosSerializer.ToStream(inputExpression.Value)) + using (Stream stream = this.CustomCosmosSerializer.ToStream(inputExpression.Value)) { using (StreamReader streamReader = new StreamReader(stream)) { @@ -64,10 +76,7 @@ public string SerializeMemberName(MemberInfo memberInfo) ? jsonPropertyNameAttribute.Name : memberInfo.Name; - if (this.LinqSerializerOptions != null) - { - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); - } + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs index b3d228e85a..134b25d97e 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs @@ -14,11 +14,11 @@ namespace Microsoft.Azure.Cosmos.Linq internal class NewtonsoftCosmosLinqSerializer : ICosmosLinqSerializer { - private readonly CosmosLinqSerializerOptions LinqSerializerOptions; + private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; - public NewtonsoftCosmosLinqSerializer(CosmosLinqSerializerOptions linqSerializerOptions) + public NewtonsoftCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) { - this.LinqSerializerOptions = linqSerializerOptions; + this.PropertyNamingPolicy = propertyNamingPolicy; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -47,7 +47,7 @@ public string Serialize(object value, MemberExpression memberExpression, Type me public string SerializeScalarExpression(ConstantExpression inputExpression) { - if (this.LinqSerializerOptions != null && this.LinqSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + if (this.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { JsonSerializerSettings serializerSettings = new JsonSerializerSettings { @@ -79,10 +79,7 @@ public string SerializeMemberName(MemberInfo memberInfo) memberName ??= memberInfo.Name; - if (this.LinqSerializerOptions != null) - { - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.LinqSerializerOptions, memberName); - } + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index aaa013b5b6..a90087c89e 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -88,13 +88,13 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.CosmosLinqSerializer = linqSerializerOptions != null ? linqSerializerOptions.LinqSerializerType switch { - LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions), - LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions), - LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions), - LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions), + LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") // todo check this } - : new DefaultCosmosLinqSerializer(linqSerializerOptions); + : new DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy.Default); } public Expression LookupSubstitution(ParameterExpression parameter) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index 171ce53f64..11672108db 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -19,5 +19,15 @@ internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOpt return name; } + + internal static string GetStringWithPropertyNamingPolicy(CosmosPropertyNamingPolicy namingPolicy, string name) + { + if (namingPolicy == CosmosPropertyNamingPolicy.CamelCase) + { + return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); + } + + return name; + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs index b252907925..f979a59cf2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs @@ -172,7 +172,7 @@ public Datum2(string jsonProperty, string dataMember, string defaultMember, stri [TestMethod] public void TestAttributePriority() { - ICosmosLinqSerializer cosmosLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosLinqSerializerOptions()); + ICosmosLinqSerializer cosmosLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosPropertyNamingPolicy()); Assert.AreEqual("jsonProperty", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("JsonProperty").First())); Assert.AreEqual("dataMember", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("DataMember").First())); Assert.AreEqual("Default", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("Default").First())); From 32ba80d1d8c69a63e6584096f11399690f6e7785 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 8 Nov 2023 16:58:02 -0800 Subject: [PATCH 11/48] fix baseline --- .../LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml index 4366c080a5..d22afb37f9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqSQLTranslationBaselineTest.ValidateSQLTranslation.xml @@ -278,7 +278,7 @@ FROM root]]> From 74e2410d809e09740884e7d93ae939d0c694f296 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 8 Nov 2023 20:33:10 -0800 Subject: [PATCH 12/48] updates --- .../src/Linq/DotNetCosmosLinqSerializer.cs | 23 ++-- .../src/Linq/LinqSerializerType.cs | 10 +- .../Linq/NewtonsoftCosmosLinqSerializer.cs | 3 +- .../src/Linq/TranslationContext.cs | 2 +- .../Serializer/CosmosLinqSerializerOptions.cs | 3 +- ...seline.TestMemberInitializerNewtonsoft.xml | 120 +++++++++--------- ...TranslationWithCustomSerializerBaseline.cs | 8 ++ .../Linq/CosmosLinqJsonConverterTests.cs | 2 +- 8 files changed, 88 insertions(+), 83 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index f1556f0165..5341f07654 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -12,16 +12,16 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; - using Newtonsoft.Json; internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer { private readonly CosmosSerializer CustomCosmosSerializer; private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; + public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, CosmosPropertyNamingPolicy propertyNamingPolicy) { - this.CustomCosmosSerializer = customCosmosSerializer; + this.CustomCosmosSerializer = customCosmosSerializer ?? new CosmosJsonDotNetSerializer(); this.PropertyNamingPolicy = propertyNamingPolicy; } @@ -50,22 +50,17 @@ public string Serialize(object value, MemberExpression memberExpression, Type me public string SerializeScalarExpression(ConstantExpression inputExpression) { - if (this.CustomCosmosSerializer != null) - { - StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); + StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); - using (Stream stream = this.CustomCosmosSerializer.ToStream(inputExpression.Value)) + using (Stream stream = this.CustomCosmosSerializer.ToStream(inputExpression.Value)) + { + using (StreamReader streamReader = new StreamReader(stream)) { - using (StreamReader streamReader = new StreamReader(stream)) - { - string propertyValue = streamReader.ReadToEnd(); - writer.Write(propertyValue); - return writer.ToString(); - } + string propertyValue = streamReader.ReadToEnd(); + writer.Write(propertyValue); + return writer.ToString(); } } - - return JsonConvert.SerializeObject(inputExpression.Value); //todo: fix this, throw error if custom serializer is null? } public string SerializeMemberName(MemberInfo memberInfo) diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index a8aafc02ca..944fd6fff0 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -4,27 +4,27 @@ namespace Microsoft.Azure.Cosmos.Linq { /// - /// Serializer to be used for LINQ query translations + /// Serializer type to be used for LINQ query translations. /// public enum LinqSerializerType { /// - /// TODO + /// Follows the exisitng serializer pattern, which honors Newtonsoft and DataContract attributes, but not System.Twxt.Json. /// Default, /// - /// TODO + /// Uses a Newtonsoft serializer, which will honor Newtonsoft attributes. /// Newtonsoft, /// - /// TODO + /// Uses a DataContract serializer, which will honor DataMember attributes specified on properies. /// DataContract, /// - /// TODO + /// Uses a custom CosmosSerializer, if provided. This will honor System.Twxt.Json attributes. /// DotNet, } diff --git a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs index 134b25d97e..47c142a4ce 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs @@ -51,8 +51,9 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) { JsonSerializerSettings serializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver() + ContractResolver = new CamelCasePropertyNamesContractResolver(), }; + //serializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); //todo: need to find way to identify StringEnumCOnverter decoration return JsonConvert.SerializeObject(inputExpression.Value, serializerSettings); } diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index a90087c89e..7c5f87a4d6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -92,7 +92,7 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") // todo check this + _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") } : new DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy.Default); } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index a041bedeec..3541db96bf 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -31,7 +31,8 @@ public CosmosLinqSerializerOptions() public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } /// - /// TODO + /// Specifies the type of serializer to be used for LINQ translations. + /// Options are detailed in /// /// /// The default value is LinqSerializerType.Default diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoft.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoft.xml index aa3c4b0f03..49793a8069 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoft.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoft.xml @@ -10,12 +10,12 @@ SELECT VALUE root FROM root WHERE (root["numberValueNewtonsoft"] = 1)]]> @@ -31,12 +31,12 @@ SELECT VALUE root FROM root WHERE (root = {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null})]]> @@ -51,12 +51,12 @@ WHERE (root = {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": n SELECT VALUE {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} FROM root]]> SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) FROM root]]> @@ -127,12 +127,12 @@ SELECT VALUE root FROM root WHERE (root["NumberValueNewtonsoft"] = 1)]]> @@ -148,12 +148,12 @@ SELECT VALUE root FROM root WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null})]]> @@ -168,12 +168,12 @@ WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": n SELECT VALUE {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} FROM root]]> SELECT VALUE ((root["NumberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) FROM root]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 5c6de7d2a0..fcbfed6a94 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -359,6 +359,9 @@ private class DataObjectDotNet : LinqTestObject [JsonPropertyName("stringValueDotNet")] public string StringField { get; set; } + [System.Text.Json.Serialization.JsonIgnore] + public string IgnoreField { get; set; } + public string id { get; set; } public string Pk { get; set; } @@ -369,6 +372,7 @@ public DataObjectDotNet(double numericField, string stringField, string id, stri { this.NumericField = numericField; this.StringField = stringField; + this.IgnoreField = "ignore"; this.id = id; this.Pk = pk; } @@ -387,6 +391,9 @@ private class DataObjectNewtonsoft : LinqTestObject [Newtonsoft.Json.JsonProperty(PropertyName = "StringValueNewtonsoft")] public string StringField { get; set; } + [Newtonsoft.Json.JsonIgnore] + public string IgnoreField { get; set; } + public string id { get; set; } public string Pk { get; set; } @@ -397,6 +404,7 @@ public DataObjectNewtonsoft(double numericField, string stringField, string id, { this.NumericField = numericField; this.StringField = stringField; + this.IgnoreField = "ignore"; this.id = id; this.Pk = pk; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 853ebfc5f3..6ff08b074a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -58,7 +58,7 @@ public void EnumIsPreservedAsINTest() string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); - Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlNewtonsoft); //todo mayapainter check this + Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlNewtonsoft); //todo: find way to support StringEnum conversion here } [TestMethod] From 573b1e4d92dccbe31b5a8eedf84e7eadfc90ca28 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Thu, 9 Nov 2023 11:05:38 -0800 Subject: [PATCH 13/48] cleanup --- .../Usage/SystemTextJson/Program.cs | 4 ---- Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs | 4 ++-- .../Linq/CosmosLinqJsonConverterTests.cs | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs b/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs index d66d54f5a2..ac990a4e28 100644 --- a/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs +++ b/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs @@ -177,19 +177,15 @@ private static async Task CleanupAsync() // public class ToDoActivity { - // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("id")] public string Id { get; set; } - // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("partitionKey")] public string PartitionKey { get; set; } - // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("activityId")] public string ActivityId { get; set; } - // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("status")] public string Status { get; set; } } diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 944fd6fff0..9bc660ffb7 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Cosmos.Linq public enum LinqSerializerType { /// - /// Follows the exisitng serializer pattern, which honors Newtonsoft and DataContract attributes, but not System.Twxt.Json. + /// Follows the exisitng serializer pattern. This honors Newtonsoft and DataContract attributes, but not System.Text.Json. /// Default, @@ -24,7 +24,7 @@ public enum LinqSerializerType DataContract, /// - /// Uses a custom CosmosSerializer, if provided. This will honor System.Twxt.Json attributes. + /// Uses a custom CosmosSerializer, if provided. This will honor System.Text.Json attributes. /// DotNet, } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 6ff08b074a..be73936020 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -152,7 +152,7 @@ enum TestEnum class TestEnumDocument { - [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] // TODO: Remove this once we have the ability to use custom serializer for LINQ queries + [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] // TODO: Remove this once we have the ability to use custom serializer for LINQ queries public TestEnum Value { get; set; } } From d6b6f9ab98852dbebb2503d6638850c038c28735 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Thu, 9 Nov 2023 16:02:24 -0800 Subject: [PATCH 14/48] removed requirement to decorate enums --- .../src/Linq/DotNetCosmosLinqSerializer.cs | 47 +++++++------------ .../Linq/CosmosLinqJsonConverterTests.cs | 1 - 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index 5341f07654..025cb461bb 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -4,13 +4,10 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; - using System.Diagnostics; using System.Globalization; using System.IO; - using System.Linq; using System.Linq.Expressions; using System.Reflection; - using System.Text.Json; using System.Text.Json.Serialization; internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer @@ -27,40 +24,17 @@ public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, Cosmo public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) { - CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)); - return converterAttribute != null; + return true; } public string Serialize(object value, MemberExpression memberExpression, Type memberType) { - JsonSerializerOptions options = new JsonSerializerOptions(); - - CustomAttributeData converterAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(System.Text.Json.Serialization.JsonConverterAttribute)); - - Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "No constructor arguments exist"); - Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value; - - if (converterType == typeof(JsonStringEnumConverter)) - { - options.Converters.Add(new JsonStringEnumConverter()); - } - - return System.Text.Json.JsonSerializer.Serialize(value, options); + return this.SerializeWithCustomSerializer(value); } public string SerializeScalarExpression(ConstantExpression inputExpression) { - StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); - - using (Stream stream = this.CustomCosmosSerializer.ToStream(inputExpression.Value)) - { - using (StreamReader streamReader = new StreamReader(stream)) - { - string propertyValue = streamReader.ReadToEnd(); - writer.Write(propertyValue); - return writer.ToString(); - } - } + return this.SerializeWithCustomSerializer(inputExpression.Value); } public string SerializeMemberName(MemberInfo memberInfo) @@ -75,5 +49,20 @@ public string SerializeMemberName(MemberInfo memberInfo) return memberName; } + + private string SerializeWithCustomSerializer(object value) + { + StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); + + using (Stream stream = this.CustomCosmosSerializer.ToStream(value)) + { + using (StreamReader streamReader = new StreamReader(stream)) + { + string propertyValue = streamReader.ReadToEnd(); + writer.Write(propertyValue); + return writer.ToString(); + } + } + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index be73936020..338fa9e9b5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -152,7 +152,6 @@ enum TestEnum class TestEnumDocument { - [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] // TODO: Remove this once we have the ability to use custom serializer for LINQ queries public TestEnum Value { get; set; } } From 0dec53d2767fa218bec3a46240259db817aac543 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Mon, 13 Nov 2023 14:13:39 -0800 Subject: [PATCH 15/48] nit PR comments --- .../Linq/DataContractCosmosLinqSerializer.cs | 4 +--- .../src/Linq/DefaultCosmosLinqSerializer.cs | 4 +--- .../src/Linq/LinqSerializerType.cs | 2 +- .../Linq/NewtonsoftCosmosLinqSerializer.cs | 4 +--- .../src/Linq/TranslationContext.cs | 20 +++++++++---------- .../src/Serializer/CosmosSerializationUtil.cs | 2 +- 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs index 257687b3bc..3ca31ee35f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs @@ -46,7 +46,7 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) public string SerializeMemberName(MemberInfo memberInfo) { - string memberName = null; + string memberName = memberInfo.Name; // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); @@ -65,8 +65,6 @@ public string SerializeMemberName(MemberInfo memberInfo) } } - memberName ??= memberInfo.Name; - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; diff --git a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs index 2e463515ce..378815e992 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs @@ -72,7 +72,7 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) public string SerializeMemberName(MemberInfo memberInfo) { - string memberName = null; + string memberName = memberInfo.Name; // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); @@ -101,8 +101,6 @@ public string SerializeMemberName(MemberInfo memberInfo) } } - memberName ??= memberInfo.Name; - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 9bc660ffb7..5f5bc02088 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Cosmos.Linq public enum LinqSerializerType { /// - /// Follows the exisitng serializer pattern. This honors Newtonsoft and DataContract attributes, but not System.Text.Json. + /// Follows the exisiting serializer pattern. This honors Newtonsoft attributes, followed by DataContract attributes, but not System.Text.Json attributes. /// Default, diff --git a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs index 47c142a4ce..09f91b0da1 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs @@ -63,7 +63,7 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) public string SerializeMemberName(MemberInfo memberInfo) { - string memberName = null; + string memberName = memberInfo.Name; // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); @@ -78,8 +78,6 @@ public string SerializeMemberName(MemberInfo memberInfo) memberName = jsonPropertyAttribute.PropertyName; } - memberName ??= memberInfo.Name; - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); return memberName; diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 7c5f87a4d6..a35a7d547c 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -84,17 +84,15 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.LinqSerializerOptions = linqSerializerOptions; this.Parameters = parameters; this.MemberNames = new MemberNames(linqSerializerOptions); - - this.CosmosLinqSerializer = linqSerializerOptions != null - ? linqSerializerOptions.LinqSerializerType switch - { - LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") - } - : new DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy.Default); + + this.CosmosLinqSerializer = (linqSerializerOptions?.LinqSerializerType ?? LinqSerializerType.Default) switch + { + LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") + }; } public Expression LookupSubstitution(ParameterExpression parameter) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index 11672108db..0f355d6298 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -12,7 +12,7 @@ internal static class CosmosSerializationUtil internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOptions options, string name) { - if (options != null && options.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + if (options?.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) { return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); } From 6a2d26a7c9d752b35426c1368b36b34702b2dd52 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Mon, 13 Nov 2023 14:36:45 -0800 Subject: [PATCH 16/48] bug fix and api updates --- .../src/Linq/TranslationContext.cs | 18 ++++--- .../Contracts/DotNetSDKAPI.json | 50 +++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index a35a7d547c..7e18c88a8f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -85,14 +85,16 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.Parameters = parameters; this.MemberNames = new MemberNames(linqSerializerOptions); - this.CosmosLinqSerializer = (linqSerializerOptions?.LinqSerializerType ?? LinqSerializerType.Default) switch - { - LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") - }; + this.CosmosLinqSerializer = linqSerializerOptions != null + ? linqSerializerOptions.LinqSerializerType switch + { + LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") + } + : new DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy.Default); } public Expression LookupSubstitution(ParameterExpression parameter) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 8c8d7d6ae1..cc345a2caa 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3383,11 +3383,30 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy PropertyNamingPolicy;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy get_PropertyNamingPolicy();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_PropertyNamingPolicy(Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void .ctor()": { "Type": "Constructor", "Attributes": [], "MethodInfo": "[Void .ctor(), Void .ctor()]" }, + "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_PropertyNamingPolicy(Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -5707,6 +5726,37 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { + "Subclasses": {}, + "Members": { + "Int32 value__": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DataContract": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DataContract;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DotNet": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DotNet;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Newtonsoft": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Newtonsoft;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.OperationKind;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { "Subclasses": {}, "Members": { From ab6be11a19fb39554e3ab9bae0fbdebb01778c0c Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Mon, 13 Nov 2023 15:45:19 -0800 Subject: [PATCH 17/48] Pr comments --- .../Linq/DataContractCosmosLinqSerializer.cs | 36 ++--- .../src/Linq/DotNetCosmosLinqSerializer.cs | 6 +- ...erBaseline.TestMemberInitializerDotNet.xml | 141 +++++++++--------- ...TranslationWithCustomSerializerBaseline.cs | 6 +- 4 files changed, 95 insertions(+), 94 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs index 3ca31ee35f..461e5fba73 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs @@ -4,18 +4,25 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; + using System.Globalization; + using System.IO; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; internal class DataContractCosmosLinqSerializer : ICosmosLinqSerializer { + private readonly CosmosSerializer CosmosSerializer; + private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; public DataContractCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) { + this.CosmosSerializer = new CosmosJsonDotNetSerializer(new CosmosSerializationOptions() + { + PropertyNamingPolicy = propertyNamingPolicy + }); + this.PropertyNamingPolicy = propertyNamingPolicy; } @@ -26,35 +33,28 @@ public bool RequiresCustomSerialization(MemberExpression memberExpression, Type public string Serialize(object value, MemberExpression memberExpression, Type memberType) { - throw new InvalidOperationException($"{nameof(DefaultCosmosLinqSerializer)} Assert! - should not reach this function."); + throw new InvalidOperationException($"{nameof(DefaultCosmosLinqSerializer)} Assert! Should not reach this function."); } public string SerializeScalarExpression(ConstantExpression inputExpression) { - if (this.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); + + using (Stream stream = this.CosmosSerializer.ToStream(inputExpression.Value)) { - JsonSerializerSettings serializerSettings = new JsonSerializerSettings + using (StreamReader streamReader = new StreamReader(stream)) { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - - return JsonConvert.SerializeObject(inputExpression.Value, serializerSettings); + string propertyValue = streamReader.ReadToEnd(); + writer.Write(propertyValue); + return writer.ToString(); + } } - - return JsonConvert.SerializeObject(inputExpression.Value); } public string SerializeMemberName(MemberInfo memberInfo) { string memberName = memberInfo.Name; - // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. - Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); - if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) - { - return null; - } - DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); if (dataContractAttribute != null) { diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index 025cb461bb..c0dba01a88 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -18,7 +18,11 @@ internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, CosmosPropertyNamingPolicy propertyNamingPolicy) { - this.CustomCosmosSerializer = customCosmosSerializer ?? new CosmosJsonDotNetSerializer(); + this.CustomCosmosSerializer = customCosmosSerializer + ?? new CosmosJsonDotNetSerializer(new CosmosSerializationOptions() + { + PropertyNamingPolicy = propertyNamingPolicy + }); this.PropertyNamingPolicy = propertyNamingPolicy; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml index 0b02c7897f..18b6d2dada 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml @@ -10,17 +10,14 @@ SELECT VALUE root FROM root WHERE (root["numberValueDotNet"] = 1)]]> - + @@ -32,14 +29,14 @@ WHERE (root["numberValueDotNet"] = 1)]]> +WHERE (root = {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null})]]> @@ -51,15 +48,15 @@ WHERE (root = {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk 1) ? {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null} : {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["numberValueDotNet"] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> @@ -128,14 +125,14 @@ WHERE (root = {"numberValueDotNet": root["numberValueDotNet"], "stringValueDotNe +WHERE (root["NumberValueDotNet"] = 1)]]> +WHERE (root = {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null})]]> @@ -171,15 +168,15 @@ WHERE (root = {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk 1) ? {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null} : {"numberValueDotNet": 1, "stringValueDotNet": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["NumberValueDotNet"] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root = {"NumberValueDotNet": root["NumberValueDotNet"], "StringValueDotNet": root["StringValueDotNet"]})]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index fcbfed6a94..1a125e0494 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -353,10 +353,10 @@ public override Stream ToStream(T input) private class DataObjectDotNet : LinqTestObject { - [JsonPropertyName("numberValueDotNet")] + [JsonPropertyName("NumberValueDotNet")] public double NumericField { get; set; } - [JsonPropertyName("stringValueDotNet")] + [JsonPropertyName("StringValueDotNet")] public string StringField { get; set; } [System.Text.Json.Serialization.JsonIgnore] @@ -372,7 +372,7 @@ public DataObjectDotNet(double numericField, string stringField, string id, stri { this.NumericField = numericField; this.StringField = stringField; - this.IgnoreField = "ignore"; + this.IgnoreField = "Ignore"; this.id = id; this.Pk = pk; } From 7e9f1ea34e3047764e42ac6bf076cd3ccd3ffde0 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 14 Nov 2023 11:53:31 -0800 Subject: [PATCH 18/48] remove datacontract and newtonsoft serializer types --- .../Linq/DataContractCosmosLinqSerializer.cs | 73 ---------------- .../src/Linq/LinqSerializerType.cs | 12 +-- .../Linq/NewtonsoftCosmosLinqSerializer.cs | 86 ------------------- .../src/Linq/TranslationContext.cs | 4 +- ...TranslationWithCustomSerializerBaseline.cs | 12 +-- .../Linq/CosmosLinqJsonConverterTests.cs | 18 ++-- 6 files changed, 17 insertions(+), 188 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs deleted file mode 100644 index 461e5fba73..0000000000 --- a/Microsoft.Azure.Cosmos/src/Linq/DataContractCosmosLinqSerializer.cs +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Linq -{ - using System; - using System.Globalization; - using System.IO; - using System.Linq.Expressions; - using System.Reflection; - using System.Runtime.Serialization; - - internal class DataContractCosmosLinqSerializer : ICosmosLinqSerializer - { - private readonly CosmosSerializer CosmosSerializer; - - private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; - - public DataContractCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) - { - this.CosmosSerializer = new CosmosJsonDotNetSerializer(new CosmosSerializationOptions() - { - PropertyNamingPolicy = propertyNamingPolicy - }); - - this.PropertyNamingPolicy = propertyNamingPolicy; - } - - public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) - { - return false; - } - - public string Serialize(object value, MemberExpression memberExpression, Type memberType) - { - throw new InvalidOperationException($"{nameof(DefaultCosmosLinqSerializer)} Assert! Should not reach this function."); - } - - public string SerializeScalarExpression(ConstantExpression inputExpression) - { - StringWriter writer = new StringWriter(CultureInfo.InvariantCulture); - - using (Stream stream = this.CosmosSerializer.ToStream(inputExpression.Value)) - { - using (StreamReader streamReader = new StreamReader(stream)) - { - string propertyValue = streamReader.ReadToEnd(); - writer.Write(propertyValue); - return writer.ToString(); - } - } - } - - public string SerializeMemberName(MemberInfo memberInfo) - { - string memberName = memberInfo.Name; - - DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute(true); - if (dataContractAttribute != null) - { - DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute(true); - if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name)) - { - memberName = dataMemberAttribute.Name; - } - } - - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); - - return memberName; - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 5f5bc02088..10b17d0937 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -13,19 +13,9 @@ public enum LinqSerializerType /// Default, - /// - /// Uses a Newtonsoft serializer, which will honor Newtonsoft attributes. - /// - Newtonsoft, - - /// - /// Uses a DataContract serializer, which will honor DataMember attributes specified on properies. - /// - DataContract, - /// /// Uses a custom CosmosSerializer, if provided. This will honor System.Text.Json attributes. /// - DotNet, + CustomCosmosSerializer, } } diff --git a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs deleted file mode 100644 index 09f91b0da1..0000000000 --- a/Microsoft.Azure.Cosmos/src/Linq/NewtonsoftCosmosLinqSerializer.cs +++ /dev/null @@ -1,86 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Linq -{ - using System; - using System.Diagnostics; - using System.Linq; - using System.Linq.Expressions; - using System.Reflection; - using Microsoft.Azure.Documents; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; - - internal class NewtonsoftCosmosLinqSerializer : ICosmosLinqSerializer - { - private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; - - public NewtonsoftCosmosLinqSerializer(CosmosPropertyNamingPolicy propertyNamingPolicy) - { - this.PropertyNamingPolicy = propertyNamingPolicy; - } - - public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) - { - CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - - return memberAttribute != null || typeAttribute != null; - } - - public string Serialize(object value, MemberExpression memberExpression, Type memberType) - { - CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute)); - CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute; - - Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "At least one constructor argument exists."); - Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value; - - string serializedValue = converterType.GetConstructor(Type.EmptyTypes) != null - ? JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType)) - : JsonConvert.SerializeObject(value); - - return serializedValue; - } - - public string SerializeScalarExpression(ConstantExpression inputExpression) - { - if (this.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) - { - JsonSerializerSettings serializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - }; - //serializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); //todo: need to find way to identify StringEnumCOnverter decoration - - return JsonConvert.SerializeObject(inputExpression.Value, serializerSettings); - } - - return JsonConvert.SerializeObject(inputExpression.Value); - } - - public string SerializeMemberName(MemberInfo memberInfo) - { - string memberName = memberInfo.Name; - - // Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name. - Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute(true); - if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData) - { - return null; - } - - JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute(true); - if (jsonPropertyAttribute != null && !string.IsNullOrEmpty(jsonPropertyAttribute.PropertyName)) - { - memberName = jsonPropertyAttribute.PropertyName; - } - - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); - - return memberName; - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 7e18c88a8f..e9603ec3b8 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -89,9 +89,7 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi ? linqSerializerOptions.LinqSerializerType switch { LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.DotNet => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.Newtonsoft => new NewtonsoftCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.DataContract => new DataContractCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), + LinqSerializerType.CustomCosmosSerializer => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") } : new DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy.Default); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 1a125e0494..6c57a065d3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -75,7 +75,7 @@ public void TestMemberInitializerDotNet() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.DotNet); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.CustomCosmosSerializer); string insertedData = this.GetInsertedData().Result; @@ -105,7 +105,7 @@ public void TestMemberInitializerNewtonsoft() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Newtonsoft); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -134,7 +134,7 @@ public void TestMemberInitializerDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.DataContract); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -163,7 +163,7 @@ public void TestMemberInitializerNewtonsoftDotNetSerializer() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Newtonsoft); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -192,7 +192,7 @@ public void TestMemberInitializerNewtonsoftDataMemberSerializer() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Newtonsoft); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -221,7 +221,7 @@ public void TestMemberInitializerDotNetDataMemberSerializer() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.DataContract); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 338fa9e9b5..b7e3276a3f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -40,13 +40,13 @@ public void EnumIsPreservedAsINTest() CosmosLinqSerializerOptions options = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.DotNet + LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; CosmosLinqSerializerOptions newtonsoftOptions = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.Newtonsoft + LinqSerializerType = LinqSerializerType.Default }; TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; @@ -58,7 +58,7 @@ public void EnumIsPreservedAsINTest() string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); - Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlNewtonsoft); //todo: find way to support StringEnum conversion here + Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlNewtonsoft); } [TestMethod] @@ -67,13 +67,13 @@ public void EnumIsPreservedAsEQUALSTest() CosmosLinqSerializerOptions options = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.DotNet + LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; CosmosLinqSerializerOptions newtonsoftOptions = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.Newtonsoft + LinqSerializerType = LinqSerializerType.Default }; TestEnum statusValue = TestEnum.One; @@ -94,13 +94,13 @@ public void EnumIsPreservedAsEXPRESSIONTest() CosmosLinqSerializerOptions options = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.DotNet + LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; CosmosLinqSerializerOptions newtonsoftOptions = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.Newtonsoft + LinqSerializerType = LinqSerializerType.Default }; // Get status constant @@ -187,7 +187,7 @@ public void TestNewtonsoftExtensionDataQuery() { CosmosLinqSerializerOptions newtonsoftOptions = new() { - LinqSerializerType = LinqSerializerType.Newtonsoft + LinqSerializerType = LinqSerializerType.Default }; Expression> expr = a => (string)a.NewtonsoftExtensionData["foo"] == "bar"; @@ -201,7 +201,7 @@ public void TestSystemTextJsonExtensionDataQuery() { CosmosLinqSerializerOptions dotNetOptions = new() { - LinqSerializerType = LinqSerializerType.DotNet + LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; Expression> expr = a => ((object)a.NetExtensionData["foo"]) == "bar"; From 4e34878eac66a925ac897e2c2974f8adfbd681ff Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 15 Nov 2023 14:35:32 -0800 Subject: [PATCH 19/48] Pr comments and adding tests --- .../src/Linq/DotNetCosmosLinqSerializer.cs | 5 +- ...seline.TestMemberInitializerDataMember.xml | 6 +- ...erBaseline.TestMemberInitializerDotNet.xml | 73 ++++++++++++++++--- ...rInitializerDotNetDataMemberSerializer.xml | 6 +- ...seline.TestMemberInitializerNewtonsoft.xml | 6 +- ...tializerNewtonsoftDataMemberSerializer.xml | 6 +- ...rInitializerNewtonsoftDotNetSerializer.xml | 6 +- ...TranslationWithCustomSerializerBaseline.cs | 3 +- .../Contracts/DotNetSDKAPI.json | 14 +--- .../Linq/CosmosLinqJsonConverterTests.cs | 36 ++++----- 10 files changed, 100 insertions(+), 61 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index c0dba01a88..0f43a1998f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -14,8 +14,6 @@ internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer { private readonly CosmosSerializer CustomCosmosSerializer; - private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; - public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, CosmosPropertyNamingPolicy propertyNamingPolicy) { this.CustomCosmosSerializer = customCosmosSerializer @@ -23,7 +21,6 @@ public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, Cosmo { PropertyNamingPolicy = propertyNamingPolicy }); - this.PropertyNamingPolicy = propertyNamingPolicy; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -49,7 +46,7 @@ public string SerializeMemberName(MemberInfo memberInfo) ? jsonPropertyNameAttribute.Name : memberInfo.Name; - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(this.PropertyNamingPolicy, memberName); + memberName = this.SerializeWithCustomSerializer(memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml index d53c9f35a0..6734a125fa 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDataMember.xml @@ -29,7 +29,7 @@ WHERE (root["numericFieldDataMember"] = 1)]]> +WHERE (root = {"NumericFieldDataMember": 1, "StringFieldDataMember": "1", "id": null, "Pk": null})]]> 1) ? {"numericFieldDataMember": 1, "stringFieldDataMember": "1", "id": null, "pk": null} : {"numericFieldDataMember": 1, "stringFieldDataMember": "1", "id": null, "pk": null}) +SELECT VALUE ((root["numericFieldDataMember"] > 1) ? {"NumericFieldDataMember": 1, "StringFieldDataMember": "1", "id": null, "Pk": null} : {"NumericFieldDataMember": 1, "StringFieldDataMember": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root["\"NumberValueDotNet\""] = 1)]]> 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["\"NumberValueDotNet\""] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root = {""NumberValueDotNet"": root["\"NumberValueDotNet\""], ""StringValueDotNet"": root["\"StringValueDotNet\""]})]]> - + - - (doc.NumericField == 1))]]> + + x).OrderBy(x => x.NumericField).Take(5)]]> +ORDER BY root["\"NumberValueDotNet\""] ASC]]> "{\"NumberValueDotNet\": 2, \"StringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> + + + + (doc.NumericField == 1))]]> + + + + + + + @@ -195,7 +219,7 @@ FROM root]]> 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["\"NumberValueDotNet\""] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root = {""NumberValueDotNet"": root["\"NumberValueDotNet\""], ""StringValueDotNet"": root["\"StringValueDotNet\""]})]]> - + + + + + + + x).OrderBy(x => x.NumericField).Take(5)]]> + + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml index 8423e42b46..a08a3bc493 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml @@ -29,7 +29,7 @@ WHERE (root["numericFieldDataMember"] = 1)]]> +WHERE (root = {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"})]]> 1) ? {"numericFieldDataMember": 1, "stringFieldDataMember": "1"} : {"numericFieldDataMember": 1, "stringFieldDataMember": "1"}) +SELECT VALUE ((root["numericFieldDataMember"] > 1) ? {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"} : {"NumericFieldDataMember": 1, "StringFieldDataMember": "1"}) FROM root]]> +WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null})]]> 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) +SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"})]]> 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1"} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1"}) +SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1"}) FROM root]]> +WHERE (root = {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null})]]> 1) ? {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null} : {"numberValueNewtonsoft": 1, "stringValueNewtonsoft": "1", "id": null, "pk": null}) +SELECT VALUE ((root["numberValueNewtonsoft"] > 1) ? {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null} : {"NumberValueNewtonsoft": 1, "StringValueNewtonsoft": "1", "id": null, "Pk": null}) FROM root]]> getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData), + new LinqTestInput("Mutistep query, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(x => x).OrderBy(x => x).Select(f => f.NumericField).Take(5), skipVerification : true, inputData: insertedData), }; inputs.AddRange(camelCaseSettingInputs); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index b018ec605d..4eb50c0de0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -5734,25 +5734,15 @@ "Attributes": [], "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DataContract": { + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer": { "Type": "Field", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DataContract;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer;IsInitOnly:False;IsStatic:True;" }, "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default": { "Type": "Field", "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DotNet": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType DotNet;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Newtonsoft": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Newtonsoft;IsInitOnly:False;IsStatic:True;" } }, "NestedTypes": {} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index b7e3276a3f..0bba951949 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -43,7 +43,7 @@ public void EnumIsPreservedAsINTest() LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; - CosmosLinqSerializerOptions newtonsoftOptions = new() + CosmosLinqSerializerOptions defaultOptions = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), LinqSerializerType = LinqSerializerType.Default @@ -51,14 +51,14 @@ public void EnumIsPreservedAsINTest() TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; - Expression> expr = a => values.Contains(a.Value); + Expression> expr = a => values.Contains(a.Value); string sql = SqlTranslator.TranslateExpression(expr.Body, options); Expression> exprNewtonsoft = a => values.Contains(a.Value); - string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); + string sqlDefault = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, defaultOptions); Assert.AreEqual("(a[\"Value\"] IN (\"One\", \"Two\"))", sql); - Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlNewtonsoft); + Assert.AreEqual("(a[\"Value\"] IN (0, 1))", sqlDefault); } [TestMethod] @@ -70,7 +70,7 @@ public void EnumIsPreservedAsEQUALSTest() LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; - CosmosLinqSerializerOptions newtonsoftOptions = new() + CosmosLinqSerializerOptions defaultOptions = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), LinqSerializerType = LinqSerializerType.Default @@ -81,8 +81,8 @@ public void EnumIsPreservedAsEQUALSTest() Expression> expr = a => a.Value == statusValue; string sql = SqlTranslator.TranslateExpression(expr.Body, options); - Expression> exprNewtonsoft = a => a.Value == statusValue; - string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprNewtonsoft.Body, newtonsoftOptions); + Expression> exprDefault = a => a.Value == statusValue; + string sqlNewtonsoft = SqlTranslator.TranslateExpression(exprDefault.Body, defaultOptions); Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); @@ -97,7 +97,7 @@ public void EnumIsPreservedAsEXPRESSIONTest() LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; - CosmosLinqSerializerOptions newtonsoftOptions = new() + CosmosLinqSerializerOptions defaultOptions = new() { CustomCosmosSerializer = new TestCustomJsonSerializer(), LinqSerializerType = LinqSerializerType.Default @@ -115,7 +115,7 @@ public void EnumIsPreservedAsEXPRESSIONTest() arg, typeof(TestEnumDocument).GetProperty(nameof(TestEnumDocument.Value))! ); - MemberExpression docValueExpressionNewtonsoft = Expression.MakeMemberAccess( + MemberExpression docValueExpressionDefault = Expression.MakeMemberAccess( argNewtonsoft, typeof(TestEnumNewtonsoftDocument).GetProperty(nameof(TestEnumNewtonsoftDocument.Value))! ); @@ -125,22 +125,22 @@ public void EnumIsPreservedAsEXPRESSIONTest() docValueExpression, status ); - BinaryExpression expressionNewtonsoft = Expression.Equal( - docValueExpressionNewtonsoft, + BinaryExpression expressionDefault = Expression.Equal( + docValueExpressionDefault, status ); // Create lambda expression - Expression> lambda = + Expression> lambda = Expression.Lambda>(expression, arg); string sql = SqlTranslator.TranslateExpression(lambda.Body, options); Expression> lambdaNewtonsoft = - Expression.Lambda>(expressionNewtonsoft, argNewtonsoft); - string sqlNewtonsoft = SqlTranslator.TranslateExpression(lambdaNewtonsoft.Body, newtonsoftOptions); + Expression.Lambda>(expressionDefault, argNewtonsoft); + string sqlDefault = SqlTranslator.TranslateExpression(lambdaNewtonsoft.Body, defaultOptions); Assert.AreEqual("(a[\"Value\"] = \"One\")", sql); - Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlNewtonsoft); + Assert.AreEqual("(a[\"Value\"] = \"One\")", sqlDefault); } enum TestEnum @@ -185,13 +185,13 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s [TestMethod] public void TestNewtonsoftExtensionDataQuery() { - CosmosLinqSerializerOptions newtonsoftOptions = new() + CosmosLinqSerializerOptions options = new() { LinqSerializerType = LinqSerializerType.Default }; Expression> expr = a => (string)a.NewtonsoftExtensionData["foo"] == "bar"; - string sql = SqlTranslator.TranslateExpression(expr.Body, newtonsoftOptions); + string sql = SqlTranslator.TranslateExpression(expr.Body, options); Assert.AreEqual("(a[\"foo\"] = \"bar\")", sql); } @@ -260,7 +260,7 @@ public override T FromStream(Stream stream) public override Stream ToStream(T input) { - MemoryStream stream = new (); + MemoryStream stream = new(); this.systemTextJsonSerializer.Serialize(stream, input, typeof(T), default); stream.Position = 0; From 36c5e67b01c9c2b841cc1bfbae1f0c253a7e8546 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 15 Nov 2023 18:44:47 -0800 Subject: [PATCH 20/48] updates --- .../src/Linq/DotNetCosmosLinqSerializer.cs | 11 ++--- .../src/Linq/TranslationContext.cs | 24 +++++++---- ...erBaseline.TestMemberInitializerDotNet.xml | 42 +++++++++++-------- ...TranslationWithCustomSerializerBaseline.cs | 2 +- .../Linq/CosmosLinqJsonConverterTests.cs | 1 + 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index 0f43a1998f..7baad5b975 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -14,13 +14,9 @@ internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer { private readonly CosmosSerializer CustomCosmosSerializer; - public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer, CosmosPropertyNamingPolicy propertyNamingPolicy) + public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer) { - this.CustomCosmosSerializer = customCosmosSerializer - ?? new CosmosJsonDotNetSerializer(new CosmosSerializationOptions() - { - PropertyNamingPolicy = propertyNamingPolicy - }); + this.CustomCosmosSerializer = customCosmosSerializer; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -46,7 +42,8 @@ public string SerializeMemberName(MemberInfo memberInfo) ? jsonPropertyNameAttribute.Name : memberInfo.Name; - memberName = this.SerializeWithCustomSerializer(memberName); + //memberName = this.SerializeWithCustomSerializer(memberName); //This doesnt work - too many quotes + memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(new CosmosLinqSerializerOptions(), memberName); return memberName; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index e9603ec3b8..4cc6b8040e 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -81,18 +81,24 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.collectionStack = new List(); this.CurrentQuery = new QueryUnderConstruction(this.GetGenFreshParameterFunc()); this.subqueryBindingStack = new Stack(); - this.LinqSerializerOptions = linqSerializerOptions; + this.LinqSerializerOptions = linqSerializerOptions ?? new CosmosLinqSerializerOptions(); this.Parameters = parameters; - this.MemberNames = new MemberNames(linqSerializerOptions); - - this.CosmosLinqSerializer = linqSerializerOptions != null - ? linqSerializerOptions.LinqSerializerType switch + + if (this.LinqSerializerOptions.LinqSerializerType == LinqSerializerType.CustomCosmosSerializer) + { + if (this.LinqSerializerOptions.CustomCosmosSerializer == null) { - LinqSerializerType.Default => new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy), - LinqSerializerType.CustomCosmosSerializer => new DotNetCosmosLinqSerializer(linqSerializerOptions.CustomCosmosSerializer, linqSerializerOptions.PropertyNamingPolicy), - _ => throw new InvalidOperationException($"Unknown type: {linqSerializerOptions.LinqSerializerType.GetType()}") + throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer"); } - : new DefaultCosmosLinqSerializer(CosmosPropertyNamingPolicy.Default); + + this.CosmosLinqSerializer = new DotNetCosmosLinqSerializer(this.LinqSerializerOptions.CustomCosmosSerializer); + this.MemberNames = new MemberNames(new CosmosLinqSerializerOptions()); + } + else + { + this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(this.LinqSerializerOptions.PropertyNamingPolicy); + this.MemberNames = new MemberNames(this.LinqSerializerOptions); + } } public Expression LookupSubstitution(ParameterExpression parameter) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml index 12d6be1d77..4c734d63f2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml @@ -8,7 +8,7 @@ +WHERE (root["NumberValueDotNet"] = 1)]]> "{\"NumberValueDotNet\": 1, \"StringValueDotNet\": \"1\", \"id\": \"1-False\", \"Pk\": \"Test\"}", "{\"NumberValueDotNet\": 2, \"StringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> - + @@ -75,7 +78,7 @@ FROM root]]> 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["NumberValueDotNet"] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root = {"NumberValueDotNet": root["NumberValueDotNet"], "StringValueDotNet": root["StringValueDotNet"]})]]> - + @@ -125,7 +128,7 @@ WHERE (root = {""NumberValueDotNet"": root["\"NumberValueDotNet\""], ""StringVal +ORDER BY root["NumberValueDotNet"] ASC]]> ]]]> @@ -152,7 +155,7 @@ ORDER BY root["\"NumberValueDotNet\""] ASC]]> +WHERE (root["NumberValueDotNet"] = 1)]]> "{\"NumberValueDotNet\": 1, \"StringValueDotNet\": \"1\", \"id\": \"1-False\", \"Pk\": \"Test\"}", "{\"NumberValueDotNet\": 2, \"StringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> - + @@ -219,7 +225,7 @@ FROM root]]> 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) +SELECT VALUE ((root["NumberValueDotNet"] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> +WHERE (root = {"NumberValueDotNet": root["NumberValueDotNet"], "StringValueDotNet": root["StringValueDotNet"]})]]> - + @@ -269,7 +275,7 @@ WHERE (root = {""NumberValueDotNet"": root["\"NumberValueDotNet\""], ""StringVal +ORDER BY root["NumberValueDotNet"] ASC]]> ]]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 7663c257d7..609e0d964a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -92,7 +92,7 @@ public void TestMemberInitializerDotNet() new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData), - new LinqTestInput("Mutistep query, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(x => x).OrderBy(x => x).Select(f => f.NumericField).Take(5), skipVerification : true, inputData: insertedData), + new LinqTestInput("OrderBy query, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(x => x).OrderBy(x => x.NumericField).Take(5), skipVerification : true, inputData: insertedData), }; inputs.AddRange(camelCaseSettingInputs); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 0bba951949..c5b415080c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -201,6 +201,7 @@ public void TestSystemTextJsonExtensionDataQuery() { CosmosLinqSerializerOptions dotNetOptions = new() { + CustomCosmosSerializer = new TestCustomJsonSerializer(), LinqSerializerType = LinqSerializerType.CustomCosmosSerializer }; From 0c2bc462a17a2aabb2f5b25e5517525ec14a583f Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Thu, 16 Nov 2023 15:10:10 -0800 Subject: [PATCH 21/48] Aggregate tests and new serializer member --- .../src/Linq/DotNetCosmosLinqSerializer.cs | 12 +- .../src/Linq/TranslationContext.cs | 1 + .../src/Serializer/CosmosSerializer.cs | 10 + ...stAggregateQueriesWithCustomSerializer.xml | 110 +++++++++++ ...inqAggregateCustomSerializationBaseline.cs | 173 ++++++++++++++++++ .../Linq/LinqTestsCommon.cs | 55 ++++++ ...TranslationWithCustomSerializerBaseline.cs | 40 ---- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 3 + .../Contracts/DotNetSDKAPI.json | 5 + .../Linq/CosmosLinqJsonConverterTests.cs | 17 +- 10 files changed, 373 insertions(+), 53 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs index 7baad5b975..b4551a7877 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos.Linq using System.IO; using System.Linq.Expressions; using System.Reflection; - using System.Text.Json.Serialization; internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer { @@ -36,16 +35,7 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) public string SerializeMemberName(MemberInfo memberInfo) { - JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); - - string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name) - ? jsonPropertyNameAttribute.Name - : memberInfo.Name; - - //memberName = this.SerializeWithCustomSerializer(memberName); //This doesnt work - too many quotes - memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(new CosmosLinqSerializerOptions(), memberName); - - return memberName; + return this.CustomCosmosSerializer.SerializeLinqMemberName(memberInfo); } private string SerializeWithCustomSerializer(object value) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 4cc6b8040e..79c2a22d76 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -88,6 +88,7 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi { if (this.LinqSerializerOptions.CustomCosmosSerializer == null) { + //Todo mayapainter: make sure GetMemberName implemented throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer"); } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index 3632bd6740..a6627be2ae 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -4,7 +4,9 @@ namespace Microsoft.Azure.Cosmos { + using System; using System.IO; + using System.Reflection; /// /// This is an interface to allow a custom serializer to be used by the CosmosClient @@ -29,5 +31,13 @@ public abstract class CosmosSerializer /// Any type passed to . /// A readable Stream containing JSON of the serialized object. public abstract Stream ToStream(T input); + + /// //mayapainter todo + /// Any MemberInfo used in the query. + /// A serialized representation of the member + public virtual string SerializeLinqMemberName(MemberInfo memberInfo) + { + throw new NotImplementedException($"{nameof(CosmosSerializer)}.{nameof(SerializeLinqMemberName)})"); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml new file mode 100644 index 0000000000..c94d1c91de --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml @@ -0,0 +1,110 @@ + + + + + doc.NumericField), Object)]]> + + + + + + + + + doc.NumericField), Object)]]> + + + + + + + + + + + + 0) +FROM ( + SELECT VALUE root + FROM root) AS v0 +]]> + + + + + + Filter -> Select -> Any, custom serializer: True]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Any(), Object)]]> + + + 0) +FROM ( + SELECT VALUE m0 + FROM root + JOIN m0 IN root["ArrayValuesDotNet"] + WHERE ((m0 % 3) = 0)) AS v0 +]]> + + + + + + + doc.NumericField), Object)]]> + + + + + + + + + doc.NumericField), Object)]]> + + + + + + + + + + + + 0) +FROM ( + SELECT VALUE root + FROM root) AS v0 +]]> + + + + + + Filter -> Select -> Any, custom serializer: False]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Any(), Object)]]> + + + 0) +FROM ( + SELECT VALUE m0 + FROM root + JOIN m0 IN root["ArrayField"] + WHERE ((m0 % 3) = 0)) AS v0 +]]> + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs new file mode 100644 index 0000000000..499dde8ba3 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs @@ -0,0 +1,173 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Linq; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests; + using Microsoft.Azure.Cosmos.Services.Management.Tests.BaselineTest; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json.Linq; + using static Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests.LinqAggregateFunctionBaselineTests; + + [Microsoft.Azure.Cosmos.SDK.EmulatorTests.TestClass] + public class LinqAggregateCustomSerializationBaseline : BaselineTests + { + private static CosmosSerializer customCosmosSerializer; + private static CosmosClient client; + private static Cosmos.Database testDb; + private static Container testContainer; + private static IQueryable lastExecutedScalarQuery; + + [ClassInitialize] + public async static Task Initialize(TestContext textContext) + { + customCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + client = TestCommon.CreateCosmosClient((cosmosClientBuilder) + => cosmosClientBuilder.WithCustomSerializer(customCosmosSerializer)); + + // Set a callback to get the handle of the last executed query to do the verification + // This is neede because aggregate queries return type is a scalar so it can't be used + // to verify the translated LINQ directly as other queries type. + client.DocumentClient.OnExecuteScalarQueryCallback = q => lastExecutedScalarQuery = q; + + string dbName = $"{nameof(LinqAggregateCustomSerializationBaseline)}-{Guid.NewGuid().ToString("N")}"; + testDb = await client.CreateDatabaseAsync(dbName); + + testContainer = testDb.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")).Result; + } + + [ClassCleanup] + public async static Task CleanUp() + { + if (testDb != null) + { + await testDb.DeleteStreamAsync(); + } + + client?.Dispose(); + } + + [TestMethod] + [Owner("mayapainter")] + public void TestAggregateQueriesWithCustomSerializer() + { + static DataObjectDotNet createDataObj(int index, bool camelCase) + { + DataObjectDotNet obj = new DataObjectDotNet + { + NumericField = index, + StringField = index.ToString(), + ArrayField = new int[] { 1, 2, 3, 4, 5 }, + id = Guid.NewGuid().ToString(), + Pk = "Test" + }; + return obj; + } + + CosmosLinqSerializerOptions linqSerializerOptions = new CosmosLinqSerializerOptions() + { + CustomCosmosSerializer = customCosmosSerializer, + LinqSerializerType = LinqSerializerType.CustomCosmosSerializer + }; + Func> getQueryCustomSerializer = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, linqSerializerOptions); + Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, new CosmosLinqSerializerOptions()); + List>> getQueryList = new List>> { getQueryCustomSerializer, getQueryDefault }; + + List inputs = new List(); + + foreach (bool isCustomSerializer in new List{ true, false }) + { + Func> getQuery = getQueryList[isCustomSerializer ? 0 : 1]; + + inputs.Add(new LinqAggregateInput( + "Avg, custom serializer: " + isCustomSerializer, b => getQuery(b) + .Average(doc => doc.NumericField))); + + inputs.Add(new LinqAggregateInput( + "Sum, custom serializer: " + isCustomSerializer, b => getQuery(b) + .Sum(doc => doc.NumericField))); + + inputs.Add(new LinqAggregateInput( + "Any, custom serializer: " + isCustomSerializer, b => getQuery(b) + .Any())); + + inputs.Add(new LinqAggregateInput( + "Select many -> Filter -> Select -> Any, custom serializer: " + isCustomSerializer, b => getQuery(b) + .SelectMany(doc => doc.ArrayField.Where(m => m % 3 == 0).Select(m => m)).Any())); + } + + this.ExecuteTestSuite(inputs); + } + + public override LinqAggregateOutput ExecuteTest(LinqAggregateInput input) + { + lastExecutedScalarQuery = null; + Func compiledQuery = input.expression.Compile(); + + string errorMessage = null; + string query = string.Empty; + try + { + object queryResult; + try + { + queryResult = compiledQuery(true); + } + finally + { + Assert.IsNotNull(lastExecutedScalarQuery, "lastExecutedScalarQuery is not set"); + + query = JObject + .Parse(lastExecutedScalarQuery.ToString()) + .GetValue("query", StringComparison.Ordinal) + .ToString(); + } + } + catch (Exception e) + { + errorMessage = LinqTestsCommon.BuildExceptionMessageForTest(e); + } + + return new LinqAggregateOutput(query, errorMessage); + } + + private class DataObjectDotNet : LinqTestObject + { + [JsonPropertyName("NumberValueDotNet")] + public double NumericField { get; set; } + + [JsonPropertyName("StringValueDotNet")] + public string StringField { get; set; } + + [JsonPropertyName("ArrayValuesDotNet")] + public int[] ArrayField { get; set; } + + public string id { get; set; } + + public string Pk { get; set; } + + public DataObjectDotNet() { } + + public DataObjectDotNet(double numericField, string stringField, int[] arrayField, string id, string pk) + { + this.NumericField = numericField; + this.StringField = stringField; + this.ArrayField = arrayField; + this.id = id; + this.Pk = pk; + } + + public override string ToString() + { + return $"{{NumericField:{this.NumericField},StringField:{this.StringField},id:{this.id},Pk:{this.Pk}}}"; + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index efb5fb477e..55f8637f14 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -10,12 +10,17 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; + using System.IO; using System.Linq; using System.Linq.Expressions; + using System.Reflection; using System.Runtime.CompilerServices; using System.Text; + using System.Text.Json.Serialization; + using System.Text.Json; using System.Text.RegularExpressions; using System.Xml; + using global::Azure.Core.Serialization; using Microsoft.Azure.Cosmos.Services.Management.Tests.BaselineTest; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -835,4 +840,54 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } } + + public class SystemTextJsonSerializer : CosmosSerializer + { + private readonly JsonObjectSerializer systemTextJsonSerializer; + + public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions) + { + this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); + } + + public override T FromStream(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)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + } + } + + public override Stream ToStream(T input) + { + MemoryStream streamPayload = new MemoryStream(); + this.systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default); + streamPayload.Position = 0; + return streamPayload; + } + + public override string SerializeLinqMemberName(MemberInfo memberInfo) + { + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + + string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name) + ? jsonPropertyNameAttribute.Name + : memberInfo.Name; + + return memberName; + } + } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 609e0d964a..e4e99ef905 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using System.Text.Json.Serialization; using System.Threading.Tasks; using BaselineTest; - using global::Azure.Core.Serialization; using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -313,45 +312,6 @@ private async Task GetInsertedData() return insertedData; } - class SystemTextJsonSerializer : CosmosSerializer - { - private readonly JsonObjectSerializer systemTextJsonSerializer; - - public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions) - { - this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); - } - - public override T FromStream(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)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); - } - } - - public override Stream ToStream(T input) - { - MemoryStream streamPayload = new MemoryStream(); - this.systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default); - streamPayload.Position = 0; - return streamPayload; - } - } - private class DataObjectDotNet : LinqTestObject { [JsonPropertyName("NumberValueDotNet")] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 5c50aa155e..7c496a4e35 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -121,6 +121,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 4eb50c0de0..4ceba1ab83 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3633,6 +3633,11 @@ "Attributes": [], "MethodInfo": "System.IO.Stream ToStream[T](T);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "T FromStream[T](System.IO.Stream)": { "Type": "Method", "Attributes": [], diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index c5b415080c..37654a2726 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -10,6 +10,8 @@ namespace Microsoft.Azure.Cosmos.Linq using System.IO; using System.Linq; using System.Linq.Expressions; + using System.Reflection; + using System.Text.Json.Serialization; using global::Azure.Core.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -157,13 +159,13 @@ class TestEnumDocument class TestEnumNewtonsoftDocument { - [JsonConverter(typeof(StringEnumConverter))] + [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))] public TestEnum Value { get; set; } } class TestDocument { - [JsonConverter(typeof(DateJsonConverter))] + [Newtonsoft.Json.JsonConverter(typeof(DateJsonConverter))] public DateTime StartDate { get; set; } } @@ -267,6 +269,17 @@ public override Stream ToStream(T input) stream.Position = 0; return stream; } + + public override string SerializeLinqMemberName(MemberInfo memberInfo) + { + JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + + string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name) + ? jsonPropertyNameAttribute.Name + : memberInfo.Name; + + return memberName; + } } } } From e45c2a29516d0a864127019eac93335aba1589ec Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Fri, 17 Nov 2023 10:35:38 -0800 Subject: [PATCH 22/48] More tests and cleanup --- .../src/Linq/TranslationContext.cs | 2 +- .../Resource/Container/ContainerCore.Items.cs | 19 ++- .../src/Serializer/CosmosSerializer.cs | 5 +- ...stAggregateQueriesWithCustomSerializer.xml | 84 ++++++---- ...erBaseline.TestMemberInitializerDotNet.xml | 150 ++---------------- ...rInitializerDotNetDataMemberSerializer.xml | 16 +- ...tializerNewtonsoftDataMemberSerializer.xml | 16 +- ...rInitializerNewtonsoftDotNetSerializer.xml | 16 +- ...inqAggregateCustomSerializationBaseline.cs | 30 ++-- ...TranslationWithCustomSerializerBaseline.cs | 103 ++++++------ 10 files changed, 170 insertions(+), 271 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 79c2a22d76..daf6d7e82f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -88,7 +88,7 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi { if (this.LinqSerializerOptions.CustomCosmosSerializer == null) { - //Todo mayapainter: make sure GetMemberName implemented + //Todo mayapainter: do we want to throw error here if SerializeLinqMemberName not implemented or let it throw a notimplementedexception from SerializeLinqMemberName? throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer"); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 0072c468f6..6b7c9f25c3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -387,15 +387,20 @@ public override IOrderedQueryable GetItemLinqQueryable( { requestOptions ??= new QueryRequestOptions(); - if (linqSerializerOptions == null && this.ClientContext.ClientOptions != null) + if (this.ClientContext.ClientOptions != null) { - linqSerializerOptions = new CosmosLinqSerializerOptions + if (linqSerializerOptions == null) { - PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions != null - ? this.ClientContext.ClientOptions.SerializerOptions.PropertyNamingPolicy - : CosmosPropertyNamingPolicy.Default, - CustomCosmosSerializer = this.ClientContext.ClientOptions.Serializer - }; + linqSerializerOptions = new CosmosLinqSerializerOptions + { + PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions != null + ? this.ClientContext.ClientOptions.SerializerOptions.PropertyNamingPolicy + : CosmosPropertyNamingPolicy.Default, + }; + } + + linqSerializerOptions.CustomCosmosSerializer = this.ClientContext.ClientOptions.Serializer; + } return new CosmosLinqQuery( diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index a6627be2ae..31b58b7bf8 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -32,7 +32,10 @@ public abstract class CosmosSerializer /// A readable Stream containing JSON of the serialized object. public abstract Stream ToStream(T input); - /// //mayapainter todo + /// + /// Convert a MemberInfo to a string for use in LINQ query translation. + /// This must be implemented when using a custom serializer for LINQ queries. + /// /// Any MemberInfo used in the query. /// A serialized representation of the member public virtual string SerializeLinqMemberName(MemberInfo memberInfo) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml index c94d1c91de..1a7337ba81 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregateQueriesWithCustomSerializer.xml @@ -1,7 +1,7 @@  - + doc.NumericField), Object)]]> @@ -12,7 +12,7 @@ FROM root]]> - + doc.NumericField), Object)]]> @@ -23,39 +23,47 @@ FROM root]]> - - + Filter -> Select -> Average, Custom serializer: True]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Average(), Object)]]> 0) -FROM ( - SELECT VALUE root - FROM root) AS v0 -]]> - +SELECT VALUE AVG(m0) +FROM root +JOIN m0 IN root["ArrayValuesDotNet"] +WHERE ((m0 % 3) = 0)]]> - Filter -> Select -> Any, custom serializer: True]]> - doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Any(), Object)]]> + Skip -> Count, Custom serializer: True]]> + f.NumericField).Skip(2).Count(), Object)]]> 0) +SELECT VALUE COUNT(1) FROM ( - SELECT VALUE m0 + SELECT VALUE root["NumberValueDotNet"] FROM root - JOIN m0 IN root["ArrayValuesDotNet"] - WHERE ((m0 % 3) = 0)) AS v0 + OFFSET 2 LIMIT 2147483647) AS r0 ]]> - + + + + + + Min w/ mapping]]> + doc.NumericField).Min(num => num), Object)]]> + + + - + doc.NumericField), Object)]]> @@ -66,7 +74,7 @@ FROM root]]> - + doc.NumericField), Object)]]> @@ -77,34 +85,42 @@ FROM root]]> - - + Filter -> Select -> Average, Custom serializer: False]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Average(), Object)]]> 0) -FROM ( - SELECT VALUE root - FROM root) AS v0 -]]> - +SELECT VALUE AVG(m0) +FROM root +JOIN m0 IN root["ArrayField"] +WHERE ((m0 % 3) = 0)]]> - Filter -> Select -> Any, custom serializer: False]]> - doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Any(), Object)]]> + Skip -> Count, Custom serializer: False]]> + f.NumericField).Skip(2).Count(), Object)]]> 0) +SELECT VALUE COUNT(1) FROM ( - SELECT VALUE m0 + SELECT VALUE root["NumericField"] FROM root - JOIN m0 IN root["ArrayField"] - WHERE ((m0 % 3) = 0)) AS v0 + OFFSET 2 LIMIT 2147483647) AS r0 ]]> - + + + + + + Min w/ mapping]]> + doc.NumericField).Min(num => num), Object)]]> + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml index 4c734d63f2..9f7044fac1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml @@ -1,7 +1,7 @@  - + (doc.NumericField == 1))]]> @@ -25,7 +25,7 @@ WHERE (root["NumberValueDotNet"] = 1)]]> - + (doc == new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> @@ -46,7 +46,7 @@ WHERE (root = {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk - + new DataObjectDotNet() {NumericField = 1, StringField = "1"})]]> @@ -73,7 +73,7 @@ FROM root]]> - + IIF((doc.NumericField > 1), new DataObjectDotNet() {NumericField = 1, StringField = "1"}, new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> @@ -100,7 +100,7 @@ FROM root]]> - + (doc == new DataObjectDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> @@ -121,7 +121,7 @@ WHERE (root = {"NumberValueDotNet": root["NumberValueDotNet"], "StringValueDotNe - + x).OrderBy(x => x.NumericField).Take(5)]]> @@ -148,57 +148,12 @@ ORDER BY root["NumberValueDotNet"] ASC]]> - - (doc.NumericField == 1))]]> - - - - - - - - - - - (doc == new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> - - - - - - - - - - - new DataObjectDotNet() {NumericField = 1, StringField = "1"})]]> + + IIF((c.NumericField > 1), "true", "false"))]]> 1) ? "true" : "false") FROM root]]> "{\"NumberValueDotNet\": 2, \"StringValueDotNet\": \"2\", \"id\": \"2-False\", \"Pk\": \"Test\"}" ]]]> - - - - - - IIF((doc.NumericField > 1), new DataObjectDotNet() {NumericField = 1, StringField = "1"}, new DataObjectDotNet() {NumericField = 1, StringField = "1"}))]]> - - - 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) -FROM root]]> - - - - - - - - (doc == new DataObjectDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - - - - - - - - - - - x).OrderBy(x => x.NumericField).Take(5)]]> - - - - - diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml index a08a3bc493..df12581f67 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml @@ -23,7 +23,7 @@ WHERE (root["numericFieldDataMember"] = 1)]]> - (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> - new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectDotNetDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> - new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}, new DataObjectDotNetDataMember() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectDotNetDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectDotNetDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> - new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectNewtonsoftDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> - new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMemberSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDataMember() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectNewtonsoftDataMemberSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectNewtonsoftDataMember() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> - (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"})]]> + new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"})]]> - IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNetSerializer() {NumericField = 1, StringField = "1"}))]]> + IIF((doc.NumericField > 1), new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}, new DataObjectNewtonsoftDotNet() {NumericField = 1, StringField = "1"}))]]> - (doc == new DataObjectNewtonsoftDotNetSerializer() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> + (doc == new DataObjectNewtonsoftDotNet() {NumericField = doc.NumericField, StringField = doc.StringField})).Select(b => "A")]]> > getQueryCustomSerializer = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, linqSerializerOptions); - Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, new CosmosLinqSerializerOptions()); - List>> getQueryList = new List>> { getQueryCustomSerializer, getQueryDefault }; + + List>> getQueryList = new List>> + { + LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, linqSerializerOptions), + LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, new CosmosLinqSerializerOptions()) + }; List inputs = new List(); - foreach (bool isCustomSerializer in new List{ true, false }) + foreach (bool applyCustomSerializer in new List{ true, false }) { - Func> getQuery = getQueryList[isCustomSerializer ? 0 : 1]; + Func> getQuery = getQueryList[applyCustomSerializer ? 0 : 1]; inputs.Add(new LinqAggregateInput( - "Avg, custom serializer: " + isCustomSerializer, b => getQuery(b) + "Avg, Custom serializer: " + applyCustomSerializer, b => getQuery(b) .Average(doc => doc.NumericField))); inputs.Add(new LinqAggregateInput( - "Sum, custom serializer: " + isCustomSerializer, b => getQuery(b) + "Sum, Custom serializer: " + applyCustomSerializer, b => getQuery(b) .Sum(doc => doc.NumericField))); inputs.Add(new LinqAggregateInput( - "Any, custom serializer: " + isCustomSerializer, b => getQuery(b) - .Any())); + "Select many -> Filter -> Select -> Average, Custom serializer: " + applyCustomSerializer, b => getQuery(b) + .SelectMany(doc => doc.ArrayField.Where(m => (m % 3) == 0).Select(m => m)).Average())); + + inputs.Add(new LinqAggregateInput( + "Select number -> Skip -> Count, Custom serializer: " + applyCustomSerializer, b => getQuery(b) + .Select(f => f.NumericField).Skip(2).Count())); inputs.Add(new LinqAggregateInput( - "Select many -> Filter -> Select -> Any, custom serializer: " + isCustomSerializer, b => getQuery(b) - .SelectMany(doc => doc.ArrayField.Where(m => m % 3 == 0).Select(m => m)).Any())); + "Select number -> Min w/ mapping", b => getQuery(b) + .Select(doc => doc.NumericField).Min(num => num))); } this.ExecuteTestSuite(inputs); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index e4e99ef905..95f9bceb5d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -72,30 +72,21 @@ public override LinqTestOutput ExecuteTest(LinqTestInput input) [TestMethod] public void TestMemberInitializerDotNet() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.CustomCosmosSerializer); + Func> getQuery; + (_, getQuery) = this.InsertDataAndGetQueryables(LinqSerializerType.CustomCosmosSerializer); string insertedData = this.GetInsertedData().Result; - List inputs = new List(); - foreach (bool useCamelCaseSerializer in new bool[] { true, false }) + List inputs = new List { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; - - List camelCaseSettingInputs = new List - { - // TODO (10/13/23): extend this and other tests cases as more LINQ features are added (GROUP BY, etc.) - new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData), - new LinqTestInput("OrderBy query, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(x => x).OrderBy(x => x.NumericField).Take(5), skipVerification : true, inputData: insertedData), - }; - - inputs.AddRange(camelCaseSettingInputs); - } + new LinqTestInput("Filter w/ constant value", b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with constant value", b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer", b => getQuery(b).Select(doc => new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference", b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNet() { NumericField = 1, StringField = "1" } : new DataObjectDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization", b => getQuery(b).Where(doc => doc == new DataObjectDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData), + new LinqTestInput("OrderBy query", b => getQuery(b).Select(x => x).OrderBy(x => x.NumericField).Take(5), skipVerification : true, inputData: insertedData), + new LinqTestInput("Conditional", b => getQuery(b).Select(c => c.NumericField > 1 ? "true" : "false"), skipVerification : true, inputData: insertedData), + }; this.ExecuteTestSuite(inputs); } @@ -161,24 +152,24 @@ public void TestMemberInitializerDataMember() [TestMethod] public void TestMemberInitializerNewtonsoftDotNetSerializer() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; List camelCaseSettingInputs = new List { new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDotNetSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNetSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDotNet() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDotNet() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; inputs.AddRange(camelCaseSettingInputs); @@ -190,24 +181,24 @@ public void TestMemberInitializerNewtonsoftDotNetSerializer() [TestMethod] public void TestMemberInitializerNewtonsoftDataMemberSerializer() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; List camelCaseSettingInputs = new List { new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMemberSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" } : new DataObjectNewtonsoftDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectNewtonsoftDataMember() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) }; inputs.AddRange(camelCaseSettingInputs); @@ -219,25 +210,25 @@ public void TestMemberInitializerNewtonsoftDataMemberSerializer() [TestMethod] public void TestMemberInitializerDotNetDataMemberSerializer() { - Func> getQueryCamelCase; - Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + Func> getQueryCamelCase; + Func> getQueryDefault; + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) { - Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; + Func> getQuery = useCamelCaseSerializer ? getQueryCamelCase : getQueryDefault; List camelCaseSettingInputs = new List - { + { new LinqTestInput("Filter w/ constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc.NumericField == 1), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" } : new DataObjectDotNetDataMemberSerializer() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), - new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMemberSerializer() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) - }; + new LinqTestInput("Filter w/ DataObject initializer with constant value, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Select w/ DataObject initializer, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Deeper than top level reference, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Select(doc => doc.NumericField > 1 ? new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" } : new DataObjectDotNetDataMember() { NumericField = 1, StringField = "1" }), skipVerification : true, inputData: insertedData), + new LinqTestInput("Filter w/ DataObject initializer with member initialization, camelcase = " + useCamelCaseSerializer, b => getQuery(b).Where(doc => doc == new DataObjectDotNetDataMember() { NumericField = doc.NumericField, StringField = doc.StringField }).Select(b => "A"), skipVerification : true, inputData: insertedData) + }; inputs.AddRange(camelCaseSettingInputs); } @@ -259,13 +250,11 @@ static T createDataObj(int index, bool camelCase) CosmosLinqSerializerOptions linqSerializerOptionsCamelCase = new CosmosLinqSerializerOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, - CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()), LinqSerializerType = linqSerializerType }; CosmosLinqSerializerOptions linqSerializerOptionsDefault = new CosmosLinqSerializerOptions { - CustomCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()), LinqSerializerType = linqSerializerType }; @@ -407,7 +396,7 @@ public override string ToString() } } - private class DataObjectNewtonsoftDotNetSerializer : LinqTestObject + private class DataObjectNewtonsoftDotNet : LinqTestObject { [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] [JsonPropertyName("numberValueDotNet")] @@ -421,9 +410,9 @@ private class DataObjectNewtonsoftDotNetSerializer : LinqTestObject public string Pk { get; set; } - public DataObjectNewtonsoftDotNetSerializer() { } + public DataObjectNewtonsoftDotNet() { } - public DataObjectNewtonsoftDotNetSerializer(double numericField, string stringField, string id, string pk) + public DataObjectNewtonsoftDotNet(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; @@ -438,7 +427,7 @@ public override string ToString() } [DataContract] - private class DataObjectNewtonsoftDataMemberSerializer : LinqTestObject + private class DataObjectNewtonsoftDataMember : LinqTestObject { [Newtonsoft.Json.JsonProperty(PropertyName = "NumberValueNewtonsoft")] [DataMember(Name = "NumericFieldDataMember")] @@ -452,9 +441,9 @@ private class DataObjectNewtonsoftDataMemberSerializer : LinqTestObject public string Pk { get; set; } - public DataObjectNewtonsoftDataMemberSerializer() { } + public DataObjectNewtonsoftDataMember() { } - public DataObjectNewtonsoftDataMemberSerializer(double numericField, string stringField, string id, string pk) + public DataObjectNewtonsoftDataMember(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; @@ -469,7 +458,7 @@ public override string ToString() } [DataContract] - private class DataObjectDotNetDataMemberSerializer : LinqTestObject + private class DataObjectDotNetDataMember : LinqTestObject { [DataMember(Name = "NumericFieldDataMember")] [JsonPropertyName("numberValueDotNet")] @@ -483,9 +472,9 @@ private class DataObjectDotNetDataMemberSerializer : LinqTestObject public string Pk { get; set; } - public DataObjectDotNetDataMemberSerializer() { } + public DataObjectDotNetDataMember() { } - public DataObjectDotNetDataMemberSerializer(double numericField, string stringField, string id, string pk) + public DataObjectDotNetDataMember(double numericField, string stringField, string id, string pk) { this.NumericField = numericField; this.StringField = stringField; From be90c91129da956f018aaa74d5b1f2e37e0791d5 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Fri, 17 Nov 2023 14:32:12 -0800 Subject: [PATCH 23/48] PR comments --- ...nqSerializer.cs => CustomCosmosLinqSerializer.cs} | 4 ++-- .../src/Linq/LinqSerializerType.cs | 7 +++++-- .../src/Linq/TranslationContext.cs | 12 +++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) rename Microsoft.Azure.Cosmos/src/Linq/{DotNetCosmosLinqSerializer.cs => CustomCosmosLinqSerializer.cs} (93%) diff --git a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs similarity index 93% rename from Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs rename to Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index b4551a7877..f4336f4098 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DotNetCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -9,11 +9,11 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq.Expressions; using System.Reflection; - internal class DotNetCosmosLinqSerializer : ICosmosLinqSerializer + internal class CustomCosmosLinqSerializer : ICosmosLinqSerializer { private readonly CosmosSerializer CustomCosmosSerializer; - public DotNetCosmosLinqSerializer(CosmosSerializer customCosmosSerializer) + public CustomCosmosLinqSerializer(CosmosSerializer customCosmosSerializer) { this.CustomCosmosSerializer = customCosmosSerializer; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 10b17d0937..0c50cacb1f 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -9,12 +9,15 @@ namespace Microsoft.Azure.Cosmos.Linq public enum LinqSerializerType { /// - /// Follows the exisiting serializer pattern. This honors Newtonsoft attributes, followed by DataContract attributes, but not System.Text.Json attributes. + /// Follows the exisiting serializer pattern. This honors Newtonsoft attributes, followed by DataContract attributes. This will ignore System.Text.Json attributes. /// Default, /// - /// Uses a custom CosmosSerializer, if provided. This will honor System.Text.Json attributes. + /// Uses the provided custom CosmosSerializer. + /// This requires: + /// 1. a to be provided on a client, and + /// 2. the custom CosmosSerializer implements the member function /// CustomCosmosSerializer, } diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index daf6d7e82f..b07c8fb478 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System; using System.Collections.Generic; using System.Linq.Expressions; + using System.Reflection; using Microsoft.Azure.Cosmos.SqlObjects; using static Microsoft.Azure.Cosmos.Linq.ExpressionToSql; using static Microsoft.Azure.Cosmos.Linq.FromParameterBindings; @@ -88,11 +89,16 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi { if (this.LinqSerializerOptions.CustomCosmosSerializer == null) { - //Todo mayapainter: do we want to throw error here if SerializeLinqMemberName not implemented or let it throw a notimplementedexception from SerializeLinqMemberName? throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer"); } - - this.CosmosLinqSerializer = new DotNetCosmosLinqSerializer(this.LinqSerializerOptions.CustomCosmosSerializer); + + MethodInfo methodInfo = this.LinqSerializerOptions.CustomCosmosSerializer.GetType().GetMethod("SerializeLinqMemberName"); + if (methodInfo.DeclaringType == typeof(CosmosSerializer)) + { + throw new InvalidOperationException($"Must implement SerializeLinqMemberName to use CustomCosmosSerializer for LINQ queries."); + } + + this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(this.LinqSerializerOptions.CustomCosmosSerializer); this.MemberNames = new MemberNames(new CosmosLinqSerializerOptions()); } else From 22e05ddb37ef7b7c8133cb62fb4c5c8d98c0bc93 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 28 Nov 2023 21:52:48 -0800 Subject: [PATCH 24/48] PR comments --- .../src/Linq/ExpressionToSQL.cs | 4 ++-- .../Resource/Container/ContainerCore.Items.cs | 12 +++--------- .../src/Serializer/CosmosSerializationUtil.cs | 17 +++++++++-------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index 172e6e2acb..f0f51ac8ab 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -502,8 +502,8 @@ private static SqlScalarExpression ApplyCustomConverters(Expression left, SqlLit memberType = memberType.NullableUnderlyingType(); } - bool requiresCustomSerializatior = context.CosmosLinqSerializer.RequiresCustomSerialization(memberExpression, memberType); - if (requiresCustomSerializatior) + bool requiresCustomSerialization = context.CosmosLinqSerializer.RequiresCustomSerialization(memberExpression, memberType); + if (requiresCustomSerialization) { object value = default(object); // Enum diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 6b7c9f25c3..4f7e6d31c0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -24,7 +24,6 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; - using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; using Microsoft.Azure.Cosmos.Tracing; @@ -389,15 +388,10 @@ public override IOrderedQueryable GetItemLinqQueryable( if (this.ClientContext.ClientOptions != null) { - if (linqSerializerOptions == null) + linqSerializerOptions ??= new CosmosLinqSerializerOptions { - linqSerializerOptions = new CosmosLinqSerializerOptions - { - PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions != null - ? this.ClientContext.ClientOptions.SerializerOptions.PropertyNamingPolicy - : CosmosPropertyNamingPolicy.Default, - }; - } + PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default + }; linqSerializerOptions.CustomCosmosSerializer = this.ClientContext.ClientOptions.Serializer; diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index 0f355d6298..6deee03846 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos { + using System; using Newtonsoft.Json.Serialization; internal static class CosmosSerializationUtil @@ -12,22 +13,22 @@ internal static class CosmosSerializationUtil internal static string GetStringWithPropertyNamingPolicy(CosmosLinqSerializerOptions options, string name) { - if (options?.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase) + if (options == null) { - return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); + return name; } - return name; + return GetStringWithPropertyNamingPolicy(options.PropertyNamingPolicy, name); } internal static string GetStringWithPropertyNamingPolicy(CosmosPropertyNamingPolicy namingPolicy, string name) { - if (namingPolicy == CosmosPropertyNamingPolicy.CamelCase) + return namingPolicy switch { - return CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false); - } - - return name; + CosmosPropertyNamingPolicy.CamelCase => CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false), + CosmosPropertyNamingPolicy.Default => name, + _ => throw new ArgumentException("Unsupported CosmosPropertyNamingPolicy value"), + }; } } } From 4b526026aa7ec3f8cd88340e9bb6e20b40ebec6f Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 29 Nov 2023 18:24:09 -0800 Subject: [PATCH 25/48] PR comments - internal options class --- .../src/Linq/CosmosLinqQuery.cs | 7 +- .../src/Linq/CosmosLinqQueryProvider.cs | 4 +- .../src/Linq/DocumentQueryEvaluator.cs | 4 +- .../src/Linq/ExpressionToSQL.cs | 3 +- .../src/Linq/SQLTranslator.cs | 7 +- .../src/Linq/TranslationContext.cs | 31 +++------ .../Resource/Container/ContainerCore.Items.cs | 8 +-- .../Serializer/CosmosLinqSerializerOptions.cs | 9 --- .../CosmosLinqSerializerOptionsInternal.cs | 68 +++++++++++++++++++ .../src/Serializer/CosmosSerializationUtil.cs | 2 +- ...erBaseline.TestMemberInitializerDotNet.xml | 35 +--------- ...TranslationWithCustomSerializerBaseline.cs | 7 +- .../Linq/CosmosLinqJsonConverterTests.cs | 63 ++++++----------- 13 files changed, 122 insertions(+), 126 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs index f8400229b2..240f76c316 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs @@ -14,6 +14,7 @@ namespace Microsoft.Azure.Cosmos.Linq using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Newtonsoft.Json; @@ -32,7 +33,7 @@ internal sealed class CosmosLinqQuery : IDocumentQuery, IOrderedQueryable< private readonly QueryRequestOptions cosmosQueryRequestOptions; private readonly bool allowSynchronousQueryExecution = false; private readonly string continuationToken; - private readonly CosmosLinqSerializerOptions linqSerializationOptions; + private readonly CosmosLinqSerializerOptionsInternal linqSerializationOptions; public CosmosLinqQuery( ContainerInternal container, @@ -42,7 +43,7 @@ public CosmosLinqQuery( QueryRequestOptions cosmosQueryRequestOptions, Expression expression, bool allowSynchronousQueryExecution, - CosmosLinqSerializerOptions linqSerializationOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializationOptions = null) { this.container = container ?? throw new ArgumentNullException(nameof(container)); this.responseFactory = responseFactory ?? throw new ArgumentNullException(nameof(responseFactory)); @@ -72,7 +73,7 @@ public CosmosLinqQuery( string continuationToken, QueryRequestOptions cosmosQueryRequestOptions, bool allowSynchronousQueryExecution, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) : this( container, responseFactory, diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs index 8ba71d7b98..68bc3b519a 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs @@ -24,7 +24,7 @@ internal sealed class CosmosLinqQueryProvider : IQueryProvider private readonly bool allowSynchronousQueryExecution; private readonly Action onExecuteScalarQueryCallback; private readonly string continuationToken; - private readonly CosmosLinqSerializerOptions linqSerializerOptions; + private readonly CosmosLinqSerializerOptionsInternal linqSerializerOptions; public CosmosLinqQueryProvider( ContainerInternal container, @@ -34,7 +34,7 @@ public CosmosLinqQueryProvider( QueryRequestOptions cosmosQueryRequestOptions, bool allowSynchronousQueryExecution, Action onExecuteScalarQueryCallback = null, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { this.container = container; this.responseFactory = responseFactory; diff --git a/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs b/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs index c81f5a9645..da25188bc2 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DocumentQueryEvaluator.cs @@ -17,7 +17,7 @@ internal static class DocumentQueryEvaluator public static SqlQuerySpec Evaluate( Expression expression, - CosmosLinqSerializerOptions linqSerializerOptions = null, + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null, IDictionary parameters = null) { switch (expression.NodeType) @@ -76,7 +76,7 @@ private static SqlQuerySpec HandleEmptyQuery(ConstantExpression expression) private static SqlQuerySpec HandleMethodCallExpression( MethodCallExpression expression, IDictionary parameters, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { if (DocumentQueryEvaluator.IsTransformExpression(expression)) { diff --git a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs index f0f51ac8ab..6be1e82e9c 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs @@ -14,6 +14,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq.Expressions; using System.Reflection; using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Spatial; using Microsoft.Azure.Cosmos.SqlObjects; using Microsoft.Azure.Documents; @@ -87,7 +88,7 @@ public static class LinqMethods public static SqlQuery TranslateQuery( Expression inputExpression, IDictionary parameters, - CosmosLinqSerializerOptions linqSerializerOptions) + CosmosLinqSerializerOptionsInternal linqSerializerOptions) { TranslationContext context = new TranslationContext(linqSerializerOptions, parameters); ExpressionToSql.Translate(inputExpression, context); // ignore result here diff --git a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs index c0bd6d38a0..8491d9cf0c 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/SQLTranslator.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Collections.Generic; using System.Linq.Expressions; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.SqlObjects; /// @@ -21,7 +22,7 @@ internal static class SqlTranslator /// A string describing the expression translation. internal static string TranslateExpression( Expression inputExpression, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { TranslationContext context = new TranslationContext(linqSerializerOptions); @@ -32,7 +33,7 @@ internal static string TranslateExpression( internal static string TranslateExpressionOld( Expression inputExpression, - CosmosLinqSerializerOptions linqSerializerOptions = null) + CosmosLinqSerializerOptionsInternal linqSerializerOptions = null) { TranslationContext context = new TranslationContext(linqSerializerOptions); @@ -43,7 +44,7 @@ internal static string TranslateExpressionOld( internal static SqlQuerySpec TranslateQuery( Expression inputExpression, - CosmosLinqSerializerOptions linqSerializerOptions, + CosmosLinqSerializerOptionsInternal linqSerializerOptions, IDictionary parameters) { inputExpression = ConstantEvaluator.PartialEval(inputExpression); diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index b07c8fb478..6ea8194460 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System; using System.Collections.Generic; using System.Linq.Expressions; - using System.Reflection; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.SqlObjects; using static Microsoft.Azure.Cosmos.Linq.ExpressionToSql; using static Microsoft.Azure.Cosmos.Linq.FromParameterBindings; @@ -27,11 +27,6 @@ internal sealed class TranslationContext /// public readonly ICosmosLinqSerializer CosmosLinqSerializer; - /// - /// User-provided LINQ serializer options - /// - public CosmosLinqSerializerOptions LinqSerializerOptions; - /// /// Set of parameters in scope at any point; used to generate fresh parameter names if necessary. /// @@ -73,7 +68,7 @@ internal sealed class TranslationContext /// private Stack subqueryBindingStack; - public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDictionary parameters = null) + public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal, IDictionary parameters = null) { this.InScope = new HashSet(); this.substitutions = new ParameterSubstitution(); @@ -82,29 +77,19 @@ public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDi this.collectionStack = new List(); this.CurrentQuery = new QueryUnderConstruction(this.GetGenFreshParameterFunc()); this.subqueryBindingStack = new Stack(); - this.LinqSerializerOptions = linqSerializerOptions ?? new CosmosLinqSerializerOptions(); this.Parameters = parameters; - if (this.LinqSerializerOptions.LinqSerializerType == LinqSerializerType.CustomCosmosSerializer) + if (linqSerializerOptionsInternal?.CustomCosmosSerializer != null) { - if (this.LinqSerializerOptions.CustomCosmosSerializer == null) - { - throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer"); - } - - MethodInfo methodInfo = this.LinqSerializerOptions.CustomCosmosSerializer.GetType().GetMethod("SerializeLinqMemberName"); - if (methodInfo.DeclaringType == typeof(CosmosSerializer)) - { - throw new InvalidOperationException($"Must implement SerializeLinqMemberName to use CustomCosmosSerializer for LINQ queries."); - } - - this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(this.LinqSerializerOptions.CustomCosmosSerializer); + this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(linqSerializerOptionsInternal.CustomCosmosSerializer); this.MemberNames = new MemberNames(new CosmosLinqSerializerOptions()); } else { - this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(this.LinqSerializerOptions.PropertyNamingPolicy); - this.MemberNames = new MemberNames(this.LinqSerializerOptions); + CosmosLinqSerializerOptions linqSerializerOptions = linqSerializerOptionsInternal?.CosmosLinqSerializerOptions ?? new CosmosLinqSerializerOptions(); + + this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy); + this.MemberNames = new MemberNames(linqSerializerOptions); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs index 4f7e6d31c0..2397ab437a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.Items.cs @@ -26,6 +26,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.ReadFeed; using Microsoft.Azure.Cosmos.ReadFeed.Pagination; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -392,11 +393,10 @@ public override IOrderedQueryable GetItemLinqQueryable( { PropertyNamingPolicy = this.ClientContext.ClientOptions.SerializerOptions?.PropertyNamingPolicy ?? CosmosPropertyNamingPolicy.Default }; - - linqSerializerOptions.CustomCosmosSerializer = this.ClientContext.ClientOptions.Serializer; - } + CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal = CosmosLinqSerializerOptionsInternal.Create(linqSerializerOptions, this.ClientContext.ClientOptions.Serializer); + return new CosmosLinqQuery( this, this.ClientContext.ResponseFactory, @@ -404,7 +404,7 @@ public override IOrderedQueryable GetItemLinqQueryable( continuationToken, requestOptions, allowSynchronousQueryExecution, - linqSerializerOptions); + linqSerializerOptionsInternal); } public override FeedIterator GetItemQueryIterator( diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index 3541db96bf..ec8f313d8c 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -38,14 +38,5 @@ public CosmosLinqSerializerOptions() /// The default value is LinqSerializerType.Default /// public LinqSerializerType LinqSerializerType { get; set; } - - /// - /// Gets or sets the user defined customer serializer. If no customer serializer was defined, - /// then the value is set to the default value - /// - /// - /// The default value is null - /// - internal CosmosSerializer CustomCosmosSerializer { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs new file mode 100644 index 0000000000..37b4ecb290 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Serializer +{ + using System; + using Microsoft.Azure.Cosmos; + using Microsoft.Azure.Cosmos.Linq; + + /// + /// This class provides a way to configure Linq Serialization Properties + /// + internal sealed class CosmosLinqSerializerOptionsInternal + { + public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) + { + switch (cosmosLinqSerializerOptions.LinqSerializerType) + { + case LinqSerializerType.CustomCosmosSerializer: + { + if (customCosmosSerializer == null) + { + throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + } + + if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) + { + throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosSerializer."); + } + + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customCosmosSerializer); + } + case LinqSerializerType.Default: + { + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, null); + } + default: + { + throw new InvalidOperationException("Unsupported LinqSerializerType value."); + } + } + } + + /// + /// Create an instance of CosmosSerializationOptionsInternal + /// + internal CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) + { + this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; + this.CustomCosmosSerializer = customCosmosSerializer; + } + + /// + /// User-provided CosmosLinqSerializerOptions. + /// + public readonly CosmosLinqSerializerOptions CosmosLinqSerializerOptions; + + /// + /// Gets or sets the user defined customer serializer. If no customer serializer was defined, + /// then the value is set to the default value + /// + /// + /// The default value is null + /// + public readonly CosmosSerializer CustomCosmosSerializer; + } +} diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index 6deee03846..7717e0e991 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -27,7 +27,7 @@ internal static string GetStringWithPropertyNamingPolicy(CosmosPropertyNamingPol { CosmosPropertyNamingPolicy.CamelCase => CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false), CosmosPropertyNamingPolicy.Default => name, - _ => throw new ArgumentException("Unsupported CosmosPropertyNamingPolicy value"), + _ => throw new InvalidOperationException("Unsupported CosmosPropertyNamingPolicy value"), }; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml index 9f7044fac1..a9c63ebf41 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml @@ -10,15 +10,11 @@ SELECT VALUE root FROM root WHERE (root["NumberValueDotNet"] = 1)]]> @@ -34,9 +30,6 @@ SELECT VALUE root FROM root WHERE (root = {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null})]]> @@ -81,9 +68,6 @@ FROM root]]> SELECT VALUE ((root["NumberValueDotNet"] > 1) ? {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null} : {"NumberValueDotNet": 1, "StringValueDotNet": "1", "id": null, "Pk": null}) FROM root]]> @@ -109,9 +90,6 @@ SELECT VALUE "A" FROM root WHERE (root = {"NumberValueDotNet": root["NumberValueDotNet"], "StringValueDotNet": root["StringValueDotNet"]})]]> @@ -156,9 +129,6 @@ ORDER BY root["NumberValueDotNet"] ASC]]> SELECT VALUE ((root["NumberValueDotNet"] > 1) ? "true" : "false") FROM root]]> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 95f9bceb5d..d128b0c5c6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -258,7 +258,12 @@ static T createDataObj(int index, bool camelCase) LinqSerializerType = linqSerializerType }; - Func> getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); + Func> getQueryCamelCase = null; + if (linqSerializerType != LinqSerializerType.CustomCosmosSerializer) + { + getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); + } + Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsDefault); return (getQueryCamelCase, getQueryDefault); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 37654a2726..38833ba5ad 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Reflection; using System.Text.Json.Serialization; using global::Azure.Core.Serialization; + using Microsoft.Azure.Cosmos.Serializer; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -20,6 +21,16 @@ namespace Microsoft.Azure.Cosmos.Linq [TestClass] public class CosmosLinqJsonConverterTests { + private readonly CosmosLinqSerializerOptions defaultPublicOptions = new() + { + LinqSerializerType = LinqSerializerType.Default + }; + + private readonly CosmosLinqSerializerOptions customPublicOptions = new() + { + LinqSerializerType = LinqSerializerType.CustomCosmosSerializer + }; + [TestMethod] public void DateTimeKindIsPreservedTest() { @@ -39,17 +50,8 @@ public void DateTimeKindIsPreservedTest() [TestMethod] public void EnumIsPreservedAsINTest() { - CosmosLinqSerializerOptions options = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.CustomCosmosSerializer - }; - - CosmosLinqSerializerOptions defaultOptions = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.Default - }; + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, new TestCustomJsonSerializer()); TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; @@ -66,17 +68,8 @@ public void EnumIsPreservedAsINTest() [TestMethod] public void EnumIsPreservedAsEQUALSTest() { - CosmosLinqSerializerOptions options = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.CustomCosmosSerializer - }; - - CosmosLinqSerializerOptions defaultOptions = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.Default - }; + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, new TestCustomJsonSerializer()); TestEnum statusValue = TestEnum.One; @@ -93,17 +86,8 @@ public void EnumIsPreservedAsEQUALSTest() [TestMethod] public void EnumIsPreservedAsEXPRESSIONTest() { - CosmosLinqSerializerOptions options = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.CustomCosmosSerializer - }; - - CosmosLinqSerializerOptions defaultOptions = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.Default - }; + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, new TestCustomJsonSerializer()); // Get status constant ConstantExpression status = Expression.Constant(TestEnum.One); @@ -187,13 +171,10 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s [TestMethod] public void TestNewtonsoftExtensionDataQuery() { - CosmosLinqSerializerOptions options = new() - { - LinqSerializerType = LinqSerializerType.Default - }; + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, null); Expression> expr = a => (string)a.NewtonsoftExtensionData["foo"] == "bar"; - string sql = SqlTranslator.TranslateExpression(expr.Body, options); + string sql = SqlTranslator.TranslateExpression(expr.Body, defaultOptions); Assert.AreEqual("(a[\"foo\"] = \"bar\")", sql); } @@ -201,11 +182,7 @@ public void TestNewtonsoftExtensionDataQuery() [TestMethod] public void TestSystemTextJsonExtensionDataQuery() { - CosmosLinqSerializerOptions dotNetOptions = new() - { - CustomCosmosSerializer = new TestCustomJsonSerializer(), - LinqSerializerType = LinqSerializerType.CustomCosmosSerializer - }; + CosmosLinqSerializerOptionsInternal dotNetOptions = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); Expression> expr = a => ((object)a.NetExtensionData["foo"]) == "bar"; string sql = SqlTranslator.TranslateExpression(expr.Body, dotNetOptions); From a62eb580360f8a16d62846a22e990fd184732932 Mon Sep 17 00:00:00 2001 From: Maya Painter <130110800+Maya-Painter@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:40:03 -0800 Subject: [PATCH 26/48] Update Program.cs --- .../Usage/SystemTextJson/Program.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs b/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs index ac990a4e28..d66d54f5a2 100644 --- a/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs +++ b/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/Program.cs @@ -177,15 +177,19 @@ private static async Task CleanupAsync() // public class ToDoActivity { + // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("id")] public string Id { get; set; } + // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("partitionKey")] public string PartitionKey { get; set; } + // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("activityId")] public string ActivityId { get; set; } + // Note: System.Text.Json attributes such as JsonPropertyName are currently applied on item CRUD operations and non-LINQ queries, but not on LINQ queries [JsonPropertyName("status")] public string Status { get; set; } } From dead02e7be0971f251b7648f25e56b66bfccb648 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 29 Nov 2023 20:01:28 -0800 Subject: [PATCH 27/48] comment fixes --- .../CosmosLinqSerializerOptionsInternal.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 37b4ecb290..8fea4f9d30 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -9,10 +9,13 @@ namespace Microsoft.Azure.Cosmos.Serializer using Microsoft.Azure.Cosmos.Linq; /// - /// This class provides a way to configure Linq Serialization Properties + /// This class stores user-provided LINQ Serialization Properties. /// internal sealed class CosmosLinqSerializerOptionsInternal { + /// + /// Creates an instance of CosmosSerializationOptionsInternal. + /// public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) { switch (cosmosLinqSerializerOptions.LinqSerializerType) @@ -42,10 +45,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt } } - /// - /// Create an instance of CosmosSerializationOptionsInternal - /// - internal CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) + private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; this.CustomCosmosSerializer = customCosmosSerializer; @@ -57,12 +57,9 @@ internal CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosL public readonly CosmosLinqSerializerOptions CosmosLinqSerializerOptions; /// - /// Gets or sets the user defined customer serializer. If no customer serializer was defined, - /// then the value is set to the default value + /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. + /// Otherwise set to null; /// - /// - /// The default value is null - /// public readonly CosmosSerializer CustomCosmosSerializer; } } From 1f236789a45c5c80e8b0c350cac12c96e1bfd0e6 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 5 Dec 2023 16:56:16 -0800 Subject: [PATCH 28/48] PR comments --- .../CosmosLinqSerializerOptionsInternal.cs | 4 +- ...ine.TestAggregatesWithCustomSerializer.xml | 58 +++++++++++++++++++ ...mberInitializerDotNetCustomSerializer.xml} | 0 ...TestMemberInitializerDotNetDataMember.xml} | 0 ...MemberInitializerNewtonsoftDataMember.xml} | 0 ...TestMemberInitializerNewtonsoftDotNet.xml} | 0 .../Linq/LinqTestsCommon.cs | 2 +- ...TranslationWithCustomSerializerBaseline.cs | 8 +-- ...icrosoft.Azure.Cosmos.EmulatorTests.csproj | 16 ++--- 9 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/{LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml => LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml} (100%) rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/{LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml => LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml} (100%) rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/{LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml => LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml} (100%) rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/{LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml => LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml} (100%) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 8fea4f9d30..44026adc0f 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -54,12 +54,12 @@ private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLi /// /// User-provided CosmosLinqSerializerOptions. /// - public readonly CosmosLinqSerializerOptions CosmosLinqSerializerOptions; + public CosmosLinqSerializerOptions CosmosLinqSerializerOptions { get; } /// /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. /// Otherwise set to null; /// - public readonly CosmosSerializer CustomCosmosSerializer; + public CosmosSerializer CustomCosmosSerializer { get; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml new file mode 100644 index 0000000000..752edecde8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqAggregateCustomSerializationBaseline.TestAggregatesWithCustomSerializer.xml @@ -0,0 +1,58 @@ + + + + + doc.NumericField), Object)]]> + + + + . Actual:<0>. ]]> + + + + + + doc.NumericField), Object)]]> + + + + . Actual:<0>. ]]> + + + + + + + + + 0) +FROM ( + SELECT VALUE root + FROM root) AS v0 +]]> + + + + + + Filter -> Select -> Any]]> + doc.ArrayField.Where(m => ((m % 3) == 0)).Select(m => m)).Any(), Object)]]> + + + 0) +FROM ( + SELECT VALUE m0 + FROM root + JOIN m0 IN root["ArrayField"] + WHERE ((m0 % 3) = 0)) AS v0 +]]> + + + + \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml similarity index 100% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNet.xml rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetCustomSerializer.xml diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml similarity index 100% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMemberSerializer.xml rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerDotNetDataMember.xml diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml similarity index 100% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMemberSerializer.xml rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDataMember.xml diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml similarity index 100% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNetSerializer.xml rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqTranslationWithCustomSerializerBaseline.TestMemberInitializerNewtonsoftDotNet.xml diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 55f8637f14..f0502cec41 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -883,7 +883,7 @@ public override string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); - string memberName = jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name) + string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name) ? jsonPropertyNameAttribute.Name : memberInfo.Name; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index d128b0c5c6..b7b9d29396 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -70,7 +70,7 @@ public override LinqTestOutput ExecuteTest(LinqTestInput input) } [TestMethod] - public void TestMemberInitializerDotNet() + public void TestMemberInitializerDotNetCustomSerializer() { Func> getQuery; (_, getQuery) = this.InsertDataAndGetQueryables(LinqSerializerType.CustomCosmosSerializer); @@ -150,7 +150,7 @@ public void TestMemberInitializerDataMember() } [TestMethod] - public void TestMemberInitializerNewtonsoftDotNetSerializer() + public void TestMemberInitializerNewtonsoftDotNet() { Func> getQueryCamelCase; Func> getQueryDefault; @@ -179,7 +179,7 @@ public void TestMemberInitializerNewtonsoftDotNetSerializer() } [TestMethod] - public void TestMemberInitializerNewtonsoftDataMemberSerializer() + public void TestMemberInitializerNewtonsoftDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; @@ -208,7 +208,7 @@ public void TestMemberInitializerNewtonsoftDataMemberSerializer() } [TestMethod] - public void TestMemberInitializerDotNetDataMemberSerializer() + public void TestMemberInitializerDotNetDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj index 7c496a4e35..33b08de77e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Microsoft.Azure.Cosmos.EmulatorTests.csproj @@ -39,11 +39,11 @@ - + - - - + + + @@ -265,19 +265,19 @@ PreserveNewest - + PreserveNewest PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest From 0a3a7fcc9364d18cad19b6a66b7d418d66f970ea Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 6 Dec 2023 10:57:59 -0800 Subject: [PATCH 29/48] Add preview flag --- Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs | 7 ++++++- .../src/Serializer/CosmosLinqSerializerOptions.cs | 7 ++++++- Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs | 7 ++++++- .../Linq/LinqTestsCommon.cs | 7 ++++++- .../Linq/CosmosLinqJsonConverterTests.cs | 7 ++++++- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 0c50cacb1f..92154a3d5a 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -6,7 +6,12 @@ namespace Microsoft.Azure.Cosmos.Linq /// /// Serializer type to be used for LINQ query translations. /// - public enum LinqSerializerType + #if PREVIEW + public + #else + internal + #endif + enum LinqSerializerType { /// /// Follows the exisiting serializer pattern. This honors Newtonsoft attributes, followed by DataContract attributes. This will ignore System.Text.Json attributes. diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index ec8f313d8c..3a9fadfbd1 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -37,6 +37,11 @@ public CosmosLinqSerializerOptions() /// /// The default value is LinqSerializerType.Default /// - public LinqSerializerType LinqSerializerType { get; set; } + #if PREVIEW + public + #else + internal + #endif + LinqSerializerType LinqSerializerType { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index 31b58b7bf8..b394f27dbc 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -38,7 +38,12 @@ public abstract class CosmosSerializer /// /// Any MemberInfo used in the query. /// A serialized representation of the member - public virtual string SerializeLinqMemberName(MemberInfo memberInfo) + #if PREVIEW + public + #else + internal + #endif + virtual string SerializeLinqMemberName(MemberInfo memberInfo) { throw new NotImplementedException($"{nameof(CosmosSerializer)}.{nameof(SerializeLinqMemberName)})"); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index f0502cec41..1f2fe294dd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -879,7 +879,12 @@ public override Stream ToStream(T input) return streamPayload; } - public override string SerializeLinqMemberName(MemberInfo memberInfo) + #if PREVIEW + public + #else + internal + #endif + override string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 38833ba5ad..fcc5650d4b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -247,7 +247,12 @@ public override Stream ToStream(T input) return stream; } - public override string SerializeLinqMemberName(MemberInfo memberInfo) + #if PREVIEW + public + #else + internal + #endif + override string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); From 981b56c8e5d9cfcf1b426da961ef34c087b86604 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 6 Dec 2023 12:12:35 -0800 Subject: [PATCH 30/48] API changes --- .../Contracts/DotNetPreviewSDKAPI.json | 57 +++++++++++++++++++ .../Contracts/DotNetSDKAPI.json | 45 --------------- 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index dde2d208bc..d7fc98127e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -352,6 +352,42 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.DedicatedGatewayRequestOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -439,6 +475,27 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { + "Subclasses": {}, + "Members": { + "Int32 value__": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default;IsInitOnly:False;IsStatic:True;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.PriorityLevel;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 4ceba1ab83..46bdf1c170 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -3383,30 +3383,11 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy PropertyNamingPolicy;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy get_PropertyNamingPolicy();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_PropertyNamingPolicy(Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Void .ctor()": { "Type": "Constructor", "Attributes": [], "MethodInfo": "[Void .ctor(), Void .ctor()]" }, - "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "Void set_PropertyNamingPolicy(Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -3633,11 +3614,6 @@ "Attributes": [], "MethodInfo": "System.IO.Stream ToStream[T](T);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:True;IsConstructor:False;IsFinal:False;" }, - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, "T FromStream[T](System.IO.Stream)": { "Type": "Method", "Attributes": [], @@ -5731,27 +5707,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { - "Subclasses": {}, - "Members": { - "Int32 value__": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.OperationKind;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { "Subclasses": {}, "Members": { From c1f273753fc2f1aa22026b0855ec9d170f2054f9 Mon Sep 17 00:00:00 2001 From: "Maya Painter (from Dev Box)" Date: Tue, 12 Dec 2023 11:19:23 -0800 Subject: [PATCH 31/48] new serialization interface --- .../src/Linq/CustomCosmosLinqSerializer.cs | 9 +++--- .../src/Linq/LinqSerializerType.cs | 6 ++-- .../CosmosLinqSerializerOptionsInternal.cs | 21 ++++++++----- .../src/Serializer/CosmosQuerySerializer.cs | 30 +++++++++++++++++++ .../src/Serializer/CosmosSerializer.cs | 20 +------------ .../Linq/LinqTestsCommon.cs | 14 ++++----- .../Linq/CosmosLinqJsonConverterTests.cs | 9 ++---- 7 files changed, 60 insertions(+), 49 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index f4336f4098..65e3ee4a59 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -7,13 +7,14 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Globalization; using System.IO; using System.Linq.Expressions; - using System.Reflection; - + using System.Reflection; + using Microsoft.Azure.Cosmos.Serializer; + internal class CustomCosmosLinqSerializer : ICosmosLinqSerializer { - private readonly CosmosSerializer CustomCosmosSerializer; + private readonly CosmosQuerySerializer CustomCosmosSerializer; - public CustomCosmosLinqSerializer(CosmosSerializer customCosmosSerializer) + public CustomCosmosLinqSerializer(CosmosQuerySerializer customCosmosSerializer) { this.CustomCosmosSerializer = customCosmosSerializer; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 92154a3d5a..a7f6e4ce62 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -2,7 +2,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos.Linq -{ +{ + using Microsoft.Azure.Cosmos.Serializer; + /// /// Serializer type to be used for LINQ query translations. /// @@ -22,7 +24,7 @@ enum LinqSerializerType /// Uses the provided custom CosmosSerializer. /// This requires: /// 1. a to be provided on a client, and - /// 2. the custom CosmosSerializer implements the member function + /// 2. the custom CosmosSerializer implements /// CustomCosmosSerializer, } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 44026adc0f..15ad57b99f 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -24,15 +24,20 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt { if (customCosmosSerializer == null) { - throw new InvalidOperationException($"Must provide CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); - } - + throw new InvalidOperationException($"Must provide custom CosmosQuerySerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + } + if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) { throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosSerializer."); - } - - return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customCosmosSerializer); + } + + if (customCosmosSerializer is CosmosQuerySerializer customQueryCosmosSerializer) + { + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); + } + + throw new InvalidOperationException($"CosmosSerializer must implement CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); } case LinqSerializerType.Default: { @@ -45,7 +50,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt } } - private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) + private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosQuerySerializer customCosmosSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; this.CustomCosmosSerializer = customCosmosSerializer; @@ -60,6 +65,6 @@ private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLi /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. /// Otherwise set to null; /// - public CosmosSerializer CustomCosmosSerializer { get; } + public CosmosQuerySerializer CustomCosmosSerializer { get; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs new file mode 100644 index 0000000000..745601ac14 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Serializer +{ + using System; + using System.Reflection; + + /// + /// This is an interface to allow a custom serializer to be used by the CosmosClient for both CRUD operations and LINQ queries. + /// + #if PREVIEW + public + #else + internal + #endif + abstract class CosmosQuerySerializer : CosmosSerializer + { + /// + /// Convert a MemberInfo to a string for use in LINQ query translation. + /// This must be implemented when using a custom serializer for LINQ queries. + /// + /// Any MemberInfo used in the query. + /// A serialized representation of the member + public virtual string SerializeLinqMemberName(MemberInfo memberInfo) + { + throw new NotImplementedException($"{nameof(CosmosSerializer)}.{nameof(SerializeLinqMemberName)})"); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index b394f27dbc..2488b1e4eb 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -4,12 +4,10 @@ namespace Microsoft.Azure.Cosmos { - using System; using System.IO; - using System.Reflection; /// - /// This is an interface to allow a custom serializer to be used by the CosmosClient + /// This is an interface to allow a custom serializer to be used by the CosmosClient for CRUD operations. /// public abstract class CosmosSerializer { @@ -31,21 +29,5 @@ public abstract class CosmosSerializer /// Any type passed to . /// A readable Stream containing JSON of the serialized object. public abstract Stream ToStream(T input); - - /// - /// Convert a MemberInfo to a string for use in LINQ query translation. - /// This must be implemented when using a custom serializer for LINQ queries. - /// - /// Any MemberInfo used in the query. - /// A serialized representation of the member - #if PREVIEW - public - #else - internal - #endif - virtual string SerializeLinqMemberName(MemberInfo memberInfo) - { - throw new NotImplementedException($"{nameof(CosmosSerializer)}.{nameof(SerializeLinqMemberName)})"); - } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 1f2fe294dd..359d967e96 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -25,8 +25,9 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - + using Newtonsoft.Json.Linq; + using Microsoft.Azure.Cosmos.Serializer; + internal class LinqTestsCommon { /// @@ -841,7 +842,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } - public class SystemTextJsonSerializer : CosmosSerializer + class SystemTextJsonSerializer : CosmosQuerySerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -879,12 +880,7 @@ public override Stream ToStream(T input) return streamPayload; } - #if PREVIEW - public - #else - internal - #endif - override string SerializeLinqMemberName(MemberInfo memberInfo) + public override string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index fcc5650d4b..1605edf836 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -202,7 +202,7 @@ class DocumentWithExtensionData /// // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs /// - class TestCustomJsonSerializer : CosmosSerializer + class TestCustomJsonSerializer : CosmosQuerySerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -247,12 +247,7 @@ public override Stream ToStream(T input) return stream; } - #if PREVIEW - public - #else - internal - #endif - override string SerializeLinqMemberName(MemberInfo memberInfo) + public override string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); From 86c45153c9dc735f6027db604cd59fba1ecbaff4 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 12 Dec 2023 14:14:45 -0800 Subject: [PATCH 32/48] Update API and serializer updates --- .../src/Serializer/CosmosQuerySerializer.cs | 17 +++++------ .../src/Serializer/CosmosSerializer.cs | 2 +- .../Contracts/DotNetPreviewSDKAPI.json | 29 +++++++++++++++---- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs index 745601ac14..d1413a0a79 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs @@ -3,17 +3,17 @@ //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos.Serializer { - using System; using System.Reflection; /// - /// This is an interface to allow a custom serializer to be used by the CosmosClient for both CRUD operations and LINQ queries. + /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient + /// for both CRUD operations and LINQ queries. /// - #if PREVIEW +#if PREVIEW public - #else +#else internal - #endif +#endif abstract class CosmosQuerySerializer : CosmosSerializer { /// @@ -21,10 +21,7 @@ abstract class CosmosQuerySerializer : CosmosSerializer /// This must be implemented when using a custom serializer for LINQ queries. /// /// Any MemberInfo used in the query. - /// A serialized representation of the member - public virtual string SerializeLinqMemberName(MemberInfo memberInfo) - { - throw new NotImplementedException($"{nameof(CosmosSerializer)}.{nameof(SerializeLinqMemberName)})"); - } + /// A serialized representation of the member. + public abstract string SerializeLinqMemberName(MemberInfo memberInfo); } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index 2488b1e4eb..3df075f105 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos using System.IO; /// - /// This is an interface to allow a custom serializer to be used by the CosmosClient for CRUD operations. + /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient for CRUD operations. /// public abstract class CosmosSerializer { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index d7fc98127e..353de544db 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -378,14 +378,20 @@ "NestedTypes": {} }, "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Subclasses": { + "Microsoft.Azure.Cosmos.Serializer.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} } }, + "Members": {}, "NestedTypes": {} }, "Microsoft.Azure.Cosmos.DedicatedGatewayRequestOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { @@ -592,6 +598,17 @@ } }, "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.Serializer.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} } }, "Members": {}, From 5593fd0557dc11e560269424c103e66896db5a24 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 12 Dec 2023 16:13:48 -0800 Subject: [PATCH 33/48] API fix --- .../src/Serializer/CosmosQuerySerializer.cs | 2 +- .../Contracts/DotNetPreviewSDKAPI.json | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs index d1413a0a79..93a28c429b 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Serializer +namespace Microsoft.Azure.Cosmos { using System.Reflection; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 353de544db..05be6c6e23 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -377,9 +377,20 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": { - "Microsoft.Azure.Cosmos.Serializer.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { @@ -598,17 +609,6 @@ } }, "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.Serializer.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} } }, "Members": {}, From 125198d21e111043357590f3f8d1cbc1575da745 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Thu, 14 Dec 2023 13:18:11 -0800 Subject: [PATCH 34/48] cleanup --- Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs | 1 - Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs | 1 - Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs | 1 - .../Linq/LinqTestsCommon.cs | 3 +-- .../Linq/CosmosLinqJsonConverterTests.cs | 2 +- 5 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs index 240f76c316..a4c4c5e938 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQuery.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Diagnostics; - using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Serializer; using Microsoft.Azure.Cosmos.Tracing; diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs index 68bc3b519a..2c695c6477 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqQueryProvider.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Linq { using System; - using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index 65e3ee4a59..d62a1da506 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos.Linq using System.IO; using System.Linq.Expressions; using System.Reflection; - using Microsoft.Azure.Cosmos.Serializer; internal class CustomCosmosLinqSerializer : ICosmosLinqSerializer { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 359d967e96..18d7dbef6b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -26,7 +26,6 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - using Microsoft.Azure.Cosmos.Serializer; internal class LinqTestsCommon { @@ -875,7 +874,7 @@ public override T FromStream(Stream stream) public override Stream ToStream(T input) { MemoryStream streamPayload = new MemoryStream(); - this.systemTextJsonSerializer.Serialize(streamPayload, input, typeof(T), default); + this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default); streamPayload.Position = 0; return streamPayload; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 1605edf836..028ccf912b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -242,7 +242,7 @@ public override Stream ToStream(T input) { MemoryStream stream = new(); - this.systemTextJsonSerializer.Serialize(stream, input, typeof(T), default); + this.systemTextJsonSerializer.Serialize(stream, input, input.GetType(), default); stream.Position = 0; return stream; } From dc0afd32b12de391645b7ee5f561f88f44ddde09 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Sat, 16 Dec 2023 13:06:09 -0800 Subject: [PATCH 35/48] PR comments --- .../src/Linq/CustomCosmosLinqSerializer.cs | 4 +- .../src/Linq/LinqSerializerType.cs | 2 +- ...ySerializer.cs => CosmosLinqSerializer.cs} | 54 +++++++++---------- .../CosmosLinqSerializerOptionsInternal.cs | 10 ++-- .../Linq/LinqTestsCommon.cs | 2 +- .../Contracts/DotNetPreviewSDKAPI.json | 24 ++++----- .../Linq/CosmosLinqJsonConverterTests.cs | 2 +- 7 files changed, 49 insertions(+), 49 deletions(-) rename Microsoft.Azure.Cosmos/src/Serializer/{CosmosQuerySerializer.cs => CosmosLinqSerializer.cs} (91%) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index d62a1da506..fe491b9bf5 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -11,9 +11,9 @@ namespace Microsoft.Azure.Cosmos.Linq internal class CustomCosmosLinqSerializer : ICosmosLinqSerializer { - private readonly CosmosQuerySerializer CustomCosmosSerializer; + private readonly CosmosLinqSerializer CustomCosmosSerializer; - public CustomCosmosLinqSerializer(CosmosQuerySerializer customCosmosSerializer) + public CustomCosmosLinqSerializer(CosmosLinqSerializer customCosmosSerializer) { this.CustomCosmosSerializer = customCosmosSerializer; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index a7f6e4ce62..08f4f7f384 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -24,7 +24,7 @@ enum LinqSerializerType /// Uses the provided custom CosmosSerializer. /// This requires: /// 1. a to be provided on a client, and - /// 2. the custom CosmosSerializer implements + /// 2. the custom CosmosSerializer implements /// CustomCosmosSerializer, } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs similarity index 91% rename from Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs rename to Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs index 93a28c429b..361fa054e6 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosQuerySerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs @@ -1,27 +1,27 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos -{ - using System.Reflection; - - /// - /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient - /// for both CRUD operations and LINQ queries. - /// -#if PREVIEW - public -#else - internal -#endif - abstract class CosmosQuerySerializer : CosmosSerializer - { - /// - /// Convert a MemberInfo to a string for use in LINQ query translation. - /// This must be implemented when using a custom serializer for LINQ queries. - /// - /// Any MemberInfo used in the query. - /// A serialized representation of the member. - public abstract string SerializeLinqMemberName(MemberInfo memberInfo); - } -} +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos +{ + using System.Reflection; + + /// + /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient + /// for both CRUD operations and LINQ queries. + /// +#if PREVIEW + public +#else + internal +#endif + abstract class CosmosLinqSerializer : CosmosSerializer + { + /// + /// Convert a MemberInfo to a string for use in LINQ query translation. + /// This must be implemented when using a custom serializer for LINQ queries. + /// + /// Any MemberInfo used in the query. + /// A serialized representation of the member. + public abstract string SerializeLinqMemberName(MemberInfo memberInfo); + } +} diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 15ad57b99f..0559396b0e 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -24,7 +24,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt { if (customCosmosSerializer == null) { - throw new InvalidOperationException($"Must provide custom CosmosQuerySerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + throw new InvalidOperationException($"Must provide CosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); } if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) @@ -32,12 +32,12 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosSerializer."); } - if (customCosmosSerializer is CosmosQuerySerializer customQueryCosmosSerializer) + if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) { return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); } - throw new InvalidOperationException($"CosmosSerializer must implement CustomCosmosSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + throw new InvalidOperationException($"CosmosSerializer must implement CosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); } case LinqSerializerType.Default: { @@ -50,7 +50,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt } } - private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosQuerySerializer customCosmosSerializer) + private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosLinqSerializer customCosmosSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; this.CustomCosmosSerializer = customCosmosSerializer; @@ -65,6 +65,6 @@ private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLi /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. /// Otherwise set to null; /// - public CosmosQuerySerializer CustomCosmosSerializer { get; } + public CosmosLinqSerializer CustomCosmosSerializer { get; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 18d7dbef6b..2c1d68ddcf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -841,7 +841,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } - class SystemTextJsonSerializer : CosmosQuerySerializer + class SystemTextJsonSerializer : CosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 05be6c6e23..749193ac8b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -352,6 +352,17 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -377,20 +388,9 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": { - "Microsoft.Azure.Cosmos.CosmosQuerySerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 028ccf912b..5a48a781aa 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -202,7 +202,7 @@ class DocumentWithExtensionData /// // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs /// - class TestCustomJsonSerializer : CosmosQuerySerializer + class TestCustomJsonSerializer : CosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; From be43ed69bfeb93f7e9acdf610c1cad82b0a01baa Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Mon, 18 Dec 2023 15:24:11 -0800 Subject: [PATCH 36/48] update interface --- .../src/Linq/CustomCosmosLinqSerializer.cs | 6 +++--- .../src/Linq/DefaultCosmosLinqSerializer.cs | 2 +- ...er.cs => ICosmosLinqSerializerInternal.cs} | 2 +- .../src/Linq/LinqSerializerType.cs | 8 +++----- .../src/Linq/TranslationContext.cs | 2 +- .../CosmosLinqSerializerOptionsInternal.cs | 6 +++--- ...Serializer.cs => ICosmosLinqSerializer.cs} | 19 +++++++++++++++---- .../LinqAttributeContractBaselineTests.cs | 2 +- .../Linq/LinqTestsCommon.cs | 4 ++-- .../Linq/CosmosLinqJsonConverterTests.cs | 4 ++-- 10 files changed, 32 insertions(+), 23 deletions(-) rename Microsoft.Azure.Cosmos/src/Linq/{ICosmosLinqSerializer.cs => ICosmosLinqSerializerInternal.cs} (95%) rename Microsoft.Azure.Cosmos/src/Serializer/{CosmosLinqSerializer.cs => ICosmosLinqSerializer.cs} (52%) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index fe491b9bf5..318bba6dae 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -9,11 +9,11 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq.Expressions; using System.Reflection; - internal class CustomCosmosLinqSerializer : ICosmosLinqSerializer + internal class CustomCosmosLinqSerializer : ICosmosLinqSerializerInternal { - private readonly CosmosLinqSerializer CustomCosmosSerializer; + private readonly ICosmosLinqSerializer CustomCosmosSerializer; - public CustomCosmosLinqSerializer(CosmosLinqSerializer customCosmosSerializer) + public CustomCosmosLinqSerializer(ICosmosLinqSerializer customCosmosSerializer) { this.CustomCosmosSerializer = customCosmosSerializer; } diff --git a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs index 378815e992..31f20d5059 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.Cosmos.Linq using Microsoft.Azure.Documents; using Newtonsoft.Json; - internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializer + internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializerInternal { private readonly CosmosPropertyNamingPolicy PropertyNamingPolicy; diff --git a/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializerInternal.cs similarity index 95% rename from Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs rename to Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializerInternal.cs index c4265cb990..7a3054e3b6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/ICosmosLinqSerializerInternal.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos.Linq using System.Linq.Expressions; using System.Reflection; - internal interface ICosmosLinqSerializer + internal interface ICosmosLinqSerializerInternal { /// /// Returns true if there are custom attributes on a member expression. diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs index 08f4f7f384..9ed96d9f8e 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs @@ -3,14 +3,12 @@ //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos.Linq { - using Microsoft.Azure.Cosmos.Serializer; - /// /// Serializer type to be used for LINQ query translations. /// - #if PREVIEW +#if PREVIEW public - #else +#else internal #endif enum LinqSerializerType @@ -24,7 +22,7 @@ enum LinqSerializerType /// Uses the provided custom CosmosSerializer. /// This requires: /// 1. a to be provided on a client, and - /// 2. the custom CosmosSerializer implements + /// 2. the custom CosmosSerializer implements /// CustomCosmosSerializer, } diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 6ea8194460..40f20daf6d 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -25,7 +25,7 @@ internal sealed class TranslationContext /// /// The LINQ serializer /// - public readonly ICosmosLinqSerializer CosmosLinqSerializer; + public readonly ICosmosLinqSerializerInternal CosmosLinqSerializer; /// /// Set of parameters in scope at any point; used to generate fresh parameter names if necessary. diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 0559396b0e..e448346f55 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -32,7 +32,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosSerializer."); } - if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) + if (customCosmosSerializer is ICosmosLinqSerializer customQueryCosmosSerializer) { return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); } @@ -50,7 +50,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt } } - private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosLinqSerializer customCosmosSerializer) + private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, ICosmosLinqSerializer customCosmosSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; this.CustomCosmosSerializer = customCosmosSerializer; @@ -65,6 +65,6 @@ private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLi /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. /// Otherwise set to null; /// - public CosmosLinqSerializer CustomCosmosSerializer { get; } + public ICosmosLinqSerializer CustomCosmosSerializer { get; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs similarity index 52% rename from Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs rename to Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs index 361fa054e6..31ff9266d2 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs @@ -3,10 +3,11 @@ //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos { + using System.IO; using System.Reflection; /// - /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient + /// This interface can be implemented to allow a custom serializer to be used by the CosmosClient /// for both CRUD operations and LINQ queries. /// #if PREVIEW @@ -14,14 +15,24 @@ namespace Microsoft.Azure.Cosmos #else internal #endif - abstract class CosmosLinqSerializer : CosmosSerializer + interface ICosmosLinqSerializer { /// - /// Convert a MemberInfo to a string for use in LINQ query translation. + /// Convert a to a string for use in LINQ query translation. /// This must be implemented when using a custom serializer for LINQ queries. /// /// Any MemberInfo used in the query. /// A serialized representation of the member. - public abstract string SerializeLinqMemberName(MemberInfo memberInfo); + public string SerializeLinqMemberName(MemberInfo memberInfo); + + /// + /// + /// + public T FromStream(Stream stream); + + /// + /// + /// + public Stream ToStream(T input); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs index f979a59cf2..1f511a3c11 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAttributeContractBaselineTests.cs @@ -172,7 +172,7 @@ public Datum2(string jsonProperty, string dataMember, string defaultMember, stri [TestMethod] public void TestAttributePriority() { - ICosmosLinqSerializer cosmosLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosPropertyNamingPolicy()); + ICosmosLinqSerializerInternal cosmosLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosPropertyNamingPolicy()); Assert.AreEqual("jsonProperty", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("JsonProperty").First())); Assert.AreEqual("dataMember", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("DataMember").First())); Assert.AreEqual("Default", cosmosLinqSerializer.SerializeMemberName(typeof(Datum).GetMember("Default").First())); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 2c1d68ddcf..1e556cca64 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -841,7 +841,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } - class SystemTextJsonSerializer : CosmosLinqSerializer + class SystemTextJsonSerializer : CosmosSerializer, ICosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -879,7 +879,7 @@ public override Stream ToStream(T input) return streamPayload; } - public override string SerializeLinqMemberName(MemberInfo memberInfo) + public string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 5a48a781aa..faeaa644fd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -202,7 +202,7 @@ class DocumentWithExtensionData /// // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs /// - class TestCustomJsonSerializer : CosmosLinqSerializer + class TestCustomJsonSerializer : CosmosSerializer, ICosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -247,7 +247,7 @@ public override Stream ToStream(T input) return stream; } - public override string SerializeLinqMemberName(MemberInfo memberInfo) + public string SerializeLinqMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); From ed82751c4772a78ef6bd03557e75d7c8b01646dd Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Mon, 18 Dec 2023 15:55:46 -0800 Subject: [PATCH 37/48] fix --- .../src/Linq/CustomCosmosLinqSerializer.cs | 13 +++++++++---- .../CosmosLinqSerializerOptionsInternal.cs | 12 ++++++------ .../src/Serializer/ICosmosLinqSerializer.cs | 10 ---------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index 318bba6dae..8034b00c94 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -11,9 +11,9 @@ namespace Microsoft.Azure.Cosmos.Linq internal class CustomCosmosLinqSerializer : ICosmosLinqSerializerInternal { - private readonly ICosmosLinqSerializer CustomCosmosSerializer; + private readonly CosmosSerializer CustomCosmosSerializer; - public CustomCosmosLinqSerializer(ICosmosLinqSerializer customCosmosSerializer) + public CustomCosmosLinqSerializer(CosmosSerializer customCosmosSerializer) { this.CustomCosmosSerializer = customCosmosSerializer; } @@ -34,8 +34,13 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) } public string SerializeMemberName(MemberInfo memberInfo) - { - return this.CustomCosmosSerializer.SerializeLinqMemberName(memberInfo); + { + if (this.CustomCosmosSerializer is ICosmosLinqSerializer customLinqCosmosSerializer) + { + return customLinqCosmosSerializer.SerializeLinqMemberName(memberInfo); + } + + throw new InvalidOperationException($"CosmosSerializer must implement ICosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); } private string SerializeWithCustomSerializer(object value) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index e448346f55..023935c7f8 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -32,12 +32,12 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosSerializer."); } - if (customCosmosSerializer is ICosmosLinqSerializer customQueryCosmosSerializer) + if (customCosmosSerializer is not ICosmosLinqSerializer) { - return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); + throw new InvalidOperationException($"CosmosSerializer must implement ICosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); } - throw new InvalidOperationException($"CosmosSerializer must implement CosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customCosmosSerializer); } case LinqSerializerType.Default: { @@ -50,7 +50,7 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt } } - private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, ICosmosLinqSerializer customCosmosSerializer) + private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; this.CustomCosmosSerializer = customCosmosSerializer; @@ -63,8 +63,8 @@ private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLi /// /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. - /// Otherwise set to null; + /// Otherwise set to null. /// - public ICosmosLinqSerializer CustomCosmosSerializer { get; } + public CosmosSerializer CustomCosmosSerializer { get; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs index 31ff9266d2..40d0887825 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs @@ -24,15 +24,5 @@ interface ICosmosLinqSerializer /// Any MemberInfo used in the query. /// A serialized representation of the member. public string SerializeLinqMemberName(MemberInfo memberInfo); - - /// - /// - /// - public T FromStream(Stream stream); - - /// - /// - /// - public Stream ToStream(T input); } } From 81e29e27b79a9a41f4d29255053ef16d0e65122b Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Mon, 18 Dec 2023 17:08:38 -0800 Subject: [PATCH 38/48] API --- .../Contracts/DotNetPreviewSDKAPI.json | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 749193ac8b..46e3b76921 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -352,17 +352,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -388,23 +377,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": { - "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - } - }, - "Members": {}, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.DedicatedGatewayRequestOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -479,6 +451,17 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.ICosmosLinqSerializer;;IsAbstract:True;IsSealed:False;IsInterface:True;IsEnum:False;IsClass:False;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.Linq.CosmosLinqExtensions;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { From 7eba8505c6031ff254615a8d8d292bc4bf5d7560 Mon Sep 17 00:00:00 2001 From: Maya Painter <130110800+Maya-Painter@users.noreply.github.com> Date: Tue, 2 Jan 2024 12:20:04 -0800 Subject: [PATCH 39/48] Update Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs Co-authored-by: Kiran Kumar Kolli --- Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs index 40d0887825..52dbe24a79 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Cosmos using System.Reflection; /// - /// This interface can be implemented to allow a custom serializer to be used by the CosmosClient + /// This interface can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) to be used by the CosmosClient /// for both CRUD operations and LINQ queries. /// #if PREVIEW From 10ed71e1cb3d2428a48b4c84076c70f1feb001c7 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 2 Jan 2024 15:23:14 -0800 Subject: [PATCH 40/48] Some PR comments --- ...zerType.cs => CosmosLinqSerializerType.cs} | 8 +-- .../src/Linq/CustomCosmosLinqSerializer.cs | 8 +-- .../Serializer/CosmosLinqSerializerOptions.cs | 6 +- .../CosmosLinqSerializerOptionsInternal.cs | 16 +++-- .../src/Serializer/CosmosSerializationUtil.cs | 2 +- .../src/Serializer/ICosmosLinqSerializer.cs | 7 +-- ...inqAggregateCustomSerializationBaseline.cs | 2 +- .../Linq/LinqTestsCommon.cs | 2 +- ...TranslationWithCustomSerializerBaseline.cs | 16 ++--- .../Contracts/DotNetPreviewSDKAPI.json | 58 +++++++++---------- .../Linq/CosmosLinqJsonConverterTests.cs | 6 +- 11 files changed, 65 insertions(+), 66 deletions(-) rename Microsoft.Azure.Cosmos/src/Linq/{LinqSerializerType.cs => CosmosLinqSerializerType.cs} (75%) diff --git a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs similarity index 75% rename from Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs rename to Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs index 9ed96d9f8e..45e7bf62e6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/LinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs @@ -1,8 +1,8 @@ //------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.Linq -{ +namespace Microsoft.Azure.Cosmos +{ /// /// Serializer type to be used for LINQ query translations. /// @@ -11,10 +11,10 @@ namespace Microsoft.Azure.Cosmos.Linq #else internal #endif - enum LinqSerializerType + enum CosmosLinqSerializerType { /// - /// Follows the exisiting serializer pattern. This honors Newtonsoft attributes, followed by DataContract attributes. This will ignore System.Text.Json attributes. + /// This honors Newtonsoft attributes, followed by DataContract attributes. This will ignore System.Text.Json attributes. /// Default, diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index 8034b00c94..89d92f4b50 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -35,12 +35,8 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) public string SerializeMemberName(MemberInfo memberInfo) { - if (this.CustomCosmosSerializer is ICosmosLinqSerializer customLinqCosmosSerializer) - { - return customLinqCosmosSerializer.SerializeLinqMemberName(memberInfo); - } - - throw new InvalidOperationException($"CosmosSerializer must implement ICosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + ICosmosLinqSerializer customLinqCosmosSerializer = (ICosmosLinqSerializer)this.CustomCosmosSerializer; + return customLinqCosmosSerializer.SerializeMemberName(memberInfo); } private string SerializeWithCustomSerializer(object value) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index 3a9fadfbd1..62037f3aaf 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -18,7 +18,7 @@ public sealed class CosmosLinqSerializerOptions public CosmosLinqSerializerOptions() { this.PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default; - this.LinqSerializerType = LinqSerializerType.Default; + this.LinqSerializerType = CosmosLinqSerializerType.Default; } /// @@ -35,13 +35,13 @@ public CosmosLinqSerializerOptions() /// Options are detailed in /// /// - /// The default value is LinqSerializerType.Default + /// The default value is CosmosLinqSerializerType.Default /// #if PREVIEW public #else internal #endif - LinqSerializerType LinqSerializerType { get; set; } + CosmosLinqSerializerType LinqSerializerType { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 023935c7f8..7a6572a380 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -16,11 +16,13 @@ internal sealed class CosmosLinqSerializerOptionsInternal /// /// Creates an instance of CosmosSerializationOptionsInternal. /// - public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) + public static CosmosLinqSerializerOptionsInternal Create( + CosmosLinqSerializerOptions cosmosLinqSerializerOptions, + CosmosSerializer customCosmosSerializer) { switch (cosmosLinqSerializerOptions.LinqSerializerType) { - case LinqSerializerType.CustomCosmosSerializer: + case CosmosLinqSerializerType.CustomCosmosSerializer: { if (customCosmosSerializer == null) { @@ -39,18 +41,20 @@ public static CosmosLinqSerializerOptionsInternal Create(CosmosLinqSerializerOpt return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customCosmosSerializer); } - case LinqSerializerType.Default: + case CosmosLinqSerializerType.Default: { return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, null); } default: { - throw new InvalidOperationException("Unsupported LinqSerializerType value."); + throw new InvalidOperationException("Unsupported CosmosLinqSerializerType value."); } } } - private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) + private CosmosLinqSerializerOptionsInternal( + CosmosLinqSerializerOptions cosmosLinqSerializerOptions, + CosmosSerializer customCosmosSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; this.CustomCosmosSerializer = customCosmosSerializer; @@ -62,7 +66,7 @@ private CosmosLinqSerializerOptionsInternal(CosmosLinqSerializerOptions cosmosLi public CosmosLinqSerializerOptions CosmosLinqSerializerOptions { get; } /// - /// User defined customer serializer, if LinqSerializerType is CustomCosmosSerializer. + /// User defined customer serializer, if CosmosLinqSerializerType is CustomCosmosSerializer. /// Otherwise set to null. /// public CosmosSerializer CustomCosmosSerializer { get; } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs index 7717e0e991..8946d9ac7a 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationUtil.cs @@ -27,7 +27,7 @@ internal static string GetStringWithPropertyNamingPolicy(CosmosPropertyNamingPol { CosmosPropertyNamingPolicy.CamelCase => CosmosSerializationUtil.camelCaseNamingStrategy.GetPropertyName(name, false), CosmosPropertyNamingPolicy.Default => name, - _ => throw new InvalidOperationException("Unsupported CosmosPropertyNamingPolicy value"), + _ => throw new NotImplementedException("Unsupported CosmosPropertyNamingPolicy value"), }; } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs index 52dbe24a79..ecf5c2266a 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs @@ -3,12 +3,11 @@ //------------------------------------------------------------ namespace Microsoft.Azure.Cosmos { - using System.IO; using System.Reflection; /// - /// This interface can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) to be used by the CosmosClient - /// for both CRUD operations and LINQ queries. + /// This interface can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) + /// to be used by the CosmosClient for LINQ queries. /// #if PREVIEW public @@ -23,6 +22,6 @@ interface ICosmosLinqSerializer /// /// Any MemberInfo used in the query. /// A serialized representation of the member. - public string SerializeLinqMemberName(MemberInfo memberInfo); + public string SerializeMemberName(MemberInfo memberInfo); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs index 4abfb6c793..277438f552 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs @@ -73,7 +73,7 @@ static DataObjectDotNet createDataObj(int index, bool camelCase) CosmosLinqSerializerOptions linqSerializerOptions = new CosmosLinqSerializerOptions() { - LinqSerializerType = LinqSerializerType.CustomCosmosSerializer + LinqSerializerType = CosmosLinqSerializerType.CustomCosmosSerializer }; List>> getQueryList = new List>> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 1e556cca64..a5cad74876 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -879,7 +879,7 @@ public override Stream ToStream(T input) return streamPayload; } - public string SerializeLinqMemberName(MemberInfo memberInfo) + public string SerializeMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index b7b9d29396..d5089b43da 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -73,7 +73,7 @@ public override LinqTestOutput ExecuteTest(LinqTestInput input) public void TestMemberInitializerDotNetCustomSerializer() { Func> getQuery; - (_, getQuery) = this.InsertDataAndGetQueryables(LinqSerializerType.CustomCosmosSerializer); + (_, getQuery) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.CustomCosmosSerializer); string insertedData = this.GetInsertedData().Result; @@ -96,7 +96,7 @@ public void TestMemberInitializerNewtonsoft() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -125,7 +125,7 @@ public void TestMemberInitializerDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -154,7 +154,7 @@ public void TestMemberInitializerNewtonsoftDotNet() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -183,7 +183,7 @@ public void TestMemberInitializerNewtonsoftDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -212,7 +212,7 @@ public void TestMemberInitializerDotNetDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(LinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); string insertedData = this.GetInsertedData().Result; @@ -236,7 +236,7 @@ public void TestMemberInitializerDotNetDataMember() this.ExecuteTestSuite(inputs); } - private (Func>, Func>) InsertDataAndGetQueryables(LinqSerializerType linqSerializerType) where T : LinqTestObject + private (Func>, Func>) InsertDataAndGetQueryables(CosmosLinqSerializerType linqSerializerType) where T : LinqTestObject { static T createDataObj(int index, bool camelCase) { @@ -259,7 +259,7 @@ static T createDataObj(int index, bool camelCase) }; Func> getQueryCamelCase = null; - if (linqSerializerType != LinqSerializerType.CustomCosmosSerializer) + if (linqSerializerType != CosmosLinqSerializerType.CustomCosmosSerializer) { getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 46e3b76921..0e2c52cb71 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -355,24 +355,45 @@ "Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.CosmosLinqSerializerType get_LinqSerializerType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType": { + "Microsoft.Azure.Cosmos.CosmosLinqSerializerType LinqSerializerType": { "Type": "Property", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType LinqSerializerType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.Linq.LinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType LinqSerializerType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.CosmosLinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_LinqSerializerType(Microsoft.Azure.Cosmos.CosmosLinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.CosmosLinqSerializerType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.Linq.LinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.CosmosLinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializerType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { + "Subclasses": {}, + "Members": { + "Int32 value__": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" + }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializerType CustomCosmosSerializer": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType CustomCosmosSerializer;IsInitOnly:False;IsStatic:True;" + }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Default": { + "Type": "Field", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Default;IsInitOnly:False;IsStatic:True;" } }, "NestedTypes": {} @@ -454,10 +475,10 @@ "Microsoft.Azure.Cosmos.ICosmosLinqSerializer;;IsAbstract:True;IsSealed:False;IsInterface:True;IsEnum:False;IsClass:False;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "System.String SerializeLinqMemberName(System.Reflection.MemberInfo)": { + "System.String SerializeMemberName(System.Reflection.MemberInfo)": { "Type": "Method", "Attributes": [], - "MethodInfo": "System.String SerializeLinqMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.String SerializeMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, "NestedTypes": {} @@ -475,27 +496,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { - "Subclasses": {}, - "Members": { - "Int32 value__": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType CustomCosmosSerializer;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.Linq.LinqSerializerType Default;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.PriorityLevel;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index faeaa644fd..69ce16fa30 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -23,12 +23,12 @@ public class CosmosLinqJsonConverterTests { private readonly CosmosLinqSerializerOptions defaultPublicOptions = new() { - LinqSerializerType = LinqSerializerType.Default + LinqSerializerType = CosmosLinqSerializerType.Default }; private readonly CosmosLinqSerializerOptions customPublicOptions = new() { - LinqSerializerType = LinqSerializerType.CustomCosmosSerializer + LinqSerializerType = CosmosLinqSerializerType.CustomCosmosSerializer }; [TestMethod] @@ -247,7 +247,7 @@ public override Stream ToStream(T input) return stream; } - public string SerializeLinqMemberName(MemberInfo memberInfo) + public string SerializeMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); From 0bd4a26ddcf805d3b1fc38898204f61bb3abc111 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Tue, 2 Jan 2024 16:59:33 -0800 Subject: [PATCH 41/48] adding sample code --- .../src/Linq/CosmosLinqSerializerType.cs | 22 ++++++- .../Serializer/CosmosLinqSerializerOptions.cs | 2 - .../src/Serializer/ICosmosLinqSerializer.cs | 58 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs index 45e7bf62e6..d97651219d 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs @@ -6,12 +6,30 @@ namespace Microsoft.Azure.Cosmos /// /// Serializer type to be used for LINQ query translations. /// + /// + /// This example creates a , which specifies a Linq serialization + /// strategy. This is passed to to apply the specified + /// serializer. + /// + /// (true, linqSerializerOptions: options) + /// .Where(b => b.Title == "War and Peace") + /// .AsEnumerable() + /// .FirstOrDefault(); + /// ]]> + /// + /// #if PREVIEW public #else internal - #endif - enum CosmosLinqSerializerType +#endif + enum CosmosLinqSerializerType { /// /// This honors Newtonsoft attributes, followed by DataContract attributes. This will ignore System.Text.Json attributes. diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index 62037f3aaf..e61cd85092 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -4,8 +4,6 @@ namespace Microsoft.Azure.Cosmos { - using Microsoft.Azure.Cosmos.Linq; - /// /// This class provides a way to configure Linq Serialization Properties /// diff --git a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs index ecf5c2266a..6ebcef4843 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs @@ -9,6 +9,64 @@ namespace Microsoft.Azure.Cosmos /// This interface can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) /// to be used by the CosmosClient for LINQ queries. /// + /// + /// This example creates a with the ICosmosLinqSerializer interface implemented. + /// This example custom serializer will honor System.Text.Json attributes. + /// strategy. This + /// + /// (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)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + /// } + /// } + /// + /// public override Stream ToStream(T input) + /// { + /// MemoryStream streamPayload = new MemoryStream(); + /// this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default); + /// streamPayload.Position = 0; + /// return streamPayload; + /// } + /// + /// public string SerializeMemberName(MemberInfo memberInfo) + /// { + /// JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); + /// + /// string memberName = !string.IsNullOrEmpty(jsonPropertyNameAttribute?.Name) + /// ? jsonPropertyNameAttribute.Name + /// : memberInfo.Name; + /// + /// return memberName; + /// } + /// } + /// ]]> + /// + /// #if PREVIEW public #else From 4153a5ed7ca14172d477be0d74f4f5d8da26d456 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 3 Jan 2024 11:01:03 -0800 Subject: [PATCH 42/48] Enum rename and interfact to abstract class --- .../src/Linq/CosmosLinqSerializerType.cs | 6 +-- .../src/Linq/CustomCosmosLinqSerializer.cs | 9 ++-- .../src/Linq/TranslationContext.cs | 4 +- ...qSerializer.cs => CosmosLinqSerializer.cs} | 14 +++--- .../CosmosLinqSerializerOptionsInternal.cs | 31 +++++++------ ...inqAggregateCustomSerializationBaseline.cs | 2 +- .../Linq/LinqTestsCommon.cs | 4 +- ...TranslationWithCustomSerializerBaseline.cs | 4 +- .../Contracts/DotNetPreviewSDKAPI.json | 43 +++++++++++++------ .../Linq/CosmosLinqJsonConverterTests.cs | 6 +-- 10 files changed, 69 insertions(+), 54 deletions(-) rename Microsoft.Azure.Cosmos/src/Serializer/{ICosmosLinqSerializer.cs => CosmosLinqSerializer.cs} (80%) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs index d97651219d..caf16ba4f8 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs @@ -14,7 +14,7 @@ namespace Microsoft.Azure.Cosmos /// (true, linqSerializerOptions: options) @@ -40,8 +40,8 @@ enum CosmosLinqSerializerType /// Uses the provided custom CosmosSerializer. /// This requires: /// 1. a to be provided on a client, and - /// 2. the custom CosmosSerializer implements + /// 2. the custom CosmosSerializer implements /// - CustomCosmosSerializer, + Custom, } } diff --git a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs index 89d92f4b50..03f7c9cff6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CustomCosmosLinqSerializer.cs @@ -11,11 +11,11 @@ namespace Microsoft.Azure.Cosmos.Linq internal class CustomCosmosLinqSerializer : ICosmosLinqSerializerInternal { - private readonly CosmosSerializer CustomCosmosSerializer; + private readonly CosmosLinqSerializer CustomCosmosSerializer; - public CustomCosmosLinqSerializer(CosmosSerializer customCosmosSerializer) + public CustomCosmosLinqSerializer(CosmosLinqSerializer customCosmosLinqSerializer) { - this.CustomCosmosSerializer = customCosmosSerializer; + this.CustomCosmosSerializer = customCosmosLinqSerializer; } public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType) @@ -35,8 +35,7 @@ public string SerializeScalarExpression(ConstantExpression inputExpression) public string SerializeMemberName(MemberInfo memberInfo) { - ICosmosLinqSerializer customLinqCosmosSerializer = (ICosmosLinqSerializer)this.CustomCosmosSerializer; - return customLinqCosmosSerializer.SerializeMemberName(memberInfo); + return this.CustomCosmosSerializer.SerializeMemberName(memberInfo); } private string SerializeWithCustomSerializer(object value) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 40f20daf6d..2572640dac 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -79,9 +79,9 @@ public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOpti this.subqueryBindingStack = new Stack(); this.Parameters = parameters; - if (linqSerializerOptionsInternal?.CustomCosmosSerializer != null) + if (linqSerializerOptionsInternal?.CustomCosmosLinqSerializer != null) { - this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(linqSerializerOptionsInternal.CustomCosmosSerializer); + this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(linqSerializerOptionsInternal.CustomCosmosLinqSerializer); this.MemberNames = new MemberNames(new CosmosLinqSerializerOptions()); } else diff --git a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs similarity index 80% rename from Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs rename to Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs index 6ebcef4843..6a87198478 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/ICosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs @@ -6,16 +6,16 @@ namespace Microsoft.Azure.Cosmos using System.Reflection; /// - /// This interface can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) + /// This abstract class can be implemented to allow a custom serializer (Non [Json.NET serializer](https://www.newtonsoft.com/json/help/html/Introduction.htm)'s) /// to be used by the CosmosClient for LINQ queries. /// /// - /// This example creates a with the ICosmosLinqSerializer interface implemented. + /// This example implements the CosmosLinqSerializer contract. /// This example custom serializer will honor System.Text.Json attributes. /// strategy. This /// /// (true); /// @@ -72,14 +72,14 @@ namespace Microsoft.Azure.Cosmos #else internal #endif - interface ICosmosLinqSerializer + abstract class CosmosLinqSerializer : CosmosSerializer { /// - /// Convert a to a string for use in LINQ query translation. + /// Convert a MemberInfo to a string for use in LINQ query translation. /// This must be implemented when using a custom serializer for LINQ queries. /// /// Any MemberInfo used in the query. /// A serialized representation of the member. - public string SerializeMemberName(MemberInfo memberInfo); + public abstract string SerializeMemberName(MemberInfo memberInfo); } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index 7a6572a380..262c9ffc2c 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -6,7 +6,6 @@ namespace Microsoft.Azure.Cosmos.Serializer { using System; using Microsoft.Azure.Cosmos; - using Microsoft.Azure.Cosmos.Linq; /// /// This class stores user-provided LINQ Serialization Properties. @@ -22,24 +21,24 @@ public static CosmosLinqSerializerOptionsInternal Create( { switch (cosmosLinqSerializerOptions.LinqSerializerType) { - case CosmosLinqSerializerType.CustomCosmosSerializer: - { - if (customCosmosSerializer == null) - { - throw new InvalidOperationException($"Must provide CosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + case CosmosLinqSerializerType.Custom: + { + if (customCosmosSerializer == null) + { + throw new InvalidOperationException($"Must provide CosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosLinqSerializer."); } - if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) - { - throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosSerializer."); + if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) + { + throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting linqSerializerOptions.CustomCosmosLinqSerializer."); } - if (customCosmosSerializer is not ICosmosLinqSerializer) + if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) { - throw new InvalidOperationException($"CosmosSerializer must implement ICosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosSerializer."); + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); } - return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customCosmosSerializer); + throw new InvalidOperationException($"CosmosSerializer must implement CosmosLinqSerializer if selecting linqSerializerOptions.CustomCosmosLinqSerializer."); } case CosmosLinqSerializerType.Default: { @@ -54,10 +53,10 @@ public static CosmosLinqSerializerOptionsInternal Create( private CosmosLinqSerializerOptionsInternal( CosmosLinqSerializerOptions cosmosLinqSerializerOptions, - CosmosSerializer customCosmosSerializer) + CosmosLinqSerializer customCosmosLinqSerializer) { this.CosmosLinqSerializerOptions = cosmosLinqSerializerOptions; - this.CustomCosmosSerializer = customCosmosSerializer; + this.CustomCosmosLinqSerializer = customCosmosLinqSerializer; } /// @@ -66,9 +65,9 @@ private CosmosLinqSerializerOptionsInternal( public CosmosLinqSerializerOptions CosmosLinqSerializerOptions { get; } /// - /// User defined customer serializer, if CosmosLinqSerializerType is CustomCosmosSerializer. + /// User defined customer serializer, if CosmosLinqSerializerType is CustomCosmosLinqSerializer. /// Otherwise set to null. /// - public CosmosSerializer CustomCosmosSerializer { get; } + public CosmosLinqSerializer CustomCosmosLinqSerializer { get; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs index 277438f552..c5fb7859f2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs @@ -73,7 +73,7 @@ static DataObjectDotNet createDataObj(int index, bool camelCase) CosmosLinqSerializerOptions linqSerializerOptions = new CosmosLinqSerializerOptions() { - LinqSerializerType = CosmosLinqSerializerType.CustomCosmosSerializer + LinqSerializerType = CosmosLinqSerializerType.Custom }; List>> getQueryList = new List>> diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index a5cad74876..7cc7a621de 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -841,7 +841,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } - class SystemTextJsonSerializer : CosmosSerializer, ICosmosLinqSerializer + class SystemTextJsonSerializer : CosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -879,7 +879,7 @@ public override Stream ToStream(T input) return streamPayload; } - public string SerializeMemberName(MemberInfo memberInfo) + public override string SerializeMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index d5089b43da..9977b5f956 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -73,7 +73,7 @@ public override LinqTestOutput ExecuteTest(LinqTestInput input) public void TestMemberInitializerDotNetCustomSerializer() { Func> getQuery; - (_, getQuery) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.CustomCosmosSerializer); + (_, getQuery) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Custom); string insertedData = this.GetInsertedData().Result; @@ -259,7 +259,7 @@ static T createDataObj(int index, bool camelCase) }; Func> getQueryCamelCase = null; - if (linqSerializerType != CosmosLinqSerializerType.CustomCosmosSerializer) + if (linqSerializerType != CosmosLinqSerializerType.Custom) { getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 0e2c52cb71..a949aa66f1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -352,6 +352,17 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -385,10 +396,10 @@ "Attributes": [], "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializerType CustomCosmosSerializer": { + "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Custom": { "Type": "Field", "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType CustomCosmosSerializer;IsInitOnly:False;IsStatic:True;" + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Custom;IsInitOnly:False;IsStatic:True;" }, "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Default": { "Type": "Field", @@ -398,6 +409,23 @@ }, "NestedTypes": {} }, + "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": { + "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { + "Subclasses": {}, + "Members": { + "System.String SerializeMemberName(System.Reflection.MemberInfo)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String SerializeMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + } + }, + "NestedTypes": {} + } + }, + "Members": {}, + "NestedTypes": {} + }, "Microsoft.Azure.Cosmos.DedicatedGatewayRequestOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { @@ -472,17 +500,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.ICosmosLinqSerializer;;IsAbstract:True;IsSealed:False;IsInterface:True;IsEnum:False;IsClass:False;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.String SerializeMemberName(System.Reflection.MemberInfo)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.String SerializeMemberName(System.Reflection.MemberInfo);IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.Linq.CosmosLinqExtensions;System.Object;IsAbstract:True;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 69ce16fa30..80f089ea9f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -28,7 +28,7 @@ public class CosmosLinqJsonConverterTests private readonly CosmosLinqSerializerOptions customPublicOptions = new() { - LinqSerializerType = CosmosLinqSerializerType.CustomCosmosSerializer + LinqSerializerType = CosmosLinqSerializerType.Custom }; [TestMethod] @@ -202,7 +202,7 @@ class DocumentWithExtensionData /// // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs /// - class TestCustomJsonSerializer : CosmosSerializer, ICosmosLinqSerializer + class TestCustomJsonSerializer : CosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -247,7 +247,7 @@ public override Stream ToStream(T input) return stream; } - public string SerializeMemberName(MemberInfo memberInfo) + public override string SerializeMemberName(MemberInfo memberInfo) { JsonPropertyNameAttribute jsonPropertyNameAttribute = memberInfo.GetCustomAttribute(true); From ee3afe867e98198586564381ba65a41995153215 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Wed, 3 Jan 2024 12:06:55 -0800 Subject: [PATCH 43/48] PR comments --- Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs | 3 ++- .../src/Serializer/CosmosLinqSerializer.cs | 1 - .../src/Serializer/CosmosLinqSerializerOptionsInternal.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs index caf16ba4f8..8b96f6367d 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs @@ -32,7 +32,8 @@ namespace Microsoft.Azure.Cosmos enum CosmosLinqSerializerType { /// - /// This honors Newtonsoft attributes, followed by DataContract attributes. This will ignore System.Text.Json attributes. + /// This honors default serializer (Newtonsoft.Json) attributes, followed by DataContract attributes. + /// This will ignore custom serializer related attributes (including System.Text.Json). /// Default, diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs index 6a87198478..e58abfcebd 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializer.cs @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos /// /// This example implements the CosmosLinqSerializer contract. /// This example custom serializer will honor System.Text.Json attributes. - /// strategy. This /// /// Date: Thu, 4 Jan 2024 11:23:39 -0800 Subject: [PATCH 44/48] PR comments --- .../src/Serializer/CosmosLinqSerializerOptionsInternal.cs | 6 +++--- Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index acbe0665ad..ad7c4efa77 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -25,12 +25,12 @@ public static CosmosLinqSerializerOptionsInternal Create( { if (customCosmosSerializer == null) { - throw new InvalidOperationException($"Must provide CosmosLinqSerializer if selecting CosmosLinqSerializerType.Custom."); + throw new InvalidOperationException($"Must provide CosmosLinqSerializer if selecting CosmosLinqSerializerType.Custom. See https://aka.ms/CosmosDB/dotnetlinq for more information on custom serialization in LINQ."); } if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) { - throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting CosmosLinqSerializerType.Custom."); + throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting CosmosLinqSerializerType.Custom. See https://aka.ms/CosmosDB/dotnetlinq for more information on custom serialization in LINQ."); } if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) @@ -38,7 +38,7 @@ public static CosmosLinqSerializerOptionsInternal Create( return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); } - throw new InvalidOperationException($"CosmosSerializer must implement CosmosLinqSerializer if selecting CosmosLinqSerializerType.Custom."); + throw new InvalidOperationException($"CosmosSerializer must implement CosmosLinqSerializer if selecting CosmosLinqSerializerType.Custom. See https://aka.ms/CosmosDB/dotnetlinq for more information on custom serialization in LINQ."); } case CosmosLinqSerializerType.Default: { diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs index 3df075f105..e6bd34d1f5 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs @@ -7,8 +7,11 @@ namespace Microsoft.Azure.Cosmos using System.IO; /// - /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient for CRUD operations. + /// This abstract class can be implemented to allow a custom serializer to be used by the CosmosClient. /// + /// + /// To use the custom serializer for LINQ queries, must be implemented. + /// public abstract class CosmosSerializer { /// From 8efedf1300d03220c2637b6e8c422ba2235db2bf Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Thu, 4 Jan 2024 13:04:52 -0800 Subject: [PATCH 45/48] Remove CosmosLinqSerializerType --- .../src/Linq/CosmosLinqSerializerType.cs | 48 ----------- .../Serializer/CosmosLinqSerializerOptions.cs | 15 ---- .../CosmosLinqSerializerOptionsInternal.cs | 41 +++------- ...inqAggregateCustomSerializationBaseline.cs | 39 ++++++--- .../Linq/LinqTestsCommon.cs | 43 +++++++++- ...TranslationWithCustomSerializerBaseline.cs | 65 ++++++++------- .../Contracts/DotNetPreviewSDKAPI.json | 46 ----------- .../Linq/CosmosLinqJsonConverterTests.cs | 79 ++++++++++++++----- 8 files changed, 180 insertions(+), 196 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs diff --git a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs b/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs deleted file mode 100644 index 8b96f6367d..0000000000 --- a/Microsoft.Azure.Cosmos/src/Linq/CosmosLinqSerializerType.cs +++ /dev/null @@ -1,48 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos -{ - /// - /// Serializer type to be used for LINQ query translations. - /// - /// - /// This example creates a , which specifies a Linq serialization - /// strategy. This is passed to to apply the specified - /// serializer. - /// - /// (true, linqSerializerOptions: options) - /// .Where(b => b.Title == "War and Peace") - /// .AsEnumerable() - /// .FirstOrDefault(); - /// ]]> - /// - /// -#if PREVIEW - public -#else - internal -#endif - enum CosmosLinqSerializerType - { - /// - /// This honors default serializer (Newtonsoft.Json) attributes, followed by DataContract attributes. - /// This will ignore custom serializer related attributes (including System.Text.Json). - /// - Default, - - /// - /// Uses the provided custom CosmosSerializer. - /// This requires: - /// 1. a to be provided on a client, and - /// 2. the custom CosmosSerializer implements - /// - Custom, - } -} diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs index e61cd85092..3b215a9cc8 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptions.cs @@ -16,7 +16,6 @@ public sealed class CosmosLinqSerializerOptions public CosmosLinqSerializerOptions() { this.PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default; - this.LinqSerializerType = CosmosLinqSerializerType.Default; } /// @@ -27,19 +26,5 @@ public CosmosLinqSerializerOptions() /// The default value is CosmosPropertyNamingPolicy.Default /// public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } - - /// - /// Specifies the type of serializer to be used for LINQ translations. - /// Options are detailed in - /// - /// - /// The default value is CosmosLinqSerializerType.Default - /// - #if PREVIEW - public - #else - internal - #endif - CosmosLinqSerializerType LinqSerializerType { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs index ad7c4efa77..638c05c47b 100644 --- a/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosLinqSerializerOptionsInternal.cs @@ -18,36 +18,19 @@ internal sealed class CosmosLinqSerializerOptionsInternal public static CosmosLinqSerializerOptionsInternal Create( CosmosLinqSerializerOptions cosmosLinqSerializerOptions, CosmosSerializer customCosmosSerializer) - { - switch (cosmosLinqSerializerOptions.LinqSerializerType) - { - case CosmosLinqSerializerType.Custom: + { + if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) + { + if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) { - if (customCosmosSerializer == null) - { - throw new InvalidOperationException($"Must provide CosmosLinqSerializer if selecting CosmosLinqSerializerType.Custom. See https://aka.ms/CosmosDB/dotnetlinq for more information on custom serialization in LINQ."); - } - - if (cosmosLinqSerializerOptions.PropertyNamingPolicy != CosmosPropertyNamingPolicy.Default) - { - throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if selecting CosmosLinqSerializerType.Custom. See https://aka.ms/CosmosDB/dotnetlinq for more information on custom serialization in LINQ."); - } - - if (customCosmosSerializer is CosmosLinqSerializer customQueryCosmosSerializer) - { - return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); - } + throw new InvalidOperationException($"CosmosPropertyNamingPolicy must be CosmosPropertyNamingPolicy.Default if using custom serializer for LINQ translations. See https://aka.ms/CosmosDB/dotnetlinq for more information."); + } - throw new InvalidOperationException($"CosmosSerializer must implement CosmosLinqSerializer if selecting CosmosLinqSerializerType.Custom. See https://aka.ms/CosmosDB/dotnetlinq for more information on custom serialization in LINQ."); - } - case CosmosLinqSerializerType.Default: - { - return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, null); - } - default: - { - throw new InvalidOperationException("Unsupported CosmosLinqSerializerType value."); - } + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, customQueryCosmosSerializer); + } + else + { + return new CosmosLinqSerializerOptionsInternal(cosmosLinqSerializerOptions, null); } } @@ -65,7 +48,7 @@ private CosmosLinqSerializerOptionsInternal( public CosmosLinqSerializerOptions CosmosLinqSerializerOptions { get; } /// - /// User defined customer serializer, if CosmosLinqSerializerType is CustomCosmosLinqSerializer. + /// User defined customer serializer, if one exists. /// Otherwise set to null. /// public CosmosLinqSerializer CustomCosmosLinqSerializer { get; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs index c5fb7859f2..f7be777aee 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqAggregateCustomSerializationBaseline.cs @@ -9,7 +9,6 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.Azure.Cosmos.Services.Management.Tests.BaselineTest; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -19,16 +18,35 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests [Microsoft.Azure.Cosmos.SDK.EmulatorTests.TestClass] public class LinqAggregateCustomSerializationBaseline : BaselineTests { + private static CosmosSerializer customCosmosLinqSerializer; + private static CosmosClient clientLinq; + private static Cosmos.Database testDbLinq; + private static Container testContainerLinq; + private static IQueryable lastExecutedScalarQuery; + private static CosmosSerializer customCosmosSerializer; private static CosmosClient client; private static Cosmos.Database testDb; private static Container testContainer; - private static IQueryable lastExecutedScalarQuery; [ClassInitialize] public async static Task Initialize(TestContext textContext) { + customCosmosLinqSerializer = new SystemTextJsonLinqSerializer(new JsonSerializerOptions()); + clientLinq = TestCommon.CreateCosmosClient((cosmosClientBuilder) + => cosmosClientBuilder.WithCustomSerializer(customCosmosLinqSerializer)); + + // Set a callback to get the handle of the last executed query to do the verification + // This is neede because aggregate queries return type is a scalar so it can't be used + // to verify the translated LINQ directly as other queries type. + clientLinq.DocumentClient.OnExecuteScalarQueryCallback = q => lastExecutedScalarQuery = q; + + string dbName = $"{nameof(LinqAggregateCustomSerializationBaseline)}-{Guid.NewGuid().ToString("N")}"; + testDbLinq = await clientLinq.CreateDatabaseAsync(dbName); + testContainerLinq = testDbLinq.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")).Result; + customCosmosSerializer = new SystemTextJsonSerializer(new JsonSerializerOptions()); + client = TestCommon.CreateCosmosClient((cosmosClientBuilder) => cosmosClientBuilder.WithCustomSerializer(customCosmosSerializer)); @@ -37,15 +55,21 @@ public async static Task Initialize(TestContext textContext) // to verify the translated LINQ directly as other queries type. client.DocumentClient.OnExecuteScalarQueryCallback = q => lastExecutedScalarQuery = q; - string dbName = $"{nameof(LinqAggregateCustomSerializationBaseline)}-{Guid.NewGuid().ToString("N")}"; + dbName = $"{nameof(LinqAggregateCustomSerializationBaseline)}-{Guid.NewGuid().ToString("N")}"; testDb = await client.CreateDatabaseAsync(dbName); - testContainer = testDb.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")).Result; } [ClassCleanup] public async static Task CleanUp() { + if (testDbLinq != null) + { + await testDbLinq.DeleteStreamAsync(); + } + + clientLinq?.Dispose(); + if (testDb != null) { await testDb.DeleteStreamAsync(); @@ -71,14 +95,9 @@ static DataObjectDotNet createDataObj(int index, bool camelCase) return obj; } - CosmosLinqSerializerOptions linqSerializerOptions = new CosmosLinqSerializerOptions() - { - LinqSerializerType = CosmosLinqSerializerType.Custom - }; - List>> getQueryList = new List>> { - LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, linqSerializerOptions), + LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainerLinq, new CosmosLinqSerializerOptions()), LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, 5, testContainer, new CosmosLinqSerializerOptions()) }; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs index 7cc7a621de..8cc819014a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Linq/LinqTestsCommon.cs @@ -841,11 +841,11 @@ public override void SerializeAsXml(XmlWriter xmlWriter) } } - class SystemTextJsonSerializer : CosmosLinqSerializer + class SystemTextJsonLinqSerializer : CosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; - public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions) + public SystemTextJsonLinqSerializer(JsonSerializerOptions jsonSerializerOptions) { this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); } @@ -889,5 +889,44 @@ public override string SerializeMemberName(MemberInfo memberInfo) return memberName; } + } + + class SystemTextJsonSerializer : CosmosSerializer + { + private readonly JsonObjectSerializer systemTextJsonSerializer; + + public SystemTextJsonSerializer(JsonSerializerOptions jsonSerializerOptions) + { + this.systemTextJsonSerializer = new JsonObjectSerializer(jsonSerializerOptions); + } + + public override T FromStream(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)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + } + } + + public override Stream ToStream(T input) + { + MemoryStream streamPayload = new MemoryStream(); + this.systemTextJsonSerializer.Serialize(streamPayload, input, input.GetType(), default); + streamPayload.Position = 0; + return streamPayload; + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs index 9977b5f956..817b030a60 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/LinqTranslationWithCustomSerializerBaseline.cs @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests using System.Text.Json.Serialization; using System.Threading.Tasks; using BaselineTest; - using Microsoft.Azure.Cosmos.Linq; using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -24,6 +23,10 @@ namespace Microsoft.Azure.Cosmos.Services.Management.Tests.LinqProviderTests [SDK.EmulatorTests.TestClass] public class LinqTranslationWithCustomSerializerBaseline : BaselineTests { + private static CosmosClient CosmosLinqClient; + private static Database TestDbLinq; + private static Container TestLinqContainer; + private static CosmosClient CosmosClient; private static Database TestDb; private static Container TestContainer; @@ -36,8 +39,14 @@ public class LinqTranslationWithCustomSerializerBaseline : BaselineTests cosmosClientBuilder.WithCustomSerializer(new SystemTextJsonLinqSerializer(new JsonSerializerOptions()))); + + string dbNameLinq = $"{nameof(LinqTranslationBaselineTests)}-{Guid.NewGuid():N}"; + TestDbLinq = await CosmosLinqClient.CreateDatabaseAsync(dbNameLinq); + CosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) - => cosmosClientBuilder.WithCustomSerializer(new SystemTextJsonSerializer(new JsonSerializerOptions()))); + => cosmosClientBuilder.WithCustomSerializer(new SystemTextJsonSerializer(new JsonSerializerOptions()))); string dbName = $"{nameof(LinqTranslationBaselineTests)}-{Guid.NewGuid():N}"; TestDb = await CosmosClient.CreateDatabaseAsync(dbName); @@ -46,6 +55,11 @@ public async static Task Initialize(TestContext textContext) [ClassCleanup] public async static Task Cleanup() { + if (TestDbLinq != null) + { + await TestDbLinq.DeleteStreamAsync(); + } + if (TestDb != null) { await TestDb.DeleteStreamAsync(); @@ -55,12 +69,14 @@ public async static Task Cleanup() [TestInitialize] public async Task TestInitialize() { + TestLinqContainer = await TestDbLinq.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")); TestContainer = await TestDb.CreateContainerAsync(new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: "/Pk")); } [TestCleanup] public async Task TestCleanup() { + await TestLinqContainer.DeleteContainerStreamAsync(); await TestContainer.DeleteContainerStreamAsync(); } @@ -73,9 +89,9 @@ public override LinqTestOutput ExecuteTest(LinqTestInput input) public void TestMemberInitializerDotNetCustomSerializer() { Func> getQuery; - (_, getQuery) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Custom); + (_, getQuery) = this.InsertDataAndGetQueryables(true, TestLinqContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestLinqContainer).Result; List inputs = new List { @@ -96,9 +112,9 @@ public void TestMemberInitializerNewtonsoft() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -125,9 +141,9 @@ public void TestMemberInitializerDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -154,9 +170,9 @@ public void TestMemberInitializerNewtonsoftDotNet() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -183,9 +199,9 @@ public void TestMemberInitializerNewtonsoftDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -212,9 +228,9 @@ public void TestMemberInitializerDotNetDataMember() { Func> getQueryCamelCase; Func> getQueryDefault; - (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(CosmosLinqSerializerType.Default); + (getQueryCamelCase, getQueryDefault) = this.InsertDataAndGetQueryables(false, TestContainer); - string insertedData = this.GetInsertedData().Result; + string insertedData = this.GetInsertedData(TestContainer).Result; List inputs = new List(); foreach (bool useCamelCaseSerializer in new bool[] { true, false }) @@ -236,7 +252,7 @@ public void TestMemberInitializerDotNetDataMember() this.ExecuteTestSuite(inputs); } - private (Func>, Func>) InsertDataAndGetQueryables(CosmosLinqSerializerType linqSerializerType) where T : LinqTestObject + private (Func>, Func>) InsertDataAndGetQueryables(bool customSerializer, Container container) where T : LinqTestObject { static T createDataObj(int index, bool camelCase) { @@ -250,29 +266,25 @@ static T createDataObj(int index, bool camelCase) CosmosLinqSerializerOptions linqSerializerOptionsCamelCase = new CosmosLinqSerializerOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, - LinqSerializerType = linqSerializerType }; - CosmosLinqSerializerOptions linqSerializerOptionsDefault = new CosmosLinqSerializerOptions - { - LinqSerializerType = linqSerializerType - }; + CosmosLinqSerializerOptions linqSerializerOptionsDefault = new CosmosLinqSerializerOptions(); Func> getQueryCamelCase = null; - if (linqSerializerType != CosmosLinqSerializerType.Custom) + if (!customSerializer) { - getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsCamelCase); + getQueryCamelCase = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, container, linqSerializerOptionsCamelCase); } - Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, TestContainer, linqSerializerOptionsDefault); + Func> getQueryDefault = LinqTestsCommon.GenerateSerializationTestCosmosData(createDataObj, RecordCount, container, linqSerializerOptionsDefault); return (getQueryCamelCase, getQueryDefault); } - private async Task GetInsertedData() + private async Task GetInsertedData(Container container) { List insertedDataList = new List(); - using (FeedIterator feedIterator = TestContainer.GetItemQueryStreamIterator("SELECT * FROM c")) + using (FeedIterator feedIterator = container.GetItemQueryStreamIterator("SELECT * FROM c")) { while (feedIterator.HasMoreResults) { @@ -302,8 +314,7 @@ private async Task GetInsertedData() } } - string insertedData = JsonConvert.SerializeObject(insertedDataList.Select(item => item), new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented }); - return insertedData; + return JsonConvert.SerializeObject(insertedDataList.Select(item => item), new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented }); } private class DataObjectDotNet : LinqTestObject diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index a949aa66f1..94aae6155b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -363,52 +363,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "Microsoft.Azure.Cosmos.CosmosLinqSerializerType get_LinqSerializerType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializerType LinqSerializerType": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType LinqSerializerType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.CosmosLinqSerializerType get_LinqSerializerType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_LinqSerializerType(Microsoft.Azure.Cosmos.CosmosLinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.CosmosLinqSerializerType)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_LinqSerializerType(Microsoft.Azure.Cosmos.CosmosLinqSerializerType);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializerType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { - "Subclasses": {}, - "Members": { - "Int32 value__": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Int32 value__;IsInitOnly:False;IsStatic:False;" - }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Custom": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Custom;IsInitOnly:False;IsStatic:True;" - }, - "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Default": { - "Type": "Field", - "Attributes": [], - "MethodInfo": "Microsoft.Azure.Cosmos.CosmosLinqSerializerType Default;IsInitOnly:False;IsStatic:True;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.CosmosSerializer;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": { "Microsoft.Azure.Cosmos.CosmosLinqSerializer;Microsoft.Azure.Cosmos.CosmosSerializer;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs index 80f089ea9f..0931271d92 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Linq/CosmosLinqJsonConverterTests.cs @@ -21,15 +21,7 @@ namespace Microsoft.Azure.Cosmos.Linq [TestClass] public class CosmosLinqJsonConverterTests { - private readonly CosmosLinqSerializerOptions defaultPublicOptions = new() - { - LinqSerializerType = CosmosLinqSerializerType.Default - }; - - private readonly CosmosLinqSerializerOptions customPublicOptions = new() - { - LinqSerializerType = CosmosLinqSerializerType.Custom - }; + private readonly CosmosLinqSerializerOptions defaultOptions = new(); [TestMethod] public void DateTimeKindIsPreservedTest() @@ -50,8 +42,8 @@ public void DateTimeKindIsPreservedTest() [TestMethod] public void EnumIsPreservedAsINTest() { - CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); - CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonSerializer()); TestEnum[] values = new[] { TestEnum.One, TestEnum.Two }; @@ -68,8 +60,8 @@ public void EnumIsPreservedAsINTest() [TestMethod] public void EnumIsPreservedAsEQUALSTest() { - CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); - CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonSerializer()); TestEnum statusValue = TestEnum.One; @@ -86,8 +78,8 @@ public void EnumIsPreservedAsEQUALSTest() [TestMethod] public void EnumIsPreservedAsEXPRESSIONTest() { - CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); - CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal options = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonSerializer()); // Get status constant ConstantExpression status = Expression.Constant(TestEnum.One); @@ -171,7 +163,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s [TestMethod] public void TestNewtonsoftExtensionDataQuery() { - CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultPublicOptions, null); + CosmosLinqSerializerOptionsInternal defaultOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, null); Expression> expr = a => (string)a.NewtonsoftExtensionData["foo"] == "bar"; string sql = SqlTranslator.TranslateExpression(expr.Body, defaultOptions); @@ -182,7 +174,7 @@ public void TestNewtonsoftExtensionDataQuery() [TestMethod] public void TestSystemTextJsonExtensionDataQuery() { - CosmosLinqSerializerOptionsInternal dotNetOptions = CosmosLinqSerializerOptionsInternal.Create(this.customPublicOptions, new TestCustomJsonSerializer()); + CosmosLinqSerializerOptionsInternal dotNetOptions = CosmosLinqSerializerOptionsInternal.Create(this.defaultOptions, new TestCustomJsonLinqSerializer()); Expression> expr = a => ((object)a.NetExtensionData["foo"]) == "bar"; string sql = SqlTranslator.TranslateExpression(expr.Body, dotNetOptions); @@ -202,7 +194,7 @@ class DocumentWithExtensionData /// // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs /// - class TestCustomJsonSerializer : CosmosLinqSerializer + class TestCustomJsonLinqSerializer : CosmosLinqSerializer { private readonly JsonObjectSerializer systemTextJsonSerializer; @@ -215,7 +207,7 @@ class TestCustomJsonSerializer : CosmosLinqSerializer } }; - public TestCustomJsonSerializer() + public TestCustomJsonLinqSerializer() { this.systemTextJsonSerializer = new JsonObjectSerializer(JsonOptions); } @@ -258,5 +250,54 @@ public override string SerializeMemberName(MemberInfo memberInfo) return memberName; } } + + /// + // See: https://github.com/Azure/azure-cosmos-dotnet-v3/blob/master/Microsoft.Azure.Cosmos.Samples/Usage/SystemTextJson/CosmosSystemTextJsonSerializer.cs + /// + class TestCustomJsonSerializer : CosmosSerializer + { + private readonly JsonObjectSerializer systemTextJsonSerializer; + + public static readonly System.Text.Json.JsonSerializerOptions JsonOptions = new() + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + Converters = { + new System.Text.Json.Serialization.JsonStringEnumConverter(), + } + }; + + public TestCustomJsonSerializer() + { + this.systemTextJsonSerializer = new JsonObjectSerializer(JsonOptions); + } + + public override T FromStream(Stream stream) + { + using (stream) + { + if (stream.CanSeek && stream.Length == 0) + { + return default; + } + + if (typeof(Stream).IsAssignableFrom(typeof(T))) + { + return (T)(object)stream; + } + + return (T)this.systemTextJsonSerializer.Deserialize(stream, typeof(T), default); + } + } + + public override Stream ToStream(T input) + { + MemoryStream stream = new(); + + this.systemTextJsonSerializer.Serialize(stream, input, input.GetType(), default); + stream.Position = 0; + return stream; + } + } } } From fa402f90ccc58a9fe3fe03629848d6ae31823425 Mon Sep 17 00:00:00 2001 From: Maya-Painter Date: Fri, 5 Jan 2024 10:29:19 -0800 Subject: [PATCH 46/48] last one (hopefully) --- Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 2572640dac..3ea9512a0a 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -68,6 +68,10 @@ internal sealed class TranslationContext /// private Stack subqueryBindingStack; + private static readonly ICosmosLinqSerializerInternal DefaultLinqSerializer = new DefaultCosmosLinqSerializer(new CosmosLinqSerializerOptions().PropertyNamingPolicy); + + private static readonly MemberNames DefaultMemberNames = new MemberNames(new CosmosLinqSerializerOptions()); + public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOptionsInternal, IDictionary parameters = null) { this.InScope = new HashSet(); @@ -84,13 +88,18 @@ public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOpti this.CosmosLinqSerializer = new CustomCosmosLinqSerializer(linqSerializerOptionsInternal.CustomCosmosLinqSerializer); this.MemberNames = new MemberNames(new CosmosLinqSerializerOptions()); } - else + else if (linqSerializerOptionsInternal?.CosmosLinqSerializerOptions != null) { CosmosLinqSerializerOptions linqSerializerOptions = linqSerializerOptionsInternal?.CosmosLinqSerializerOptions ?? new CosmosLinqSerializerOptions(); this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy); this.MemberNames = new MemberNames(linqSerializerOptions); } + else + { + this.CosmosLinqSerializer = TranslationContext.DefaultLinqSerializer; + this.MemberNames = DefaultMemberNames; + } } public Expression LookupSubstitution(ParameterExpression parameter) From 7e2bbd591a5fec9fd4ba9b61ba7069a0ae6f9ad9 Mon Sep 17 00:00:00 2001 From: Maya Painter <130110800+Maya-Painter@users.noreply.github.com> Date: Fri, 5 Jan 2024 10:40:00 -0800 Subject: [PATCH 47/48] Update Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs Co-authored-by: Matias Quaranta --- Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index 3ea9512a0a..eb3f3d24e6 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -90,7 +90,7 @@ public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOpti } else if (linqSerializerOptionsInternal?.CosmosLinqSerializerOptions != null) { - CosmosLinqSerializerOptions linqSerializerOptions = linqSerializerOptionsInternal?.CosmosLinqSerializerOptions ?? new CosmosLinqSerializerOptions(); + CosmosLinqSerializerOptions linqSerializerOptions = linqSerializerOptionsInternal.CosmosLinqSerializerOptions; this.CosmosLinqSerializer = new DefaultCosmosLinqSerializer(linqSerializerOptions.PropertyNamingPolicy); this.MemberNames = new MemberNames(linqSerializerOptions); From 090642ae8a34cb8e0bf29cc620a2e6d4e60e4150 Mon Sep 17 00:00:00 2001 From: Maya Painter <130110800+Maya-Painter@users.noreply.github.com> Date: Fri, 5 Jan 2024 10:40:11 -0800 Subject: [PATCH 48/48] Update Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs Co-authored-by: Matias Quaranta --- Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs index eb3f3d24e6..3b1d9cd20b 100644 --- a/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs @@ -98,7 +98,7 @@ public TranslationContext(CosmosLinqSerializerOptionsInternal linqSerializerOpti else { this.CosmosLinqSerializer = TranslationContext.DefaultLinqSerializer; - this.MemberNames = DefaultMemberNames; + this.MemberNames = TranslationContext.DefaultMemberNames; } }