diff --git a/src/EFCore.Relational.Specification.Tests/Query/GearsOfWarQueryRelationalFixture.cs b/src/EFCore.Relational.Specification.Tests/Query/GearsOfWarQueryRelationalFixture.cs index afdb01d930c..f0566c3696a 100644 --- a/src/EFCore.Relational.Specification.Tests/Query/GearsOfWarQueryRelationalFixture.cs +++ b/src/EFCore.Relational.Specification.Tests/Query/GearsOfWarQueryRelationalFixture.cs @@ -14,7 +14,7 @@ public abstract class GearsOfWarQueryRelationalFixture : GearsOfWarQueryFixtureB public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) => base.AddOptions(builder).ConfigureWarnings( - c => c - .Log(RelationalEventId.QueryClientEvaluationWarning)); + c => c.Log(RelationalEventId.QueryClientEvaluationWarning) + .Log(RelationalEventId.ValueConversionSqlLiteralWarning)); } } diff --git a/src/EFCore.Relational.Specification.Tests/Query/NorthwindQueryRelationalFixture.cs b/src/EFCore.Relational.Specification.Tests/Query/NorthwindQueryRelationalFixture.cs index 64f50e4b0b7..a71a32dd7d7 100644 --- a/src/EFCore.Relational.Specification.Tests/Query/NorthwindQueryRelationalFixture.cs +++ b/src/EFCore.Relational.Specification.Tests/Query/NorthwindQueryRelationalFixture.cs @@ -22,7 +22,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build c => c .Log(RelationalEventId.QueryClientEvaluationWarning) .Log(RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning) - .Log(RelationalEventId.QueryPossibleExceptionWithAggregateOperator)); + .Log(RelationalEventId.QueryPossibleExceptionWithAggregateOperator) + .Log(RelationalEventId.ValueConversionSqlLiteralWarning)); protected override Type ContextType => typeof(NorthwindRelationalContext); } diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index dec430baa75..12c8ce1bfad 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -64,6 +64,7 @@ private enum Id QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500, QueryPossibleUnintendedUseOfEqualsWarning, QueryPossibleExceptionWithAggregateOperator, + ValueConversionSqlLiteralWarning, // Model validation events ModelValidationKeyDefaultValueWarning = CoreEventId.RelationalBaseId + 600, @@ -463,6 +464,16 @@ private enum Id /// public static readonly EventId QueryPossibleExceptionWithAggregateOperator = MakeQueryId(Id.QueryPossibleExceptionWithAggregateOperator); + /// + /// + /// A SQL literal is being generated for a value that is using a value conversion. + /// + /// + /// This event is in the category. + /// + /// + public static readonly EventId ValueConversionSqlLiteralWarning = MakeQueryId(Id.ValueConversionSqlLiteralWarning); + private static readonly string _validationPrefix = DbLoggerCategory.Model.Validation.Name + "."; private static EventId MakeValidationId(Id id) => new EventId((int)id, _validationPrefix + id); diff --git a/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs index 1ba7586f60d..e965821b0b0 100644 --- a/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Internal/RelationalLoggerExtensions.cs @@ -17,6 +17,7 @@ using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Update; using Remotion.Linq; @@ -1252,6 +1253,46 @@ public static void QueryPossibleExceptionWithAggregateOperator( } } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static void ValueConversionSqlLiteralWarning( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] Type mappingClrType, + [NotNull] ValueConverter valueConverter) + { + var definition = RelationalStrings.LogValueConversionSqlLiteralWarning; + + var warningBehavior = definition.GetLogBehavior(diagnostics); + if (warningBehavior != WarningBehavior.Ignore) + { + definition.Log(diagnostics, + warningBehavior, + mappingClrType.ShortDisplayName(), + valueConverter.GetType().ShortDisplayName()); + } + + if (diagnostics.DiagnosticSource.IsEnabled(definition.EventId.Name)) + { + diagnostics.DiagnosticSource.Write( + definition.EventId.Name, + new ValueConverterEventData( + definition, + ValueConversionSqlLiteral, + mappingClrType, + valueConverter)); + } + } + + private static string ValueConversionSqlLiteral(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (ValueConverterEventData)payload; + return d.GenerateMessage(p.ValueConverter); + } + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 40f6cc09878..1f59e94c286 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1000,6 +1000,19 @@ public static string DuplicateUniqueIndexValuesRemovedSensitive([CanBeNull] obje GetString("DuplicateUniqueIndexValuesRemovedSensitive", nameof(entityType), nameof(firstKeyValues), nameof(secondKeyValues), nameof(indexValue)), entityType, firstKeyValues, secondKeyValues, indexValue); + /// + /// A SQL parameter or literal was generated for the type '{type}' using the ValueConverter '{valueConverter}'. Review the generated SQL for correctness and consider evaluating the target expression in-memory instead. + /// + public static readonly EventDefinition LogValueConversionSqlLiteralWarning + = new EventDefinition( + RelationalEventId.ValueConversionSqlLiteralWarning, + LogLevel.Warning, + "RelationalEventId.ValueConversionSqlLiteralWarning", + LoggerMessage.Define( + LogLevel.Warning, + RelationalEventId.ValueConversionSqlLiteralWarning, + _resourceManager.GetString("LogValueConversionSqlLiteralWarning"))); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index cc4731ca3be..a62197a2717 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -479,4 +479,8 @@ The entities of type '{entityType}' with key values {firstKeyValues} and {secondKeyValues} had the same value for the unique index {indexValue}. Configure the index as non-unique if duplicates should be allowed. + + A SQL parameter or literal was generated for the type '{type}' using the ValueConverter '{valueConverter}'. Review the generated SQL for correctness and consider evaluating the target expression in-memory instead. + Warning RelationalEventId.ValueConversionSqlLiteralWarning object object + \ No newline at end of file diff --git a/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs b/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs index 8b221cb392d..6f47a58ef76 100644 --- a/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs @@ -36,6 +36,8 @@ public class DefaultQuerySqlGenerator : ThrowingExpressionVisitor, ISqlExpressio private ReducingExpressionVisitor _reducingExpressionVisitor; private BooleanExpressionTranslatingVisitor _booleanExpressionTranslatingVisitor; private InExpressionValuesExpandingVisitor _inExpressionValuesExpandingVisitor; + + private bool _valueConverterWarningsEnabled; private static readonly Dictionary _operatorMap = new Dictionary { @@ -241,6 +243,10 @@ public virtual Expression VisitSelect(SelectExpression selectExpression) _relationalCommandBuilder.Append("1"); } + var oldValueConverterWarningsEnabled = _valueConverterWarningsEnabled; + + _valueConverterWarningsEnabled = true; + if (selectExpression.Tables.Count > 0) { _relationalCommandBuilder.AppendLine() @@ -295,6 +301,8 @@ public virtual Expression VisitSelect(SelectExpression selectExpression) } } + _valueConverterWarningsEnabled = oldValueConverterWarningsEnabled; + return selectExpression; } @@ -818,6 +826,8 @@ private string GenerateSqlLiteral(object value) mapping = Dependencies.TypeMappingSource.GetMappingForValue(value); } + LogValueConversionWarning(mapping); + return mapping.GenerateSqlLiteral(value); } @@ -919,7 +929,13 @@ public virtual Expression VisitStringCompare(StringCompareExpression stringCompa /// public virtual Expression VisitIn(InExpression inExpression) { - GenerateIn(inExpression, negated: false); + var oldValueConverterWarningsEnabled = _valueConverterWarningsEnabled; + + _valueConverterWarningsEnabled = false; + + GenerateIn(inExpression); + + _valueConverterWarningsEnabled = oldValueConverterWarningsEnabled; return inExpression; } @@ -1175,8 +1191,14 @@ protected override Expression VisitConditional(ConditionalExpression conditional { _relationalCommandBuilder.Append("WHEN "); + var oldValueConverterWarningsEnabled = _valueConverterWarningsEnabled; + + _valueConverterWarningsEnabled = false; + Visit(conditionalExpression.Test); + _valueConverterWarningsEnabled = oldValueConverterWarningsEnabled; + _relationalCommandBuilder.AppendLine(); _relationalCommandBuilder.Append("THEN "); @@ -1248,6 +1270,13 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { Check.NotNull(binaryExpression, nameof(binaryExpression)); + var oldValueConverterWarningsEnabled = _valueConverterWarningsEnabled; + + _valueConverterWarningsEnabled + = _valueConverterWarningsEnabled + && binaryExpression.NodeType != ExpressionType.Equal + && binaryExpression.NodeType != ExpressionType.NotEqual; + switch (binaryExpression.NodeType) { case ExpressionType.Coalesce: @@ -1308,6 +1337,8 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) break; } + _valueConverterWarningsEnabled = oldValueConverterWarningsEnabled; + return binaryExpression; } @@ -1556,6 +1587,8 @@ public virtual Expression VisitExplicitCast(ExplicitCastExpression explicitCastE RelationalStrings.UnsupportedType(explicitCastExpression.Type.ShortDisplayName())); } + LogValueConversionWarning(typeMapping); + _relationalCommandBuilder.Append(typeMapping.StoreType); _relationalCommandBuilder.Append(")"); @@ -1652,10 +1685,16 @@ protected override Expression VisitParameter(ParameterExpression parameterExpres if (_relationalCommandBuilder.ParameterBuilder.Parameters .All(p => p.InvariantName != parameterExpression.Name)) { + var typeMapping + = _typeMapping + ?? Dependencies.TypeMappingSource.GetMapping(parameterExpression.Type); + + LogValueConversionWarning(typeMapping); + _relationalCommandBuilder.AddParameter( parameterExpression.Name, parameterName, - _typeMapping ?? Dependencies.TypeMappingSource.GetMapping(parameterExpression.Type), + typeMapping, parameterExpression.Type.IsNullableType()); } @@ -1663,6 +1702,15 @@ protected override Expression VisitParameter(ParameterExpression parameterExpres return parameterExpression; } + + private void LogValueConversionWarning(CoreTypeMapping typeMapping) + { + if (_valueConverterWarningsEnabled + && typeMapping.Converter != null) + { + Dependencies.Logger.ValueConversionSqlLiteralWarning(typeMapping.ClrType, typeMapping.Converter); + } + } /// /// Visits a PropertyParameterExpression. diff --git a/src/EFCore.Relational/Query/Sql/QuerySqlGeneratorDependencies.cs b/src/EFCore.Relational/Query/Sql/QuerySqlGeneratorDependencies.cs index cc4f6c83f69..69648cad0f2 100644 --- a/src/EFCore.Relational/Query/Sql/QuerySqlGeneratorDependencies.cs +++ b/src/EFCore.Relational/Query/Sql/QuerySqlGeneratorDependencies.cs @@ -3,6 +3,7 @@ using System; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -45,6 +46,7 @@ public sealed class QuerySqlGeneratorDependencies /// The parameter name generator factory. /// The relational type mapper. /// The type mapper. + /// The logger. public QuerySqlGeneratorDependencies( [NotNull] IRelationalCommandBuilderFactory commandBuilderFactory, [NotNull] ISqlGenerationHelper sqlGenerationHelper, @@ -52,13 +54,15 @@ public QuerySqlGeneratorDependencies( #pragma warning disable 618 [NotNull] IRelationalTypeMapper relationalTypeMapper, #pragma warning restore 618 - [NotNull] IRelationalTypeMappingSource typeMappingSource) + [NotNull] IRelationalTypeMappingSource typeMappingSource, + [NotNull] IDiagnosticsLogger logger) { Check.NotNull(commandBuilderFactory, nameof(commandBuilderFactory)); Check.NotNull(sqlGenerationHelper, nameof(sqlGenerationHelper)); Check.NotNull(parameterNameGeneratorFactory, nameof(parameterNameGeneratorFactory)); Check.NotNull(relationalTypeMapper, nameof(relationalTypeMapper)); Check.NotNull(typeMappingSource, nameof(typeMappingSource)); + Check.NotNull(logger, nameof(logger)); CommandBuilderFactory = commandBuilderFactory; SqlGenerationHelper = sqlGenerationHelper; @@ -67,6 +71,7 @@ public QuerySqlGeneratorDependencies( RelationalTypeMapper = relationalTypeMapper; #pragma warning restore 618 TypeMappingSource = typeMappingSource; + Logger = logger; } /// @@ -95,6 +100,11 @@ public QuerySqlGeneratorDependencies( /// public IRelationalTypeMappingSource TypeMappingSource { get; } + /// + /// The logger. + /// + public IDiagnosticsLogger Logger { get; } + /// /// Clones this dependency parameter object with one service replaced. /// @@ -108,7 +118,8 @@ public QuerySqlGeneratorDependencies With([NotNull] IRelationalCommandBuilderFac #pragma warning disable 618 RelationalTypeMapper, #pragma warning restore 618 - TypeMappingSource); + TypeMappingSource, + Logger); /// /// Clones this dependency parameter object with one service replaced. @@ -123,7 +134,8 @@ public QuerySqlGeneratorDependencies With([NotNull] ISqlGenerationHelper sqlGene #pragma warning disable 618 RelationalTypeMapper, #pragma warning restore 618 - TypeMappingSource); + TypeMappingSource, + Logger); /// /// Clones this dependency parameter object with one service replaced. @@ -138,7 +150,8 @@ public QuerySqlGeneratorDependencies With([NotNull] IParameterNameGeneratorFacto #pragma warning disable 618 RelationalTypeMapper, #pragma warning restore 618 - TypeMappingSource); + TypeMappingSource, + Logger); /// /// Clones this dependency parameter object with one service replaced. @@ -152,7 +165,8 @@ public QuerySqlGeneratorDependencies With([NotNull] IRelationalTypeMapper relati SqlGenerationHelper, ParameterNameGeneratorFactory, relationalTypeMapper, - TypeMappingSource); + TypeMappingSource, + Logger); /// /// Clones this dependency parameter object with one service replaced. @@ -167,6 +181,23 @@ public QuerySqlGeneratorDependencies With([NotNull] IRelationalTypeMappingSource #pragma warning disable 618 RelationalTypeMapper, #pragma warning restore 618 - typeMappingSource); + typeMappingSource, + Logger); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public QuerySqlGeneratorDependencies With([NotNull] IDiagnosticsLogger logger) + => new QuerySqlGeneratorDependencies( + CommandBuilderFactory, + SqlGenerationHelper, + ParameterNameGeneratorFactory, +#pragma warning disable 618 + RelationalTypeMapper, +#pragma warning restore 618 + TypeMappingSource, + logger); } } diff --git a/src/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs b/src/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs index d97418783e2..55fec845ae2 100644 --- a/src/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs +++ b/src/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Xunit; + namespace Microsoft.EntityFrameworkCore { public abstract class ConvertToProviderTypesTestBase : BuiltInDataTypesTestBase diff --git a/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs index c48e05f27d4..fb737712eaa 100644 --- a/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections; using System.Linq; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; diff --git a/src/EFCore/Diagnostics/ValueConverterEventData.cs b/src/EFCore/Diagnostics/ValueConverterEventData.cs new file mode 100644 index 00000000000..8d664ce337f --- /dev/null +++ b/src/EFCore/Diagnostics/ValueConverterEventData.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Microsoft.EntityFrameworkCore.Diagnostics +{ + /// + /// A event payload class for events that have + /// a . + /// + public class ValueConverterEventData : EventData + { + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The CLR type. + /// The . + public ValueConverterEventData( + [NotNull] EventDefinitionBase eventDefinition, + [NotNull] Func messageGenerator, + [NotNull] Type mappingClrType, + [NotNull] ValueConverter valueConverter) + : base(eventDefinition, messageGenerator) + { + MappingClrType = mappingClrType; + ValueConverter = valueConverter; + } + + /// + /// The CLR type. + /// + public Type MappingClrType { get; } + + /// + /// The . + /// + public virtual ValueConverter ValueConverter { get; } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs index da71d5df91d..29edb260783 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs @@ -3,7 +3,10 @@ #if !Test20 using System; +using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Xunit; @@ -18,6 +21,22 @@ public ConvertToProviderTypesSqlServerTest(ConvertToProviderTypesSqlServerFixtur { } + [ConditionalFact] + public virtual void Warning_when_suspicious_conversion_in_sql() + { + using (var context = CreateContext()) + { + Assert.Contains( + RelationalStrings.LogValueConversionSqlLiteralWarning + .GenerateMessage( + typeof(decimal).ShortDisplayName(), + new NumberToBytesConverter().GetType().ShortDisplayName()), + Assert.Throws( + () => + context.Set().Where(b => b.TestDecimal > 123.0m).ToList()).Message); + } + } + [ConditionalFact] public virtual void Columns_have_expected_data_types() { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 99f5fb6b928..78acd8cc903 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.TestModels.GearsOfWarModel; using Xunit; using Xunit.Abstractions; @@ -659,6 +662,13 @@ public override void Where_bitwise_and_nullable_enum_with_non_nullable_parameter SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([w].[AmmunitionType] & @__ammunitionType_0) > 0"); + + Assert.Contains( + RelationalStrings.LogValueConversionSqlLiteralWarning + .GenerateMessage( + typeof(AmmunitionType).ShortDisplayName(), + new EnumToNumberConverter().GetType().ShortDisplayName()), + Fixture.TestSqlLoggerFactory.Log); } public override void Where_bitwise_and_nullable_enum_with_nullable_parameter() @@ -677,6 +687,13 @@ FROM [Weapons] AS [w] SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapons] AS [w] WHERE ([w].[AmmunitionType] & @__ammunitionType_0) > 0"); + + Assert.Contains( + RelationalStrings.LogValueConversionSqlLiteralWarning + .GenerateMessage( + typeof(AmmunitionType).ShortDisplayName(), + new EnumToNumberConverter().GetType().ShortDisplayName()), + Fixture.TestSqlLoggerFactory.Log); } public override void Where_bitwise_or_enum() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs index 2423dbfff91..3d5f658f1d9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Xunit; namespace Microsoft.EntityFrameworkCore.Query