diff --git a/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs
index e8ca711a80b..5bb9de441eb 100644
--- a/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs
+++ b/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs
@@ -142,7 +142,16 @@ public Expression GetProjectionExpression(ProjectionMember member)
var readValueExpression = (MethodCallExpression)projection;
var index = (int)((ConstantExpression)readValueExpression.Arguments[1]).Value;
- return _valueBufferSlots[index];
+ var valueBufferSlotExpression = _valueBufferSlots[index];
+
+ var memberType = member.MemberType;
+ if (memberType != null
+ && memberType != valueBufferSlotExpression.Type)
+ {
+ valueBufferSlotExpression = Convert(valueBufferSlotExpression, memberType);
+ }
+
+ return valueBufferSlotExpression;
}
public LambdaExpression GetScalarProjectionLambda()
diff --git a/src/EFCore.InMemory/Query/Pipeline/Translator.cs b/src/EFCore.InMemory/Query/Pipeline/Translator.cs
index 7427fee4607..136683f19d0 100644
--- a/src/EFCore.InMemory/Query/Pipeline/Translator.cs
+++ b/src/EFCore.InMemory/Query/Pipeline/Translator.cs
@@ -54,7 +54,15 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var entityType = entityShaper.EntityType;
var property = entityType.FindProperty((string)((ConstantExpression)methodCallExpression.Arguments[1]).Value);
- return _inMemoryQueryExpression.BindProperty(entityShaper.ValueBufferExpression, property);
+ var boundPropertyExpression = _inMemoryQueryExpression.BindProperty(entityShaper.ValueBufferExpression, property);
+
+ if (boundPropertyExpression.Type
+ != methodCallExpression.Type)
+ {
+ boundPropertyExpression = Expression.Convert(boundPropertyExpression, methodCallExpression.Type);
+ }
+
+ return boundPropertyExpression;
}
}
diff --git a/src/EFCore.Relational/Query/Pipeline/ExpressionExtensions.cs b/src/EFCore.Relational/Query/Pipeline/ExpressionExtensions.cs
index 74d55c652b2..644eeba0b0d 100644
--- a/src/EFCore.Relational/Query/Pipeline/ExpressionExtensions.cs
+++ b/src/EFCore.Relational/Query/Pipeline/ExpressionExtensions.cs
@@ -1,22 +1,57 @@
// 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.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
{
public static class ExpressionExtensions
{
- public static RelationalTypeMapping InferTypeMapping(params Expression[] expressions)
+ public static RelationalTypeMapping InferTypeMapping(
+ IValueConverterSelector valueConverterSelector,
+ params Expression[] expressions)
{
for (var i = 0; i < expressions.Length; i++)
{
- if (expressions[i] is SqlExpression sql
- && sql.TypeMapping != null)
+ if (expressions[i] is SqlExpression sqlExpression)
{
- return sql.TypeMapping;
+ var mapping = sqlExpression.TypeMapping;
+ if (mapping != null)
+ {
+ return mapping;
+ }
+
+ // This is to cover the cases of enums and char values being erased when using
+ // literals in the expression tree. It adds a new converter onto the type mapper
+ // that takes the literal and converts it back to the type that the type mapping
+ // is expecting.
+ // This code is temporary until more complete type inference is completed.
+ if (sqlExpression is SqlUnaryExpression sqlUnaryExpression
+ && sqlUnaryExpression.OperatorType == ExpressionType.Convert)
+ {
+ var operandType = sqlUnaryExpression.Operand.Type.UnwrapNullableType();
+ mapping = InferTypeMapping(valueConverterSelector, sqlUnaryExpression.Operand);
+
+ if (mapping != null
+ && (operandType.IsEnum
+ || operandType == typeof(char)
+ || mapping.Converter?.ProviderClrType == typeof(byte[])))
+ {
+ if (mapping.ClrType != sqlUnaryExpression.Type)
+ {
+ var converter = valueConverterSelector.Select(sqlUnaryExpression.Type, mapping.ClrType).ToList();
+
+ mapping = (RelationalTypeMapping)mapping.Clone(converter.First().Create());
+ }
+
+ return mapping;
+ }
+ }
}
}
diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs
index 5bd4f2a5378..b2c19c045f7 100644
--- a/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressionFactory.cs
@@ -7,17 +7,22 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.Relational.Query.Pipeline
{
public class SqlExpressionFactory : ISqlExpressionFactory
{
private readonly IRelationalTypeMappingSource _typeMappingSource;
+ private readonly IValueConverterSelector _valueConverterSelector;
private readonly RelationalTypeMapping _boolTypeMapping;
- public SqlExpressionFactory(IRelationalTypeMappingSource typeMappingSource)
+ public SqlExpressionFactory(
+ IRelationalTypeMappingSource typeMappingSource,
+ IValueConverterSelector valueConverterSelector)
{
_typeMappingSource = typeMappingSource;
+ _valueConverterSelector = valueConverterSelector;
_boolTypeMapping = typeMappingSource.FindMapping(typeof(bool));
}
@@ -75,8 +80,9 @@ public SqlExpression ApplyTypeMapping(SqlExpression sqlExpression, RelationalTyp
private SqlExpression ApplyTypeMappingOnLike(LikeExpression likeExpression)
{
var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(
- likeExpression.Match, likeExpression.Pattern, likeExpression.EscapeChar)
- ?? _typeMappingSource.FindMapping(likeExpression.Match.Type);
+ _valueConverterSelector,
+ likeExpression.Match, likeExpression.Pattern, likeExpression.EscapeChar)
+ ?? _typeMappingSource.FindMapping(likeExpression.Match.Type);
return new LikeExpression(
ApplyTypeMapping(likeExpression.Match, inferredTypeMapping),
@@ -155,7 +161,7 @@ private SqlExpression ApplyTypeMappingOnSqlBinary(
case ExpressionType.LessThanOrEqual:
case ExpressionType.NotEqual:
{
- inferredTypeMapping = ExpressionExtensions.InferTypeMapping(left, right)
+ inferredTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, left, right)
?? _typeMappingSource.FindMapping(left.Type);
resultType = typeof(bool);
resultTypeMapping = _boolTypeMapping;
@@ -180,7 +186,7 @@ private SqlExpression ApplyTypeMappingOnSqlBinary(
case ExpressionType.And:
case ExpressionType.Or:
{
- inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(left, right);
+ inferredTypeMapping = typeMapping ?? ExpressionExtensions.InferTypeMapping(_valueConverterSelector, left, right);
resultType = left.Type;
resultTypeMapping = inferredTypeMapping;
}
diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
index 3d8a852c681..498847baef3 100644
--- a/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
+++ b/src/EFCore.Relational/Storage/RelationalTypeMappingInfo.cs
@@ -214,10 +214,18 @@ public RelationalTypeMappingInfo(
public bool? IsRowVersion => _coreTypeMappingInfo.IsRowVersion;
///
- /// The CLR type in the model.
+ /// The CLR type used to store the property in the model. This may be the backing field type.
+ /// May be null if type information is conveyed via other means (e.g. the store name in a relational type mapping info)
///
public Type ClrType => _coreTypeMappingInfo.ClrType;
+ ///
+ /// The CLR type of the property, which may be different from if a backing field of
+ /// a different type is used.
+ /// May be null if type information is conveyed via other means (e.g. the store name in a relational type mapping info)
+ ///
+ public Type DeclaredClrType => _coreTypeMappingInfo.DeclaredClrType;
+
///
/// Returns a new with the given converter applied.
///
diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs
index bc41ae5630f..3c23f3c0b71 100644
--- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs
+++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -126,54 +127,79 @@ private RelationalTypeMapping FindMappingWithConversion(
k =>
{
var (info, providerType, converter) = k;
+
+ var sourceType = info.ClrType;
+ var sourceDeclaredType = info.DeclaredClrType;
+
+ var needsConvertFromObject
+ = sourceType == typeof(object)
+ && sourceDeclaredType != typeof(object);
+
+ var effectiveSourceType = needsConvertFromObject ? sourceDeclaredType : sourceType;
+
+ ValueConverterInfo fromObjectConverter = default;
+ var fromObjectMappingInfo = info;
+
+ if (needsConvertFromObject)
+ {
+ fromObjectConverter = Dependencies.ValueConverterSelector
+ .Select(typeof(object), sourceDeclaredType)
+ .Single();
+
+ fromObjectMappingInfo = info.WithConverter(fromObjectConverter);
+ }
+
var mapping = providerType == null
- || providerType == info.ClrType
- ? FindMapping(info)
+ || providerType == effectiveSourceType
+ ? FindMapping(fromObjectMappingInfo)
: null;
- if (mapping == null)
+ if (mapping == null
+ && effectiveSourceType != null)
{
- var sourceType = info.ClrType;
-
- if (sourceType != null)
+ foreach (var converterInfo in Dependencies
+ .ValueConverterSelector
+ .Select(effectiveSourceType, providerType))
{
- foreach (var converterInfo in Dependencies
- .ValueConverterSelector
- .Select(sourceType, providerType))
- {
- var mappingInfoUsed = info.WithConverter(converterInfo);
- mapping = FindMapping(mappingInfoUsed);
+ var mappingInfoUsed = info.WithConverter(converterInfo);
+ mapping = FindMapping(mappingInfoUsed);
- if (mapping == null
- && providerType != null)
+ if (mapping == null
+ && providerType != null)
+ {
+ foreach (var secondConverterInfo in Dependencies
+ .ValueConverterSelector
+ .Select(providerType))
{
- foreach (var secondConverterInfo in Dependencies
- .ValueConverterSelector
- .Select(providerType))
- {
- mapping = FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo));
+ mapping = FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo));
- if (mapping != null)
- {
- mapping = (RelationalTypeMapping)mapping.Clone(secondConverterInfo.Create());
- break;
- }
+ if (mapping != null)
+ {
+ mapping = (RelationalTypeMapping)mapping.Clone(secondConverterInfo.Create());
+ break;
}
}
+ }
- if (mapping != null)
- {
- mapping = (RelationalTypeMapping)mapping.Clone(converterInfo.Create());
- break;
- }
+ if (mapping != null)
+ {
+ mapping = (RelationalTypeMapping)mapping.Clone(converterInfo.Create());
+ break;
}
}
}
- if (mapping != null
- && converter != null)
+ if (mapping != null)
{
- mapping = (RelationalTypeMapping)mapping.Clone(converter);
+ if (needsConvertFromObject)
+ {
+ mapping = (RelationalTypeMapping)mapping.Clone(fromObjectConverter.Create());
+ }
+
+ if (converter != null)
+ {
+ mapping = (RelationalTypeMapping)mapping.Clone(converter);
+ }
}
return mapping;
diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMethodTranslator.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMethodTranslator.cs
index 5b9c5ecad6a..5a98f31048d 100644
--- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMethodTranslator.cs
+++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerGeometryMethodTranslator.cs
@@ -10,6 +10,7 @@
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NetTopologySuite.Geometries;
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Pipeline
@@ -49,13 +50,16 @@ public class SqlServerGeometryMethodTranslator : IMethodCallTranslator
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IValueConverterSelector _valueConverterSelector;
public SqlServerGeometryMethodTranslator(
IRelationalTypeMappingSource typeMappingSource,
- ISqlExpressionFactory sqlExpressionFactory)
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
_typeMappingSource = typeMappingSource;
_sqlExpressionFactory = sqlExpressionFactory;
+ _valueConverterSelector = valueConverterSelector;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList arguments)
@@ -64,7 +68,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
{
var geometryExpressions = new[] { instance }.Concat(
arguments.Where(e => typeof(IGeometry).IsAssignableFrom(e.Type)));
- var typeMapping = ExpressionExtensions.InferTypeMapping(geometryExpressions.ToArray());
+ var typeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, geometryExpressions.ToArray());
Debug.Assert(typeMapping != null, "At least one argument must have typeMapping.");
var storeType = typeMapping.StoreType;
diff --git a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerNetTopologySuiteMethodCallTranslatorPlugin.cs b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerNetTopologySuiteMethodCallTranslatorPlugin.cs
index 88ac0181a07..9a651426f23 100644
--- a/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerNetTopologySuiteMethodCallTranslatorPlugin.cs
+++ b/src/EFCore.SqlServer.NTS/Query/Pipeline/SqlServerNetTopologySuiteMethodCallTranslatorPlugin.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Pipeline
@@ -30,12 +31,14 @@ public class SqlServerNetTopologySuiteMethodCallTranslatorPlugin : IMethodCallTr
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public SqlServerNetTopologySuiteMethodCallTranslatorPlugin(IRelationalTypeMappingSource typeMappingSource,
- ISqlExpressionFactory sqlExpressionFactory)
+ public SqlServerNetTopologySuiteMethodCallTranslatorPlugin(
+ IRelationalTypeMappingSource typeMappingSource,
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
Translators = new IMethodCallTranslator[]
{
- new SqlServerGeometryMethodTranslator(typeMappingSource, sqlExpressionFactory),
+ new SqlServerGeometryMethodTranslator(typeMappingSource, sqlExpressionFactory, valueConverterSelector),
new SqlServerGeometryCollectionMethodTranslator(typeMappingSource, sqlExpressionFactory),
new SqlServerLineStringMethodTranslator(typeMappingSource, sqlExpressionFactory),
new SqlServerPolygonMethodTranslator(typeMappingSource, sqlExpressionFactory)
diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateDiffFunctionsTranslator.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateDiffFunctionsTranslator.cs
index a4d93281142..664365c455b 100644
--- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateDiffFunctionsTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerDateDiffFunctionsTranslator.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Pipeline
{
@@ -232,11 +233,14 @@ private readonly Dictionary _methodInfoDateDiffMapping
}
};
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IValueConverterSelector _valueConverterSelector;
public SqlServerDateDiffFunctionsTranslator(
- ISqlExpressionFactory sqlExpressionFactory)
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
_sqlExpressionFactory = sqlExpressionFactory;
+ _valueConverterSelector = valueConverterSelector;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList arguments)
@@ -245,7 +249,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
{
var startDate = arguments[1];
var endDate = arguments[2];
- var typeMapping = ExpressionExtensions.InferTypeMapping(startDate, endDate);
+ var typeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, startDate, endDate);
startDate = _sqlExpressionFactory.ApplyTypeMapping(startDate, typeMapping);
endDate = _sqlExpressionFactory.ApplyTypeMapping(endDate, typeMapping);
diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerMathTranslator.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerMathTranslator.cs
index 655c3124987..31bde55cc7b 100644
--- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerMathTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerMathTranslator.cs
@@ -7,6 +7,7 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Pipeline
{
@@ -61,10 +62,14 @@ public class SqlServerMathTranslator : IMethodCallTranslator
typeof(Math).GetRuntimeMethod(nameof(Math.Round), new[] { typeof(double), typeof(int) })
};
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IValueConverterSelector _valueConverterSelector;
- public SqlServerMathTranslator(ISqlExpressionFactory sqlExpressionFactory)
+ public SqlServerMathTranslator(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
_sqlExpressionFactory = sqlExpressionFactory;
+ _valueConverterSelector = valueConverterSelector;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList arguments)
@@ -72,8 +77,8 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
if (_supportedMethodTranslations.TryGetValue(method, out var sqlFunctionName))
{
var typeMapping = arguments.Count == 1
- ? ExpressionExtensions.InferTypeMapping(arguments[0])
- : ExpressionExtensions.InferTypeMapping(arguments[0], arguments[1]);
+ ? ExpressionExtensions.InferTypeMapping(_valueConverterSelector, arguments[0])
+ : ExpressionExtensions.InferTypeMapping(_valueConverterSelector, arguments[0], arguments[1]);
var newArguments = new SqlExpression[arguments.Count];
newArguments[0] = _sqlExpressionFactory.ApplyTypeMapping(arguments[0], typeMapping);
diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerMethodCallTranslatorProvider.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerMethodCallTranslatorProvider.cs
index 6badbe7858c..293268e17bd 100644
--- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerMethodCallTranslatorProvider.cs
+++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerMethodCallTranslatorProvider.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Pipeline
{
@@ -10,16 +11,17 @@ public class SqlServerMethodCallTranslatorProvider : RelationalMethodCallTransla
{
public SqlServerMethodCallTranslatorProvider(
ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector,
IEnumerable plugins)
: base(sqlExpressionFactory, plugins)
{
AddTranslators(new IMethodCallTranslator[]
{
- new SqlServerMathTranslator(sqlExpressionFactory),
+ new SqlServerMathTranslator(sqlExpressionFactory, valueConverterSelector),
new SqlServerNewGuidTranslator(sqlExpressionFactory),
- new SqlServerStringMethodTranslator(sqlExpressionFactory),
+ new SqlServerStringMethodTranslator(sqlExpressionFactory, valueConverterSelector),
new SqlServerDateTimeMethodTranslator(sqlExpressionFactory),
- new SqlServerDateDiffFunctionsTranslator(sqlExpressionFactory),
+ new SqlServerDateDiffFunctionsTranslator(sqlExpressionFactory, valueConverterSelector),
new SqlServerConvertTranslator(sqlExpressionFactory),
new SqlServerObjectToStringTranslator(sqlExpressionFactory),
new SqlServerFullTextSearchFunctionsTranslator(sqlExpressionFactory),
diff --git a/src/EFCore.SqlServer/Query/Pipeline/SqlServerStringMethodTranslator.cs b/src/EFCore.SqlServer/Query/Pipeline/SqlServerStringMethodTranslator.cs
index f5e7c0a066c..e1ee7e82725 100644
--- a/src/EFCore.SqlServer/Query/Pipeline/SqlServerStringMethodTranslator.cs
+++ b/src/EFCore.SqlServer/Query/Pipeline/SqlServerStringMethodTranslator.cs
@@ -8,6 +8,7 @@
using System.Text;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Pipeline
{
@@ -49,12 +50,16 @@ private static readonly MethodInfo _containsMethodInfo
private static readonly MethodInfo _endsWithMethodInfo
= typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IValueConverterSelector _valueConverterSelector;
private const char LikeEscapeChar = '\\';
- public SqlServerStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
+ public SqlServerStringMethodTranslator(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
_sqlExpressionFactory = sqlExpressionFactory;
+ _valueConverterSelector = valueConverterSelector;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList arguments)
@@ -62,7 +67,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
if (_indexOfMethodInfo.Equals(method))
{
var argument = arguments[0];
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, argument);
argument = _sqlExpressionFactory.ApplyTypeMapping(argument, stringTypeMapping);
var charIndexExpression = _sqlExpressionFactory.Subtract(
@@ -92,7 +97,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
{
var firstArgument = arguments[0];
var secondArgument = arguments[1];
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, firstArgument, secondArgument);
instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
firstArgument = _sqlExpressionFactory.ApplyTypeMapping(firstArgument, stringTypeMapping);
@@ -215,7 +220,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
if (_containsMethodInfo.Equals(method))
{
var pattern = arguments[0];
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, pattern);
instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);
@@ -251,7 +256,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith)
{
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, pattern);
instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);
diff --git a/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMathTranslator.cs b/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMathTranslator.cs
index 86f545b9be7..d0832316bd9 100644
--- a/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMathTranslator.cs
+++ b/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMathTranslator.cs
@@ -7,6 +7,7 @@
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Pipeline
{
@@ -43,10 +44,14 @@ public class SqliteMathTranslator : IMethodCallTranslator
};
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IValueConverterSelector _valueConverterSelector;
- public SqliteMathTranslator(ISqlExpressionFactory sqlExpressionFactory)
+ public SqliteMathTranslator(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
_sqlExpressionFactory = sqlExpressionFactory;
+ _valueConverterSelector = valueConverterSelector;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList arguments)
@@ -58,7 +63,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
if (string.Equals(sqlFunctionName, "max")
|| string.Equals(sqlFunctionName, "max"))
{
- typeMapping = ExpressionExtensions.InferTypeMapping(arguments[0], arguments[1]);
+ typeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, arguments[0], arguments[1]);
newArguments = new List
{
_sqlExpressionFactory.ApplyTypeMapping(arguments[0], typeMapping),
diff --git a/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMethodCallTranslatorProvider.cs b/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMethodCallTranslatorProvider.cs
index 3ad2e6a17b5..20e4e3badf5 100644
--- a/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMethodCallTranslatorProvider.cs
+++ b/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteMethodCallTranslatorProvider.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Pipeline
{
@@ -10,15 +11,16 @@ public class SqliteMethodCallTranslatorProvider : RelationalMethodCallTranslator
{
public SqliteMethodCallTranslatorProvider(
ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector,
IEnumerable plugins)
: base(sqlExpressionFactory, plugins)
{
AddTranslators(
new IMethodCallTranslator[]
{
- new SqliteMathTranslator(sqlExpressionFactory),
+ new SqliteMathTranslator(sqlExpressionFactory, valueConverterSelector),
new SqliteDateTimeAddTranslator(sqlExpressionFactory),
- new SqliteStringMethodTranslator(sqlExpressionFactory),
+ new SqliteStringMethodTranslator(sqlExpressionFactory, valueConverterSelector),
});
}
}
diff --git a/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteStringMethodTranslator.cs b/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteStringMethodTranslator.cs
index b181f887447..e85a79845cd 100644
--- a/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteStringMethodTranslator.cs
+++ b/src/EFCore.Sqlite.Core/Query/Pipeline/SqliteStringMethodTranslator.cs
@@ -8,6 +8,7 @@
using System.Text;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline;
using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Pipeline
{
@@ -57,11 +58,15 @@ private static readonly MethodInfo _endsWithMethodInfo
= typeof(string).GetRuntimeMethod(nameof(string.EndsWith), new[] { typeof(string) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;
+ private readonly IValueConverterSelector _valueConverterSelector;
private const char LikeEscapeChar = '\\';
- public SqliteStringMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
+ public SqliteStringMethodTranslator(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IValueConverterSelector valueConverterSelector)
{
_sqlExpressionFactory = sqlExpressionFactory;
+ _valueConverterSelector = valueConverterSelector;
}
public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList arguments)
@@ -69,7 +74,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
if (_indexOfMethodInfo.Equals(method))
{
var argument = arguments[0];
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, argument);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, argument);
return _sqlExpressionFactory.Subtract(
_sqlExpressionFactory.Function(
@@ -87,7 +92,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
{
var firstArgument = arguments[0];
var secondArgument = arguments[1];
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, firstArgument, secondArgument);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, firstArgument, secondArgument);
return _sqlExpressionFactory.Function(
"replace",
@@ -161,7 +166,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
if (_containsMethodInfo.Equals(method))
{
var pattern = arguments[0];
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, pattern);
instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);
@@ -193,7 +198,7 @@ public SqlExpression Translate(SqlExpression instance, MethodInfo method, IList<
private SqlExpression TranslateStartsEndsWith(SqlExpression instance, SqlExpression pattern, bool startsWith)
{
- var stringTypeMapping = ExpressionExtensions.InferTypeMapping(instance, pattern);
+ var stringTypeMapping = ExpressionExtensions.InferTypeMapping(_valueConverterSelector, instance, pattern);
instance = _sqlExpressionFactory.ApplyTypeMapping(instance, stringTypeMapping);
pattern = _sqlExpressionFactory.ApplyTypeMapping(pattern, stringTypeMapping);
diff --git a/src/EFCore/ChangeTracking/CollectionEntry`.cs b/src/EFCore/ChangeTracking/CollectionEntry`.cs
index b5354249b73..129ebcc11ae 100644
--- a/src/EFCore/ChangeTracking/CollectionEntry`.cs
+++ b/src/EFCore/ChangeTracking/CollectionEntry`.cs
@@ -64,7 +64,7 @@ public CollectionEntry([NotNull] InternalEntityEntry internalEntry, [NotNull] IN
///
public new virtual IEnumerable CurrentValue
{
- get => this.GetInfrastructure().GetCurrentValue>(Metadata);
+ get => this.GetInfrastructure().GetDeclaredCurrentValue>(Metadata);
[param: CanBeNull] set => base.CurrentValue = value;
}
diff --git a/src/EFCore/ChangeTracking/Internal/CurrentPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/CurrentPropertyValues.cs
index 0f4c897b1ca..c24d08a0ae6 100644
--- a/src/EFCore/ChangeTracking/Internal/CurrentPropertyValues.cs
+++ b/src/EFCore/ChangeTracking/Internal/CurrentPropertyValues.cs
@@ -33,7 +33,7 @@ public CurrentPropertyValues([NotNull] InternalEntityEntry internalEntry)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override TValue GetValue(string propertyName)
- => InternalEntry.GetCurrentValue(InternalEntry.EntityType.GetProperty(propertyName));
+ => InternalEntry.GetDeclaredCurrentValue(InternalEntry.EntityType.GetProperty(propertyName));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -42,7 +42,7 @@ public override TValue GetValue(string propertyName)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override TValue GetValue(IProperty property)
- => InternalEntry.GetCurrentValue(InternalEntry.EntityType.CheckPropertyBelongsToType(property));
+ => InternalEntry.GetDeclaredCurrentValue(InternalEntry.EntityType.CheckPropertyBelongsToType(property));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
index be72a7a63a5..0073df9c5f8 100644
--- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
+++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
@@ -688,6 +688,15 @@ internal static readonly MethodInfo GetCurrentValueMethod
public virtual TProperty GetCurrentValue(IPropertyBase propertyBase)
=> ((Func)propertyBase.GetPropertyAccessors().CurrentValueGetter)(this);
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual TProperty GetDeclaredCurrentValue(IPropertyBase propertyBase)
+ => ((Func)propertyBase.GetPropertyAccessors().DeclaredCurrentValueGetter)(this);
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -703,9 +712,8 @@ public virtual TProperty GetOriginalValue(IProperty property)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public virtual TProperty GetRelationshipSnapshotValue([NotNull] IPropertyBase propertyBase)
- => ((Func)propertyBase.GetPropertyAccessors().RelationshipSnapshotGetter)(
- this);
+ public virtual TProperty GetDeclaredOriginalValue(IProperty property)
+ => ((Func)property.GetPropertyAccessors().DeclaredOriginalValueGetter)(this);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/ChangeTracking/Internal/OriginalPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/OriginalPropertyValues.cs
index b26786cf087..828ebd001dd 100644
--- a/src/EFCore/ChangeTracking/Internal/OriginalPropertyValues.cs
+++ b/src/EFCore/ChangeTracking/Internal/OriginalPropertyValues.cs
@@ -33,7 +33,7 @@ public OriginalPropertyValues([NotNull] InternalEntityEntry internalEntry)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override TValue GetValue(string propertyName)
- => InternalEntry.GetOriginalValue(InternalEntry.EntityType.GetProperty(propertyName));
+ => InternalEntry.GetDeclaredOriginalValue(InternalEntry.EntityType.GetProperty(propertyName));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -42,7 +42,7 @@ public override TValue GetValue(string propertyName)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public override TValue GetValue(IProperty property)
- => InternalEntry.GetOriginalValue(InternalEntry.EntityType.CheckPropertyBelongsToType(property));
+ => InternalEntry.GetDeclaredOriginalValue(InternalEntry.EntityType.CheckPropertyBelongsToType(property));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
index f6509683e43..02f9d6692c4 100644
--- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
+++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
@@ -163,12 +163,20 @@ private Expression CreateSnapshotExpression(
memberAccess = Expression.Convert(memberAccess, propertyBase.ClrType);
}
- arguments[i] = (propertyBase as INavigation)?.IsCollection() ?? false
- ? Expression.Call(
- null,
- _snapshotCollectionMethod,
- memberAccess)
- : CreateSnapshotValueExpression(memberAccess, propertyBase);
+ if (propertyBase is INavigation navigation
+ && navigation.IsCollection())
+ {
+ if (!typeof(IEnumerable
public new virtual TProperty CurrentValue
{
- get => InternalEntry.GetCurrentValue(Metadata);
+ get => InternalEntry.GetDeclaredCurrentValue(Metadata);
[param: CanBeNull] set => base.CurrentValue = value;
}
@@ -71,7 +71,7 @@ public PropertyEntry([NotNull] InternalEntityEntry internalEntry, [NotNull] IPro
///
public new virtual TProperty OriginalValue
{
- get => InternalEntry.GetOriginalValue(Metadata);
+ get => InternalEntry.GetDeclaredOriginalValue(Metadata);
[param: CanBeNull] set => base.OriginalValue = value;
}
}
diff --git a/src/EFCore/ChangeTracking/ReferenceEntry`.cs b/src/EFCore/ChangeTracking/ReferenceEntry`.cs
index 955df0d0ea9..ccce578fcdc 100644
--- a/src/EFCore/ChangeTracking/ReferenceEntry`.cs
+++ b/src/EFCore/ChangeTracking/ReferenceEntry`.cs
@@ -75,7 +75,7 @@ public ReferenceEntry([NotNull] InternalEntityEntry internalEntry, [NotNull] INa
///
public new virtual TProperty CurrentValue
{
- get => this.GetInfrastructure().GetCurrentValue(Metadata);
+ get => this.GetInfrastructure().GetDeclaredCurrentValue(Metadata);
[param: CanBeNull] set => base.CurrentValue = value;
}
diff --git a/src/EFCore/ChangeTracking/ValueComparer`.cs b/src/EFCore/ChangeTracking/ValueComparer`.cs
index 9d4ebfac2a9..b0c8e8aed08 100644
--- a/src/EFCore/ChangeTracking/ValueComparer`.cs
+++ b/src/EFCore/ChangeTracking/ValueComparer`.cs
@@ -113,9 +113,7 @@ protected static Expression> CreateDefaultEqualsExpression()
|| unwrappedType == typeof(bool)
|| unwrappedType == typeof(decimal)
|| unwrappedType == typeof(double)
- || unwrappedType == typeof(float)
- || unwrappedType == typeof(object)
- )
+ || unwrappedType == typeof(float))
{
return Expression.Lambda>(
Expression.Equal(param1, param2),
diff --git a/src/EFCore/Metadata/IPropertyBase.cs b/src/EFCore/Metadata/IPropertyBase.cs
index 9d2208fed88..f5277c06864 100644
--- a/src/EFCore/Metadata/IPropertyBase.cs
+++ b/src/EFCore/Metadata/IPropertyBase.cs
@@ -23,10 +23,16 @@ public interface IPropertyBase : IAnnotatable
ITypeBase DeclaringType { get; }
///
- /// Gets the type of value that this property holds.
+ /// Gets the type of value that this property holds, which will be the type of the
+ /// field for properties that are known to be backed by fields.
///
Type ClrType { get; }
+ ///
+ /// Gets the type that this property is declared as.
+ ///
+ Type DeclaredClrType { get; }
+
///
/// Gets the for the underlying CLR property that this
/// object represents. This may be null for shadow properties or properties mapped directly to fields.
diff --git a/src/EFCore/Metadata/Internal/Navigation.cs b/src/EFCore/Metadata/Internal/Navigation.cs
index 28a4f2d6a49..48bf7ada3f2 100644
--- a/src/EFCore/Metadata/Internal/Navigation.cs
+++ b/src/EFCore/Metadata/Internal/Navigation.cs
@@ -49,7 +49,15 @@ public Navigation(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override Type ClrType => this.GetIdentifyingMemberInfo()?.GetMemberType() ?? typeof(object);
+ public override Type ClrType => (FieldInfo ?? (MemberInfo)PropertyInfo)?.GetMemberType() ?? typeof(object);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public override Type DeclaredClrType => (PropertyInfo ?? (MemberInfo)FieldInfo)?.GetMemberType() ?? typeof(object);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index e8f2f8b3a77..814e1a27596 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -55,7 +55,7 @@ public Property(
Check.NotNull(declaringEntityType, nameof(declaringEntityType));
DeclaringEntityType = declaringEntityType;
- ClrType = clrType;
+ DeclaredClrType = clrType;
_configurationSource = configurationSource;
_typeConfigurationSource = typeConfigurationSource;
@@ -95,7 +95,21 @@ public override TypeBase DeclaringType
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- public override Type ClrType { [DebuggerStepThrough] get; }
+ public override Type ClrType
+ {
+ [DebuggerStepThrough] get => FieldInfo?.FieldType ?? DeclaredClrType;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public override Type DeclaredClrType
+ {
+ [DebuggerStepThrough] get;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Metadata/Internal/PropertyAccessors.cs b/src/EFCore/Metadata/Internal/PropertyAccessors.cs
index 60b0700b5df..668c8445652 100644
--- a/src/EFCore/Metadata/Internal/PropertyAccessors.cs
+++ b/src/EFCore/Metadata/Internal/PropertyAccessors.cs
@@ -23,14 +23,18 @@ public sealed class PropertyAccessors
///
public PropertyAccessors(
[NotNull] Delegate currentValueGetter,
+ [NotNull] Delegate declaredCurrentValueGetter,
[NotNull] Delegate preStoreGeneratedCurrentValueGetter,
[CanBeNull] Delegate originalValueGetter,
+ [CanBeNull] Delegate declaredOriginalValueGetter,
[NotNull] Delegate relationshipSnapshotGetter,
[CanBeNull] Func valueBufferGetter)
{
CurrentValueGetter = currentValueGetter;
+ DeclaredCurrentValueGetter = declaredCurrentValueGetter;
PreStoreGeneratedCurrentValueGetter = preStoreGeneratedCurrentValueGetter;
OriginalValueGetter = originalValueGetter;
+ DeclaredOriginalValueGetter = declaredOriginalValueGetter;
RelationshipSnapshotGetter = relationshipSnapshotGetter;
ValueBufferGetter = valueBufferGetter;
}
@@ -43,6 +47,14 @@ public PropertyAccessors(
///
public Delegate CurrentValueGetter { get; }
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public Delegate DeclaredCurrentValueGetter { get; }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -59,6 +71,14 @@ public PropertyAccessors(
///
public Delegate OriginalValueGetter { get; }
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public Delegate DeclaredOriginalValueGetter { get; }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
index 113fa9084d9..fafd7f18d62 100644
--- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
+++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
@@ -27,7 +27,7 @@ public class PropertyAccessorsFactory
///
public virtual PropertyAccessors Create([NotNull] IPropertyBase propertyBase)
=> (PropertyAccessors)_genericCreate
- .MakeGenericMethod(propertyBase.ClrType)
+ .MakeGenericMethod(propertyBase.ClrType, propertyBase.DeclaredClrType)
.Invoke(
null, new object[]
{
@@ -38,13 +38,34 @@ private static readonly MethodInfo _genericCreate
= typeof(PropertyAccessorsFactory).GetTypeInfo().GetDeclaredMethod(nameof(CreateGeneric));
[UsedImplicitly]
- private static PropertyAccessors CreateGeneric(IPropertyBase propertyBase)
+ private static PropertyAccessors CreateGeneric(IPropertyBase propertyBase)
{
var property = propertyBase as IProperty;
+
+ var currentValueGetter
+ = CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: true);
+
+ var declaredCurrentValueGetter
+ = typeof(TProperty) == typeof(TDeclaredProperty)
+ ? (Func)(object)currentValueGetter
+ : CreateDeclaredCurrentValueGetter(propertyBase, useStoreGeneratedValues: true);
+
+ var originalValueGetter
+ = property == null
+ ? null
+ : CreateOriginalValueGetter(property);
+
+ var declaredOriginalValueGetter
+ = property == null || typeof(TProperty) == typeof(TDeclaredProperty)
+ ? (Func)(object)originalValueGetter
+ : CreateDeclaredOriginalValueGetter(property);
+
return new PropertyAccessors(
- CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: true),
+ currentValueGetter,
+ declaredCurrentValueGetter,
CreateCurrentValueGetter(propertyBase, useStoreGeneratedValues: false),
- property == null ? null : CreateOriginalValueGetter(property),
+ originalValueGetter,
+ declaredOriginalValueGetter,
CreateRelationshipSnapshotGetter(propertyBase),
property == null ? null : CreateValueBufferGetter(property));
}
@@ -52,9 +73,34 @@ private static PropertyAccessors CreateGeneric(IPropertyBase property
private static Func CreateCurrentValueGetter(
IPropertyBase propertyBase, bool useStoreGeneratedValues)
{
- var entityClrType = propertyBase.DeclaringType.ClrType;
var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
+ return Expression.Lambda>(
+ CreateCurrentValueExpression(
+ entryParameter, propertyBase, useStoreGeneratedValues),
+ entryParameter)
+ .Compile();
+ }
+
+ private static Func CreateDeclaredCurrentValueGetter(
+ IPropertyBase propertyBase, bool useStoreGeneratedValues)
+ {
+ var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
+
+ return Expression.Lambda>(
+ Expression.Convert(
+ CreateCurrentValueExpression(
+ entryParameter, propertyBase, useStoreGeneratedValues),
+ typeof(TDeclaredProperty)),
+ entryParameter)
+ .Compile();
+ }
+
+ private static Expression CreateCurrentValueExpression(
+ ParameterExpression entryParameter, IPropertyBase propertyBase, bool useStoreGeneratedValues)
+ {
+ var entityClrType = propertyBase.DeclaringType.ClrType;
+
var shadowIndex = propertyBase.GetShadowIndex();
Expression currentValueExpression;
if (shadowIndex >= 0)
@@ -120,36 +166,49 @@ private static Func CreateCurrentValueGetter CreateOriginalValueGetter(IProperty property)
+ {
+ var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
+
return Expression.Lambda>(
- currentValueExpression,
+ CreateOriginalValueGetterExpression(entryParameter, property),
entryParameter)
.Compile();
}
- private static Func CreateOriginalValueGetter(IProperty property)
+ private static Func CreateDeclaredOriginalValueGetter(IProperty property)
{
var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
- var originalValuesIndex = property.GetOriginalValueIndex();
- return Expression.Lambda>(
- originalValuesIndex >= 0
- ? (Expression)Expression.Call(
- entryParameter,
- InternalEntityEntry.ReadOriginalValueMethod.MakeGenericMethod(typeof(TProperty)),
- Expression.Constant(property),
- Expression.Constant(originalValuesIndex))
- : Expression.Block(
- Expression.Throw(
- Expression.Constant(
- new InvalidOperationException(
- CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringEntityType.DisplayName())))),
-#pragma warning disable IDE0034 // Simplify 'default' expression - default infer to default(object) instead of default(TProperty)
- Expression.Constant(default(TProperty), typeof(TProperty))),
-#pragma warning restore IDE0034 // Simplify 'default' expression
+ return Expression.Lambda>(
+ Expression.Convert(
+ CreateOriginalValueGetterExpression(entryParameter, property),
+ typeof(TDeclaredProperty)),
entryParameter)
.Compile();
}
+ private static Expression CreateOriginalValueGetterExpression(ParameterExpression entryParameter, IProperty property)
+ {
+ var originalValuesIndex = property.GetOriginalValueIndex();
+
+ return originalValuesIndex >= 0
+ ? (Expression)Expression.Call(
+ entryParameter,
+ InternalEntityEntry.ReadOriginalValueMethod.MakeGenericMethod(typeof(TProperty)),
+ Expression.Constant(property),
+ Expression.Constant(originalValuesIndex))
+ : Expression.Block(
+ Expression.Throw(
+ Expression.Constant(
+ new InvalidOperationException(
+ CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringEntityType.DisplayName())))),
+ Expression.Constant(default(TProperty), typeof(TProperty)));
+ }
+
private static Func CreateRelationshipSnapshotGetter(IPropertyBase propertyBase)
{
var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs
index 28e9675f946..18c27cf87fe 100644
--- a/src/EFCore/Metadata/Internal/PropertyBase.cs
+++ b/src/EFCore/Metadata/Internal/PropertyBase.cs
@@ -292,6 +292,14 @@ private void UpdateFieldInfoConfigurationSource(ConfigurationSource configuratio
///
public abstract Type ClrType { get; }
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public abstract Type DeclaredClrType { get; }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Metadata/Internal/ServiceProperty.cs b/src/EFCore/Metadata/Internal/ServiceProperty.cs
index 33a2f8ea906..18dae2ef524 100644
--- a/src/EFCore/Metadata/Internal/ServiceProperty.cs
+++ b/src/EFCore/Metadata/Internal/ServiceProperty.cs
@@ -40,7 +40,8 @@ public ServiceProperty(
Check.NotNull(declaringEntityType, nameof(declaringEntityType));
DeclaringEntityType = declaringEntityType;
- ClrType = propertyInfo?.PropertyType ?? fieldInfo?.FieldType;
+ DeclaredClrType = (propertyInfo ?? (MemberInfo)fieldInfo).GetMemberType();
+ ClrType = (fieldInfo ?? (MemberInfo)propertyInfo).GetMemberType();
_configurationSource = configurationSource;
Builder = new InternalServicePropertyBuilder(this, declaringEntityType.Model.Builder);
@@ -81,6 +82,14 @@ public override TypeBase DeclaringType
///
public override Type ClrType { get; }
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public override Type DeclaredClrType { get; }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Query/Pipeline/ProjectionMember.cs b/src/EFCore/Query/Pipeline/ProjectionMember.cs
index dab0fa2d3e9..20b5e4023f6 100644
--- a/src/EFCore/Query/Pipeline/ProjectionMember.cs
+++ b/src/EFCore/Query/Pipeline/ProjectionMember.cs
@@ -1,6 +1,7 @@
// 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.Linq;
using System.Collections.Generic;
using System.Reflection;
@@ -39,6 +40,8 @@ public ProjectionMember ShiftMember(MemberInfo member)
return new ProjectionMember(existingChain);
}
+ public Type MemberType => _memberChain.LastOrDefault()?.GetMemberType();
+
public override int GetHashCode()
{
unchecked
diff --git a/src/EFCore/Storage/CoreTypeMapping.cs b/src/EFCore/Storage/CoreTypeMapping.cs
index 16d9f26ea62..fd14ffae494 100644
--- a/src/EFCore/Storage/CoreTypeMapping.cs
+++ b/src/EFCore/Storage/CoreTypeMapping.cs
@@ -38,33 +38,15 @@ protected readonly struct CoreTypeMappingParameters
/// Converts types to and from the store whenever this mapping is used.
/// Supports custom value snapshotting and comparisons.
/// Supports custom comparisons between keys--e.g. PK to FK comparison.
+ /// Supports structural snapshotting needed for mutable reference types.
/// An optional factory for creating a specific .
public CoreTypeMappingParameters(
[NotNull] Type clrType,
[CanBeNull] ValueConverter converter = null,
[CanBeNull] ValueComparer comparer = null,
[CanBeNull] ValueComparer keyComparer = null,
+ [CanBeNull] ValueComparer structuralComparer = null,
[CanBeNull] Func valueGeneratorFactory = null)
- : this(clrType, converter, comparer, keyComparer, null, valueGeneratorFactory)
- {
- }
-
- ///
- /// Creates a new parameter object.
- ///
- /// The .NET type used in the EF model.
- /// Converts types to and from the store whenever this mapping is used.
- /// Supports custom value snapshotting and comparisons.
- /// Supports custom comparisons between keys--e.g. PK to FK comparison.
- /// Supports structural snapshotting needed for mutable reference types.
- /// An optional factory for creating a specific .
- public CoreTypeMappingParameters(
- [NotNull] Type clrType,
- [CanBeNull] ValueConverter converter,
- [CanBeNull] ValueComparer comparer,
- [CanBeNull] ValueComparer keyComparer,
- [CanBeNull] ValueComparer structuralComparer,
- [CanBeNull] Func valueGeneratorFactory)
{
Check.NotNull(clrType, nameof(clrType));
diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs
index 7f2d1f56b05..7ce50464172 100644
--- a/src/EFCore/Storage/TypeMappingInfo.cs
+++ b/src/EFCore/Storage/TypeMappingInfo.cs
@@ -85,6 +85,7 @@ public TypeMappingInfo(
IsUnicode = isUnicode ?? mappingHints?.IsUnicode ?? fallbackUnicode;
IsRowVersion = property.IsConcurrencyToken && property.ValueGenerated == ValueGenerated.OnAddOrUpdate;
ClrType = (customConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType();
+ DeclaredClrType = (customConverter?.ProviderClrType ?? property.DeclaredClrType).UnwrapNullableType();
Scale = mappingHints?.Scale ?? fallbackScale;
Precision = mappingHints?.Precision ?? fallbackPrecision;
}
@@ -131,6 +132,7 @@ public TypeMappingInfo(
int? scale = null)
{
ClrType = type?.UnwrapNullableType();
+ DeclaredClrType = ClrType;
IsKeyOrIndex = keyOrIndex;
Size = size;
@@ -170,6 +172,7 @@ public TypeMappingInfo(
Precision = precision ?? source.Precision ?? mappingHints?.Precision;
ClrType = converter.ProviderClrType.UnwrapNullableType();
+ DeclaredClrType = source.DeclaredClrType;
}
///
@@ -211,11 +214,18 @@ public TypeMappingInfo WithConverter(in ValueConverterInfo converterInfo)
public int? Scale { get; }
///
- /// The CLR type in the model. May be null if type information is conveyed via other means
- /// (e.g. the store name in a relational type mapping info)
+ /// The CLR type used to store the property in the model. This may be the backing field type.
+ /// May be null if type information is conveyed via other means (e.g. the store name in a relational type mapping info)
///
public Type ClrType { get; }
+ ///
+ /// The CLR type of the property, which may be different from if a backing field of
+ /// a different type is used.
+ /// May be null if type information is conveyed via other means (e.g. the store name in a relational type mapping info)
+ ///
+ public Type DeclaredClrType { get; }
+
///
/// Compares this to another to check if they represent the same mapping.
///
@@ -223,6 +233,7 @@ public TypeMappingInfo WithConverter(in ValueConverterInfo converterInfo)
/// True if they represent the same mapping; false otherwise.
public bool Equals(TypeMappingInfo other)
=> ClrType == other.ClrType
+ && DeclaredClrType == other.DeclaredClrType
&& IsKeyOrIndex == other.IsKeyOrIndex
&& Size == other.Size
&& IsUnicode == other.IsUnicode
@@ -247,6 +258,7 @@ public override bool Equals(object obj)
public override int GetHashCode()
{
var hashCode = ClrType?.GetHashCode() ?? 0;
+ hashCode = (hashCode * 397) ^ (DeclaredClrType?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ IsKeyOrIndex.GetHashCode();
hashCode = (hashCode * 397) ^ (Size?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (IsUnicode?.GetHashCode() ?? 0);
diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs
index 394b45330e7..6d4b5979cd7 100644
--- a/src/EFCore/Storage/TypeMappingSource.cs
+++ b/src/EFCore/Storage/TypeMappingSource.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -79,54 +80,79 @@ private CoreTypeMapping FindMappingWithConversion(
k =>
{
var (info, providerType, converter) = k;
+
+ var sourceType = info.ClrType;
+ var sourceDeclaredType = info.DeclaredClrType;
+
+ var needsConvertFromObject
+ = sourceType == typeof(object)
+ && sourceDeclaredType != typeof(object);
+
+ var effectiveSourceType = needsConvertFromObject ? sourceDeclaredType : sourceType;
+
+ ValueConverterInfo fromObjectConverter = default;
+ var fromObjectMappingInfo = info;
+
+ if (needsConvertFromObject)
+ {
+ fromObjectConverter = Dependencies.ValueConverterSelector
+ .Select(typeof(object), sourceDeclaredType)
+ .Single();
+
+ fromObjectMappingInfo = info.WithConverter(fromObjectConverter);
+ }
+
var mapping = providerType == null
- || providerType == info.ClrType
- ? FindMapping(info)
+ || providerType == effectiveSourceType
+ ? FindMapping(fromObjectMappingInfo)
: null;
- if (mapping == null)
+ if (mapping == null
+ && effectiveSourceType != null)
{
- var sourceType = info.ClrType;
-
- if (sourceType != null)
+ foreach (var converterInfo in Dependencies
+ .ValueConverterSelector
+ .Select(effectiveSourceType, providerType))
{
- foreach (var converterInfo in Dependencies
- .ValueConverterSelector
- .Select(sourceType, providerType))
- {
- var mappingInfoUsed = info.WithConverter(converterInfo);
- mapping = FindMapping(mappingInfoUsed);
+ var mappingInfoUsed = info.WithConverter(converterInfo);
+ mapping = FindMapping(mappingInfoUsed);
- if (mapping == null
- && providerType != null)
+ if (mapping == null
+ && providerType != null)
+ {
+ foreach (var secondConverterInfo in Dependencies
+ .ValueConverterSelector
+ .Select(providerType))
{
- foreach (var secondConverterInfo in Dependencies
- .ValueConverterSelector
- .Select(providerType))
- {
- mapping = FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo));
+ mapping = FindMapping(mappingInfoUsed.WithConverter(secondConverterInfo));
- if (mapping != null)
- {
- mapping = mapping.Clone(secondConverterInfo.Create());
- break;
- }
+ if (mapping != null)
+ {
+ mapping = mapping.Clone(secondConverterInfo.Create());
+ break;
}
}
+ }
- if (mapping != null)
- {
- mapping = mapping.Clone(converterInfo.Create());
- break;
- }
+ if (mapping != null)
+ {
+ mapping = mapping.Clone(converterInfo.Create());
+ break;
}
}
}
- if (mapping != null
- && converter != null)
+ if (mapping != null)
{
- mapping = mapping.Clone(converter);
+ if (needsConvertFromObject)
+ {
+ mapping = mapping.Clone(fromObjectConverter.Create());
+ }
+
+ if (converter != null)
+ {
+ mapping = mapping.Clone(converter);
+ }
}
return mapping;
diff --git a/src/EFCore/Storage/ValueConversion/BoolToZeroOneConverter.cs b/src/EFCore/Storage/ValueConversion/BoolToZeroOneConverter.cs
index d7ab53d9571..324bd0a7d3c 100644
--- a/src/EFCore/Storage/ValueConversion/BoolToZeroOneConverter.cs
+++ b/src/EFCore/Storage/ValueConversion/BoolToZeroOneConverter.cs
@@ -36,7 +36,7 @@ private static TProvider Zero()
typeof(BoolToZeroOneConverter),
typeof(int), typeof(short), typeof(long), typeof(sbyte),
typeof(uint), typeof(ushort), typeof(ulong), typeof(byte),
- typeof(decimal), typeof(double), typeof(float));
+ typeof(decimal), typeof(double), typeof(float), typeof(char));
return Activator.CreateInstance();
}
@@ -67,7 +67,9 @@ private static TProvider One()
? (double)1
: type == typeof(float)
? (float)1
- : (object)1);
+ : type == typeof(char)
+ ? (char)1
+ : (object)1);
}
}
}
diff --git a/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs b/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs
index 312bccb9491..37f8ca66aab 100644
--- a/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs
+++ b/src/EFCore/Storage/ValueConversion/EnumToNumberConverter.cs
@@ -66,7 +66,7 @@ private static Expression> ToNumber()
typeof(EnumToNumberConverter),
typeof(int), typeof(long), typeof(short), typeof(byte),
typeof(uint), typeof(ulong), typeof(ushort), typeof(sbyte),
- typeof(double), typeof(float), typeof(decimal));
+ typeof(double), typeof(float), typeof(decimal), typeof(char));
var param = Expression.Parameter(typeof(TEnum), "value");
diff --git a/src/EFCore/Storage/ValueConversion/ValueConverterSelector.cs b/src/EFCore/Storage/ValueConversion/ValueConverterSelector.cs
index bb2e18c9224..59bf5a08d4c 100644
--- a/src/EFCore/Storage/ValueConversion/ValueConverterSelector.cs
+++ b/src/EFCore/Storage/ValueConversion/ValueConverterSelector.cs
@@ -37,7 +37,7 @@ public class ValueConverterSelector : IValueConverterSelector
private static readonly Type[] _charPreferred = { typeof(char), typeof(int), typeof(ushort), typeof(uint), typeof(long), typeof(ulong), typeof(decimal) };
- private static readonly Type[] _numerics = { typeof(int), typeof(long), typeof(short), typeof(byte), typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte), typeof(decimal), typeof(double), typeof(float) };
+ private static readonly Type[] _numerics = { typeof(int), typeof(long), typeof(short), typeof(byte), typeof(ulong), typeof(uint), typeof(ushort), typeof(sbyte), typeof(decimal), typeof(double), typeof(float), typeof(char) };
///
/// Initializes a new instance of the class.
@@ -72,6 +72,18 @@ public virtual IEnumerable Select(
var underlyingModelType = modelClrType.UnwrapNullableType();
var underlyingProviderType = providerClrType?.UnwrapNullableType();
+ if (underlyingModelType == typeof(object)
+ && underlyingProviderType != null
+ && underlyingProviderType != typeof(object))
+ {
+ yield return _converters.GetOrAdd(
+ (typeof(object), underlyingProviderType),
+ k => (ValueConverterInfo)typeof(CastingConverter<,>)
+ .MakeGenericType(typeof(object), k.ProviderClrType)
+ .GetAnyProperty("DefaultInfo")
+ .GetValue(null));
+ }
+
if (underlyingModelType.IsEnum)
{
foreach (var converterInfo in FindNumericConventions(
@@ -166,6 +178,12 @@ public virtual IEnumerable Select(
.GetAnyProperty("DefaultInfo")
.GetValue(null));
}
+ else if (underlyingProviderType == typeof(char))
+ {
+ yield return _converters.GetOrAdd(
+ (underlyingModelType, typeof(char)),
+ k => StringToCharConverter.DefaultInfo);
+ }
else if (_numerics.Contains(underlyingProviderType))
{
foreach (var converterInfo in FindNumericConventions(
@@ -207,12 +225,6 @@ public virtual IEnumerable Select(
(underlyingModelType, typeof(bool)),
k => StringToBoolConverter.DefaultInfo);
}
- else if (underlyingProviderType == typeof(char))
- {
- yield return _converters.GetOrAdd(
- (underlyingModelType, typeof(char)),
- k => StringToCharConverter.DefaultInfo);
- }
}
else if (underlyingModelType == typeof(DateTime)
|| underlyingModelType == typeof(DateTimeOffset)
@@ -266,7 +278,8 @@ public virtual IEnumerable Select(
&& (underlyingProviderType == null
|| underlyingProviderType == typeof(byte[])
|| underlyingProviderType == typeof(string)
- || _numerics.Contains(underlyingProviderType)))
+ || _numerics.Contains(underlyingProviderType)
+ || underlyingProviderType.IsEnum))
{
foreach (var converterInfo in FindNumericConventions(
underlyingModelType,
@@ -477,6 +490,18 @@ private IEnumerable FindNumericConventions(
.GetValue(null));
}
}
+
+ if (_numerics.Contains(modelType)
+ && providerType != null
+ && providerType.IsEnum)
+ {
+ yield return _converters.GetOrAdd(
+ (modelType, providerType),
+ k => (ValueConverterInfo)typeof(CastingConverter<,>)
+ .MakeGenericType(k.ModelClrType, k.ProviderClrType)
+ .GetAnyProperty("DefaultInfo")
+ .GetValue(null));
+ }
}
private IEnumerable FindPreferredConversions(
diff --git a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs
index b81c6a00923..5b7ed10104f 100644
--- a/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs
+++ b/test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
@@ -53,7 +54,7 @@ public virtual async Task Can_filter_projection_with_captured_enum_variable(bool
}
}
- [Theory(Skip = "QueryIssue")]
+ [Theory]
[InlineData(false)]
[InlineData(true)]
public virtual async Task Can_filter_projection_with_inline_enum_variable(bool async)
@@ -203,7 +204,7 @@ public virtual void Can_perform_query_with_ansi_strings_test()
}
}
- [Fact(Skip = "QueryIssue")]
+ [Fact]
public virtual void Can_query_using_any_data_type()
{
using (var context = CreateContext())
@@ -216,7 +217,7 @@ public virtual void Can_query_using_any_data_type()
}
}
- [Fact(Skip = "QueryIssue")]
+ [Fact]
public virtual void Can_query_using_any_data_type_shadow()
{
using (var context = CreateContext())
@@ -549,7 +550,7 @@ protected EntityEntry AddTestBuiltInDataTypes(DbSet s
return entityEntry;
}
- [Fact(Skip = "QueryIssue")]
+ [Fact]
public virtual void Can_query_using_any_nullable_data_type()
{
using (var context = CreateContext())
@@ -562,7 +563,7 @@ public virtual void Can_query_using_any_nullable_data_type()
}
}
- [Fact(Skip = "QueryIssue")]
+ [Fact]
public virtual void Can_query_using_any_data_type_nullable_shadow()
{
using (var context = CreateContext())
@@ -925,7 +926,7 @@ protected virtual EntityEntry AddTestBuiltInNullableDataTypes(
return entityEntry;
}
- [Fact(Skip = "QueryIssue")]
+ [Fact]
public virtual void Can_query_using_any_nullable_data_type_as_literal()
{
using (var context = CreateContext())
@@ -1135,7 +1136,7 @@ public virtual void Can_query_using_any_nullable_data_type_as_literal()
}
}
- [Fact(Skip = "Tasklist#8")]
+ [Fact]
public virtual void Can_query_with_null_parameters_using_any_nullable_data_type()
{
using (var context = CreateContext())
@@ -1670,6 +1671,220 @@ public virtual void Can_insert_and_read_back_all_nullable_data_types_with_values
}
}
+ [Fact]
+ public virtual void Can_insert_and_read_back_object_backed_data_types()
+ {
+ using (var context = CreateContext())
+ {
+ context.Set().Add(
+ new ObjectBackedDataTypes
+ {
+ Id = 101,
+ PartitionId = 101,
+ String = "TestString",
+ Bytes = new byte[] { 10, 9, 8, 7, 6 },
+ Int16 = -1234,
+ Int32 = -123456789,
+ Int64 = -1234567890123456789L,
+ Double = -1.23456789,
+ Decimal = -1234567890.01M,
+ DateTime = DateTime.Parse("01/01/2000 12:34:56"),
+ DateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)),
+ TimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ Single = -1.234F,
+ Boolean = false,
+ Byte = 255,
+ UnsignedInt16 = 1234,
+ UnsignedInt32 = 1234565789U,
+ UnsignedInt64 = 1234567890123456789UL,
+ Character = 'a',
+ SignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
+
+ Assert.Equal(1, context.SaveChanges());
+ }
+
+ using (var context = CreateContext())
+ {
+ var dt = context.Set().Where(ndt => ndt.Id == 101).ToList().Single();
+
+ Assert.Equal("TestString", dt.String);
+ Assert.Equal(new byte[] { 10, 9, 8, 7, 6 }, dt.Bytes);
+ Assert.Equal((short)-1234, dt.Int16);
+ Assert.Equal(-123456789, dt.Int32);
+ Assert.Equal(-1234567890123456789L, dt.Int64);
+ Assert.Equal(-1.23456789, dt.Double);
+ Assert.Equal(-1234567890.01M, dt.Decimal);
+ Assert.Equal(DateTime.Parse("01/01/2000 12:34:56"), dt.DateTime);
+ Assert.Equal(new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)), dt.DateTimeOffset);
+ Assert.Equal(new TimeSpan(0, 10, 9, 8, 7), dt.TimeSpan);
+ Assert.Equal(-1.234F, dt.Single);
+ Assert.Equal(false, dt.Boolean);
+ Assert.Equal((byte)255, dt.Byte);
+ Assert.Equal(Enum64.SomeValue, dt.Enum64);
+ Assert.Equal(Enum32.SomeValue, dt.Enum32);
+ Assert.Equal(Enum16.SomeValue, dt.Enum16);
+ Assert.Equal(Enum8.SomeValue, dt.Enum8);
+ Assert.Equal((ushort)1234, dt.UnsignedInt16);
+ Assert.Equal(1234565789U, dt.UnsignedInt32);
+ Assert.Equal(1234567890123456789UL, dt.UnsignedInt64);
+ Assert.Equal('a', dt.Character);
+ Assert.Equal((sbyte)-128, dt.SignedByte);
+ Assert.Equal(EnumU64.SomeValue, dt.EnumU64);
+ Assert.Equal(EnumU32.SomeValue, dt.EnumU32);
+ Assert.Equal(EnumU16.SomeValue, dt.EnumU16);
+ Assert.Equal(EnumS8.SomeValue, dt.EnumS8);
+ }
+ }
+
+ [Fact]
+ public virtual void Can_insert_and_read_back_nullable_backed_data_types()
+ {
+ using (var context = CreateContext())
+ {
+ context.Set().Add(
+ new NullableBackedDataTypes
+ {
+ Id = 101,
+ PartitionId = 101,
+ Int16 = -1234,
+ Int32 = -123456789,
+ Int64 = -1234567890123456789L,
+ Double = -1.23456789,
+ Decimal = -1234567890.01M,
+ DateTime = DateTime.Parse("01/01/2000 12:34:56"),
+ DateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)),
+ TimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ Single = -1.234F,
+ Boolean = false,
+ Byte = 255,
+ UnsignedInt16 = 1234,
+ UnsignedInt32 = 1234565789U,
+ UnsignedInt64 = 1234567890123456789UL,
+ Character = 'a',
+ SignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
+
+ Assert.Equal(1, context.SaveChanges());
+ }
+
+ using (var context = CreateContext())
+ {
+ var dt = context.Set().Where(ndt => ndt.Id == 101).ToList().Single();
+
+ Assert.Equal((short)-1234, dt.Int16);
+ Assert.Equal(-123456789, dt.Int32);
+ Assert.Equal(-1234567890123456789L, dt.Int64);
+ Assert.Equal(-1.23456789, dt.Double);
+ Assert.Equal(-1234567890.01M, dt.Decimal);
+ Assert.Equal(DateTime.Parse("01/01/2000 12:34:56"), dt.DateTime);
+ Assert.Equal(new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)), dt.DateTimeOffset);
+ Assert.Equal(new TimeSpan(0, 10, 9, 8, 7), dt.TimeSpan);
+ Assert.Equal(-1.234F, dt.Single);
+ Assert.Equal(false, dt.Boolean);
+ Assert.Equal((byte)255, dt.Byte);
+ Assert.Equal(Enum64.SomeValue, dt.Enum64);
+ Assert.Equal(Enum32.SomeValue, dt.Enum32);
+ Assert.Equal(Enum16.SomeValue, dt.Enum16);
+ Assert.Equal(Enum8.SomeValue, dt.Enum8);
+ Assert.Equal((ushort)1234, dt.UnsignedInt16);
+ Assert.Equal(1234565789U, dt.UnsignedInt32);
+ Assert.Equal(1234567890123456789UL, dt.UnsignedInt64);
+ Assert.Equal('a', dt.Character);
+ Assert.Equal((sbyte)-128, dt.SignedByte);
+ Assert.Equal(EnumU64.SomeValue, dt.EnumU64);
+ Assert.Equal(EnumU32.SomeValue, dt.EnumU32);
+ Assert.Equal(EnumU16.SomeValue, dt.EnumU16);
+ Assert.Equal(EnumS8.SomeValue, dt.EnumS8);
+ }
+ }
+
+ [Fact]
+ public virtual void Can_insert_and_read_back_non_nullable_backed_data_types()
+ {
+ using (var context = CreateContext())
+ {
+ context.Set().Add(
+ new NonNullableBackedDataTypes
+ {
+ Id = 101,
+ PartitionId = 101,
+ Int16 = -1234,
+ Int32 = -123456789,
+ Int64 = -1234567890123456789L,
+ Double = -1.23456789,
+ Decimal = -1234567890.01M,
+ DateTime = DateTime.Parse("01/01/2000 12:34:56"),
+ DateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)),
+ TimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ Single = -1.234F,
+ Boolean = false,
+ Byte = 255,
+ UnsignedInt16 = 1234,
+ UnsignedInt32 = 1234565789U,
+ UnsignedInt64 = 1234567890123456789UL,
+ Character = 'a',
+ SignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
+
+ Assert.Equal(1, context.SaveChanges());
+ }
+
+ using (var context = CreateContext())
+ {
+ var dt = context.Set().Where(ndt => ndt.Id == 101).ToList().Single();
+
+ Assert.Equal((short)-1234, dt.Int16);
+ Assert.Equal(-123456789, dt.Int32);
+ Assert.Equal(-1234567890123456789L, dt.Int64);
+ Assert.Equal(-1.23456789, dt.Double);
+ Assert.Equal(-1234567890.01M, dt.Decimal);
+ Assert.Equal(DateTime.Parse("01/01/2000 12:34:56"), dt.DateTime);
+ Assert.Equal(new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)), dt.DateTimeOffset);
+ Assert.Equal(new TimeSpan(0, 10, 9, 8, 7), dt.TimeSpan);
+ Assert.Equal(-1.234F, dt.Single);
+ Assert.Equal(false, dt.Boolean);
+ Assert.Equal((byte)255, dt.Byte);
+ Assert.Equal(Enum64.SomeValue, dt.Enum64);
+ Assert.Equal(Enum32.SomeValue, dt.Enum32);
+ Assert.Equal(Enum16.SomeValue, dt.Enum16);
+ Assert.Equal(Enum8.SomeValue, dt.Enum8);
+ Assert.Equal((ushort)1234, dt.UnsignedInt16);
+ Assert.Equal(1234565789U, dt.UnsignedInt32);
+ Assert.Equal(1234567890123456789UL, dt.UnsignedInt64);
+ Assert.Equal('a', dt.Character);
+ Assert.Equal((sbyte)-128, dt.SignedByte);
+ Assert.Equal(EnumU64.SomeValue, dt.EnumU64);
+ Assert.Equal(EnumU32.SomeValue, dt.EnumU32);
+ Assert.Equal(EnumU16.SomeValue, dt.EnumU16);
+ Assert.Equal(EnumS8.SomeValue, dt.EnumS8);
+ }
+ }
+
public abstract class BuiltInDataTypesFixtureBase : SharedStoreFixtureBase
{
protected override string StoreName { get; } = "BuiltInDataTypes";
@@ -1806,6 +2021,101 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
TemplateType = EmailTemplateType.PasswordResetRequest
});
});
+
+ modelBuilder.Entity()
+ .HasData(new ObjectBackedDataTypes
+ {
+ Id = 13,
+ PartitionId = 1,
+ String = "string",
+ Bytes = new byte[] { 4, 20 },
+ Int16 = -1234,
+ Int32 = -123456789,
+ Int64 = -1234567890123456789L,
+ Double = -1.23456789,
+ Decimal = -1234567890.01M,
+ DateTime = new DateTime(1973, 9,3),
+ DateTimeOffset = new DateTimeOffset(new DateTime(), TimeSpan.FromHours(-8.0)),
+ TimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ Single = -1.234F,
+ Boolean = true,
+ Byte = 255,
+ UnsignedInt16 = 1234,
+ UnsignedInt32 = 1234565789U,
+ UnsignedInt64 = 1234567890123456789UL,
+ Character = 'a',
+ SignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
+
+ modelBuilder.Entity()
+ .HasData(new NullableBackedDataTypes
+ {
+ Id = 13,
+ PartitionId = 1,
+ Int16 = -1234,
+ Int32 = -123456789,
+ Int64 = -1234567890123456789L,
+ Double = -1.23456789,
+ Decimal = -1234567890.01M,
+ DateTime = new DateTime(1973, 9,3),
+ DateTimeOffset = new DateTimeOffset(new DateTime(), TimeSpan.FromHours(-8.0)),
+ TimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ Single = -1.234F,
+ Boolean = true,
+ Byte = 255,
+ UnsignedInt16 = 1234,
+ UnsignedInt32 = 1234565789U,
+ UnsignedInt64 = 1234567890123456789UL,
+ Character = 'a',
+ SignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
+
+ modelBuilder.Entity()
+ .HasData(new NonNullableBackedDataTypes
+ {
+ Id = 13,
+ PartitionId = 1,
+ Int16 = -1234,
+ Int32 = -123456789,
+ Int64 = -1234567890123456789L,
+ Double = -1.23456789,
+ Decimal = -1234567890.01M,
+ DateTime = new DateTime(1973, 9,3),
+ DateTimeOffset = new DateTimeOffset(new DateTime(), TimeSpan.FromHours(-8.0)),
+ TimeSpan = new TimeSpan(0, 10, 9, 8, 7),
+ Single = -1.234F,
+ Boolean = true,
+ Byte = 255,
+ UnsignedInt16 = 1234,
+ UnsignedInt32 = 1234565789U,
+ UnsignedInt64 = 1234567890123456789UL,
+ Character = 'a',
+ SignedByte = -128,
+ Enum64 = Enum64.SomeValue,
+ Enum32 = Enum32.SomeValue,
+ Enum16 = Enum16.SomeValue,
+ Enum8 = Enum8.SomeValue,
+ EnumU64 = EnumU64.SomeValue,
+ EnumU32 = EnumU32.SomeValue,
+ EnumU16 = EnumU16.SomeValue,
+ EnumS8 = EnumS8.SomeValue
+ });
}
protected static void MakeRequired(ModelBuilder modelBuilder)
@@ -2024,5 +2334,547 @@ protected enum EmailTemplateTypeDto
PasswordResetRequest = 0,
EmailConfirmation = 1
}
+
+ protected class ObjectBackedDataTypes
+ {
+ private object _string;
+ private object _bytes;
+ private object _int16;
+ private object _int32;
+ private object _int64;
+ private object _double;
+ private object _decimal;
+ private object _dateTime;
+ private object _dateTimeOffset;
+ private object _timeSpan;
+ private object _single;
+ private object _boolean;
+ private object _byte;
+ private object _unsignedInt16;
+ private object _unsignedInt32;
+ private object _unsignedInt64;
+ private object _character;
+ private object _signedByte;
+ private object _enum64;
+ private object _enum32;
+ private object _enum16;
+ private object _enum8;
+ private object _enumU64;
+ private object _enumU32;
+ private object _enumU16;
+ private object _enumS8;
+
+ [DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int Id { get; set; }
+ public int PartitionId { get; set; }
+
+ public string String
+ {
+ get => (string)_string;
+ set => _string = value;
+ }
+
+ public byte[] Bytes
+ {
+ get => (byte[])_bytes;
+ set => _bytes = value;
+ }
+
+ public short Int16
+ {
+ get => (short)_int16;
+ set => _int16 = value;
+ }
+
+ public int Int32
+ {
+ get => (int)_int32;
+ set => _int32 = value;
+ }
+
+ public long Int64
+ {
+ get => (long)_int64;
+ set => _int64 = value;
+ }
+
+ public double Double
+ {
+ get => (double)_double;
+ set => _double = value;
+ }
+
+ public decimal Decimal
+ {
+ get => (decimal)_decimal;
+ set => _decimal = value;
+ }
+
+ public DateTime DateTime
+ {
+ get => (DateTime)_dateTime;
+ set => _dateTime = value;
+ }
+
+ public DateTimeOffset DateTimeOffset
+ {
+ get => (DateTimeOffset)_dateTimeOffset;
+ set => _dateTimeOffset = value;
+ }
+
+ public TimeSpan TimeSpan
+ {
+ get => (TimeSpan)_timeSpan;
+ set => _timeSpan = value;
+ }
+
+ public float Single
+ {
+ get => (float)_single;
+ set => _single = value;
+ }
+
+ public bool Boolean
+ {
+ get => (bool)_boolean;
+ set => _boolean = value;
+ }
+
+ public byte Byte
+ {
+ get => (byte)_byte;
+ set => _byte = value;
+ }
+
+ public ushort UnsignedInt16
+ {
+ get => (ushort)_unsignedInt16;
+ set => _unsignedInt16 = value;
+ }
+
+ public uint UnsignedInt32
+ {
+ get => (uint)_unsignedInt32;
+ set => _unsignedInt32 = value;
+ }
+
+ public ulong UnsignedInt64
+ {
+ get => (ulong)_unsignedInt64;
+ set => _unsignedInt64 = value;
+ }
+
+ public char Character
+ {
+ get => (char)_character;
+ set => _character = value;
+ }
+
+ public sbyte SignedByte
+ {
+ get => (sbyte)_signedByte;
+ set => _signedByte = value;
+ }
+
+ public Enum64 Enum64
+ {
+ get => (Enum64)_enum64;
+ set => _enum64 = value;
+ }
+
+ public Enum32 Enum32
+ {
+ get => (Enum32)_enum32;
+ set => _enum32 = value;
+ }
+
+ public Enum16 Enum16
+ {
+ get => (Enum16)_enum16;
+ set => _enum16 = value;
+ }
+
+ public Enum8 Enum8
+ {
+ get => (Enum8)_enum8;
+ set => _enum8 = value;
+ }
+
+ public EnumU64 EnumU64
+ {
+ get => (EnumU64)_enumU64;
+ set => _enumU64 = value;
+ }
+
+ public EnumU32 EnumU32
+ {
+ get => (EnumU32)_enumU32;
+ set => _enumU32 = value;
+ }
+
+ public EnumU16 EnumU16
+ {
+ get => (EnumU16)_enumU16;
+ set => _enumU16 = value;
+ }
+
+ public EnumS8 EnumS8
+ {
+ get => (EnumS8)_enumS8;
+ set => _enumS8 = value;
+ }
+ }
+
+ protected class NullableBackedDataTypes
+ {
+ private short? _int16;
+ private int? _int32;
+ private long? _int64;
+ private double? _double;
+ private decimal? _decimal;
+ private DateTime? _dateTime;
+ private DateTimeOffset? _dateTimeOffset;
+ private TimeSpan? _timeSpan;
+ private float? _single;
+ private bool? _boolean;
+ private byte? _byte;
+ private ushort? _unsignedInt16;
+ private uint? _unsignedInt32;
+ private ulong? _unsignedInt64;
+ private char? _character;
+ private sbyte? _signedByte;
+ private Enum64? _enum64;
+ private Enum32? _enum32;
+ private Enum16? _enum16;
+ private Enum8? _enum8;
+ private EnumU64? _enumU64;
+ private EnumU32? _enumU32;
+ private EnumU16? _enumU16;
+ private EnumS8? _enumS8;
+
+ [DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int Id { get; set; }
+ public int PartitionId { get; set; }
+
+ public short Int16
+ {
+ get => (short)_int16;
+ set => _int16 = value;
+ }
+
+ public int Int32
+ {
+ get => (int)_int32;
+ set => _int32 = value;
+ }
+
+ public long Int64
+ {
+ get => (long)_int64;
+ set => _int64 = value;
+ }
+
+ public double Double
+ {
+ get => (double)_double;
+ set => _double = value;
+ }
+
+ public decimal Decimal
+ {
+ get => (decimal)_decimal;
+ set => _decimal = value;
+ }
+
+ public DateTime DateTime
+ {
+ get => (DateTime)_dateTime;
+ set => _dateTime = value;
+ }
+
+ public DateTimeOffset DateTimeOffset
+ {
+ get => (DateTimeOffset)_dateTimeOffset;
+ set => _dateTimeOffset = value;
+ }
+
+ public TimeSpan TimeSpan
+ {
+ get => (TimeSpan)_timeSpan;
+ set => _timeSpan = value;
+ }
+
+ public float Single
+ {
+ get => (float)_single;
+ set => _single = value;
+ }
+
+ public bool Boolean
+ {
+ get => (bool)_boolean;
+ set => _boolean = value;
+ }
+
+ public byte Byte
+ {
+ get => (byte)_byte;
+ set => _byte = value;
+ }
+
+ public ushort UnsignedInt16
+ {
+ get => (ushort)_unsignedInt16;
+ set => _unsignedInt16 = value;
+ }
+
+ public uint UnsignedInt32
+ {
+ get => (uint)_unsignedInt32;
+ set => _unsignedInt32 = value;
+ }
+
+ public ulong UnsignedInt64
+ {
+ get => (ulong)_unsignedInt64;
+ set => _unsignedInt64 = value;
+ }
+
+ public char Character
+ {
+ get => (char)_character;
+ set => _character = value;
+ }
+
+ public sbyte SignedByte
+ {
+ get => (sbyte)_signedByte;
+ set => _signedByte = value;
+ }
+
+ public Enum64 Enum64
+ {
+ get => (Enum64)_enum64;
+ set => _enum64 = value;
+ }
+
+ public Enum32 Enum32
+ {
+ get => (Enum32)_enum32;
+ set => _enum32 = value;
+ }
+
+ public Enum16 Enum16
+ {
+ get => (Enum16)_enum16;
+ set => _enum16 = value;
+ }
+
+ public Enum8 Enum8
+ {
+ get => (Enum8)_enum8;
+ set => _enum8 = value;
+ }
+
+ public EnumU64 EnumU64
+ {
+ get => (EnumU64)_enumU64;
+ set => _enumU64 = value;
+ }
+
+ public EnumU32 EnumU32
+ {
+ get => (EnumU32)_enumU32;
+ set => _enumU32 = value;
+ }
+
+ public EnumU16 EnumU16
+ {
+ get => (EnumU16)_enumU16;
+ set => _enumU16 = value;
+ }
+
+ public EnumS8 EnumS8
+ {
+ get => (EnumS8)_enumS8;
+ set => _enumS8 = value;
+ }
+ }
+
+ protected class NonNullableBackedDataTypes
+ {
+ private short _int16;
+ private int _int32;
+ private long _int64;
+ private double _double;
+ private decimal _decimal;
+ private DateTime _dateTime;
+ private DateTimeOffset _dateTimeOffset;
+ private TimeSpan _timeSpan;
+ private float _single;
+ private bool _boolean;
+ private byte _byte;
+ private ushort _unsignedInt16;
+ private uint _unsignedInt32;
+ private ulong _unsignedInt64;
+ private char _character;
+ private sbyte _signedByte;
+ private Enum64 _enum64;
+ private Enum32 _enum32;
+ private Enum16 _enum16;
+ private Enum8 _enum8;
+ private EnumU64 _enumU64;
+ private EnumU32 _enumU32;
+ private EnumU16 _enumU16;
+ private EnumS8 _enumS8;
+
+ [DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int Id { get; set; }
+ public int PartitionId { get; set; }
+
+ public short? Int16
+ {
+ get => _int16;
+ set => _int16 = (short)value;
+ }
+
+ public int? Int32
+ {
+ get => _int32;
+ set => _int32 = (int)value;
+ }
+
+ public long? Int64
+ {
+ get => _int64;
+ set => _int64 = (long)value;
+ }
+
+ public double? Double
+ {
+ get => _double;
+ set => _double = (double)value;
+ }
+
+ public decimal? Decimal
+ {
+ get => _decimal;
+ set => _decimal = (decimal)value;
+ }
+
+ public DateTime? DateTime
+ {
+ get => _dateTime;
+ set => _dateTime = (DateTime)value;
+ }
+
+ public DateTimeOffset? DateTimeOffset
+ {
+ get => _dateTimeOffset;
+ set => _dateTimeOffset = (DateTimeOffset)value;
+ }
+
+ public TimeSpan? TimeSpan
+ {
+ get => _timeSpan;
+ set => _timeSpan = (TimeSpan)value;
+ }
+
+ public float? Single
+ {
+ get => _single;
+ set => _single = (float)value;
+ }
+
+ public bool? Boolean
+ {
+ get => _boolean;
+ set => _boolean = (bool)value;
+ }
+
+ public byte? Byte
+ {
+ get => _byte;
+ set => _byte = (byte)value;
+ }
+
+ public ushort? UnsignedInt16
+ {
+ get => _unsignedInt16;
+ set => _unsignedInt16 = (ushort)value;
+ }
+
+ public uint? UnsignedInt32
+ {
+ get => _unsignedInt32;
+ set => _unsignedInt32 = (uint)value;
+ }
+
+ public ulong? UnsignedInt64
+ {
+ get => _unsignedInt64;
+ set => _unsignedInt64 = (ulong)value;
+ }
+
+ public char? Character
+ {
+ get => _character;
+ set => _character = (char)value;
+ }
+
+ public sbyte? SignedByte
+ {
+ get => _signedByte;
+ set => _signedByte = (sbyte)value;
+ }
+
+ public Enum64? Enum64
+ {
+ get => _enum64;
+ set => _enum64 = (Enum64)value;
+ }
+
+ public Enum32? Enum32
+ {
+ get => _enum32;
+ set => _enum32 = (Enum32)value;
+ }
+
+ public Enum16? Enum16
+ {
+ get => _enum16;
+ set => _enum16 = (Enum16)value;
+ }
+
+ public Enum8? Enum8
+ {
+ get => _enum8;
+ set => _enum8 = (Enum8)value;
+ }
+
+ public EnumU64? EnumU64
+ {
+ get => _enumU64;
+ set => _enumU64 = (EnumU64)value;
+ }
+
+ public EnumU32? EnumU32
+ {
+ get => _enumU32;
+ set => _enumU32 = (EnumU32)value;
+ }
+
+ public EnumU16? EnumU16
+ {
+ get => _enumU16;
+ set => _enumU16 = (EnumU16)value;
+ }
+
+ public EnumS8? EnumS8
+ {
+ get => _enumS8;
+ set => _enumS8 = (EnumS8)value;
+ }
+ }
}
}
diff --git a/test/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs b/test/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs
index 50d360cad74..982f76cf023 100644
--- a/test/EFCore.Specification.Tests/ConvertToProviderTypesTestBase.cs
+++ b/test/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
@@ -11,6 +13,36 @@ protected ConvertToProviderTypesTestBase(TFixture fixture)
{
}
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_data_type()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_data_type_shadow()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_data_type_nullable_shadow()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_nullable_data_type()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_nullable_data_type_as_literal()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
public abstract class ConvertToProviderTypesFixtureBase : BuiltInDataTypesFixtureBase
{
protected override string StoreName { get; } = "ConvertToProviderTypes";
diff --git a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs
index 1da9ce20444..54dd57c4d52 100644
--- a/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs
+++ b/test/EFCore.Specification.Tests/CustomConvertersTestBase.cs
@@ -19,6 +19,42 @@ protected CustomConvertersTestBase(TFixture fixture)
{
}
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_data_type()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_data_type_shadow()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_data_type_nullable_shadow()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_nullable_data_type()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_using_any_nullable_data_type_as_literal()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
+ [Fact(Skip = "QueryIssue")]
+ public override void Can_query_with_null_parameters_using_any_nullable_data_type()
+ {
+ base.Can_query_using_any_data_type();
+ }
+
[Fact]
public virtual void Can_query_and_update_with_nullable_converter_on_unique_index()
{
diff --git a/test/EFCore.Specification.Tests/FieldMappingTestBase.cs b/test/EFCore.Specification.Tests/FieldMappingTestBase.cs
index abf2b7e09c2..ba8d90a4322 100644
--- a/test/EFCore.Specification.Tests/FieldMappingTestBase.cs
+++ b/test/EFCore.Specification.Tests/FieldMappingTestBase.cs
@@ -27,7 +27,22 @@ protected interface IUser2
protected class User2 : IUser2
{
+ private object _loginSessionId;
+ private object _loginSessionId2;
+
public int Id { get; set; }
+
+ public int? LoginSessionId
+ {
+ get => (int?)_loginSessionId;
+ set => _loginSessionId = value;
+ }
+
+ public int? LoginSessionId2
+ {
+ get => (int?)_loginSessionId2;
+ set => _loginSessionId2 = value;
+ }
}
protected class LoginSession
@@ -1883,7 +1898,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity().Metadata.FindNavigation("Blog").SetField("_myblog");
modelBuilder.Entity().Metadata.FindNavigation("Posts").SetField("_myposts");
- modelBuilder.Entity().UsePropertyAccessMode(PropertyAccessMode.Field);
+ modelBuilder.Entity(
+ b =>
+ {
+ b.UsePropertyAccessMode(PropertyAccessMode.Field);
+
+ b.HasOne(e => e.User).WithOne().HasForeignKey(e => e.LoginSessionId);
+ b.HasMany(e => e.Users).WithOne().HasForeignKey(e => e.LoginSessionId2);
+ });
if (modelBuilder.Model.GetPropertyAccessMode() != PropertyAccessMode.Property)
{
diff --git a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs
index fbba62259d3..776b2462df7 100644
--- a/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs
+++ b/test/EFCore.Specification.Tests/StoreGeneratedTestBase.cs
@@ -1068,6 +1068,80 @@ public virtual void Fields_used_correctly_for_store_generated_values()
});
}
+ [Fact]
+ public virtual void Nullable_fields_get_defaults_when_not_set()
+ {
+ ExecuteWithStrategyInTransaction(
+ context =>
+ {
+ var entity = context.Add(new WithNullableBackingFields()).Entity;
+
+ context.SaveChanges();
+
+ Assert.NotEqual(0, entity.Id);
+ Assert.True(entity.NullableBackedBool);
+ Assert.Equal(-1, entity.NullableBackedInt);
+ },
+ context =>
+ {
+ var entity = context.Set().Single();
+ Assert.True(entity.NullableBackedBool);
+ Assert.Equal(-1, entity.NullableBackedInt);
+ });
+ }
+
+ [Fact]
+ public virtual void Nullable_fields_store_non_defaults_when_set()
+ {
+ ExecuteWithStrategyInTransaction(
+ context =>
+ {
+ var entity = context.Add(new WithNullableBackingFields
+ {
+ NullableBackedBool = false,
+ NullableBackedInt = 0
+ }).Entity;
+
+ context.SaveChanges();
+
+ Assert.NotEqual(0, entity.Id);
+ Assert.False(entity.NullableBackedBool);
+ Assert.Equal(0, entity.NullableBackedInt);
+ },
+ context =>
+ {
+ var entity = context.Set().Single();
+ Assert.False(entity.NullableBackedBool);
+ Assert.Equal(0, entity.NullableBackedInt);
+ });
+ }
+
+ [Fact]
+ public virtual void Nullable_fields_store_any_value_when_set()
+ {
+ ExecuteWithStrategyInTransaction(
+ context =>
+ {
+ var entity = context.Add(new WithNullableBackingFields
+ {
+ NullableBackedBool = true,
+ NullableBackedInt = 3
+ }).Entity;
+
+ context.SaveChanges();
+
+ Assert.NotEqual(0, entity.Id);
+ Assert.True(entity.NullableBackedBool);
+ Assert.Equal(3, entity.NullableBackedInt);
+ },
+ context =>
+ {
+ var entity = context.Set().Single();
+ Assert.True(entity.NullableBackedBool);
+ Assert.Equal(3, entity.NullableBackedInt);
+ });
+ }
+
protected class Darwin
{
public int Id { get; set; }
@@ -1148,13 +1222,12 @@ protected class WithBackingFields
{
private int _id;
-#pragma warning disable RCS1085 // Use auto-implemented property.
+ // ReSharper disable once ConvertToAutoProperty
public int Id
{
get => _id;
set => _id = value;
}
-#pragma warning restore RCS1085 // Use auto-implemented property.
private int? _nullableAsNonNullable = 0;
@@ -1173,6 +1246,31 @@ public int? NonNullableAsNullable
}
}
+ protected class WithNullableBackingFields
+ {
+ private int? _id;
+
+ public int Id
+ {
+ get => _id ?? 0;
+ set => _id = value;
+ }
+
+ private bool? _nullableBackedBool;
+ public bool NullableBackedBool
+ {
+ get => _nullableBackedBool ?? false;
+ set => _nullableBackedBool = value;
+ }
+
+ private int? _nullableBackedInt;
+ public int NullableBackedInt
+ {
+ get => _nullableBackedInt ?? 0;
+ set => _nullableBackedInt = value;
+ }
+ }
+
protected class WithConverter
{
public TKey Id { get; set; }
diff --git a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
index 6e7efca1bd1..613ed19eedd 100644
--- a/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs
@@ -2368,7 +2368,9 @@ public virtual void Can_insert_and_read_back_all_mapped_data_types_with_precisio
[ConditionalFact]
public virtual void Columns_have_expected_data_types()
{
- var actual = QueryForColumnTypes(CreateContext());
+ var actual = QueryForColumnTypes(
+ CreateContext(),
+ nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
const string expected = @"BinaryForeignKeyDataType.BinaryKeyDataTypeId ---> [nullable varbinary] [MaxLength = 900]
BinaryForeignKeyDataType.Id ---> [int] [Precision = 10 Scale = 0]
@@ -2805,7 +2807,7 @@ public void Can_get_column_types_from_built_model()
}
}
- public static string QueryForColumnTypes(DbContext context)
+ public static string QueryForColumnTypes(DbContext context, params string[] tablesToIgnore)
{
const string query
= @"SELECT
@@ -2844,7 +2846,10 @@ const string query
DateTimePrecision = reader.IsDBNull(7) ? null : (int?)reader.GetInt16(7)
};
- columns.Add(columnInfo);
+ if (!tablesToIgnore.Contains(columnInfo.TableName))
+ {
+ columns.Add(columnInfo);
+ }
}
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs
index 5d5e8696cdf..a50177f30cf 100644
--- a/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs
@@ -42,7 +42,9 @@ public virtual void Warning_when_suspicious_conversion_in_sql()
[ConditionalFact]
public virtual void Columns_have_expected_data_types()
{
- var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(CreateContext());
+ var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(
+ CreateContext(),
+ nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
const string expected = @"BinaryForeignKeyDataType.BinaryKeyDataTypeId ---> [nullable nvarchar] [MaxLength = 450]
BinaryForeignKeyDataType.Id ---> [int] [Precision = 10 Scale = 0]
diff --git a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs
index 6aeec85c6f6..235631e358c 100644
--- a/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs
@@ -21,7 +21,9 @@ public CustomConvertersSqlServerTest(CustomConvertersSqlServerFixture fixture)
[ConditionalFact]
public virtual void Columns_have_expected_data_types()
{
- var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(CreateContext());
+ var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(
+ CreateContext(),
+ nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
const string expected = @"BinaryForeignKeyDataType.BinaryKeyDataTypeId ---> [nullable varbinary] [MaxLength = 900]
BinaryForeignKeyDataType.Id ---> [int] [Precision = 10 Scale = 0]
diff --git a/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs
index fd529d2bf05..499a87b9a9c 100644
--- a/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs
@@ -25,7 +25,9 @@ public EverythingIsBytesSqlServerTest(EverythingIsBytesSqlServerFixture fixture)
[ConditionalFact]
public virtual void Columns_have_expected_data_types()
{
- var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(CreateContext());
+ var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(
+ CreateContext(),
+ nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
const string expected = @"BinaryForeignKeyDataType.BinaryKeyDataTypeId ---> [nullable varbinary] [MaxLength = 900]
BinaryForeignKeyDataType.Id ---> [varbinary] [MaxLength = 4]
@@ -243,29 +245,26 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
}
}
- if (clrType != null)
+ if (clrType == typeof(byte[]))
{
- if (clrType == typeof(byte[]))
+ if (mappingInfo.IsRowVersion == true)
{
- if (mappingInfo.IsRowVersion == true)
- {
- return _rowversion;
- }
-
- var isFixedLength = mappingInfo.IsFixedLength == true;
+ return _rowversion;
+ }
- var size = mappingInfo.Size ?? (mappingInfo.IsKeyOrIndex ? (int?)900 : null);
- if (size > 8000)
- {
- size = isFixedLength ? 8000 : (int?)null;
- }
+ var isFixedLength = mappingInfo.IsFixedLength == true;
- return new SqlServerByteArrayTypeMapping(
- "varbinary(" + (size == null ? "max" : size.ToString()) + ")",
- size,
- isFixedLength,
- storeTypePostfix: size == null ? StoreTypePostfix.None : (StoreTypePostfix?)null);
+ var size = mappingInfo.Size ?? (mappingInfo.IsKeyOrIndex ? (int?)900 : null);
+ if (size > 8000)
+ {
+ size = isFixedLength ? 8000 : (int?)null;
}
+
+ return new SqlServerByteArrayTypeMapping(
+ "varbinary(" + (size == null ? "max" : size.ToString()) + ")",
+ size,
+ isFixedLength,
+ storeTypePostfix: size == null ? StoreTypePostfix.None : (StoreTypePostfix?)null);
}
return null;
diff --git a/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs
index 222b5a2e548..9a50a0fa610 100644
--- a/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs
@@ -26,7 +26,9 @@ public EverythingIsStringsSqlServerTest(EverythingIsStringsSqlServerFixture fixt
[ConditionalFact]
public virtual void Columns_have_expected_data_types()
{
- var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(CreateContext());
+ var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(
+ CreateContext(),
+ nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
const string expected = @"BinaryForeignKeyDataType.BinaryKeyDataTypeId ---> [nullable nvarchar] [MaxLength = 450]
BinaryForeignKeyDataType.Id ---> [nvarchar] [MaxLength = 64]
diff --git a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs
index 655e81cf3c3..8824422e767 100644
--- a/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/StoreGeneratedSqlServerTest.cs
@@ -163,6 +163,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(e => e.NonNullableAsNullable).HasComputedColumnSql("1");
});
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.NullableBackedBool).HasDefaultValue(true);
+ b.Property(e => e.NullableBackedInt).HasDefaultValue(-1);
+ });
+
base.OnModelCreating(modelBuilder, context);
}
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs
index 75df70f4842..abf1c2b8bbd 100644
--- a/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/StoreGeneratedSqliteTest.cs
@@ -82,6 +82,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(e => e.OnUpdateThrowBeforeThrowAfter).HasDefaultValue("Rabbit");
});
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.NullableBackedBool).HasDefaultValue(true);
+ b.Property(e => e.NullableBackedInt).HasDefaultValue(-1);
+ });
+
base.OnModelCreating(modelBuilder, context);
}
}
diff --git a/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs b/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs
index 0a7aa0dea92..e728e1ccced 100644
--- a/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs
@@ -6,7 +6,6 @@
using System.Runtime.CompilerServices;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
@@ -195,12 +194,19 @@ public void Setting_IsModified_is_not_reset_by_OriginalValues()
[Fact]
public void Can_get_name()
+ => Can_get_name();
+
+ [Fact]
+ public void Can_get_name_with_object_field()
+ => Can_get_name();
+
+ private void Can_get_name() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
var entry = context
.Entry(
- new Wotty
+ new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -216,12 +222,19 @@ public void Can_get_name()
[Fact]
public void Can_get_current_value()
+ => Can_get_current_value();
+
+ [Fact]
+ public void Can_get_current_value_with_object_field()
+ => Can_get_current_value();
+
+ private void Can_get_current_value() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
var entry = context
.Entry(
- new Wotty
+ new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -238,10 +251,17 @@ public void Can_get_current_value()
[Fact]
public void Can_set_current_value()
+ => Can_set_current_value();
+
+ [Fact]
+ public void Can_set_current_value_with_object_field()
+ => Can_set_current_value();
+
+ private void Can_set_current_value() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -265,10 +285,17 @@ public void Can_set_current_value()
[Fact]
public void Can_set_current_value_to_null()
+ => Can_set_current_value_to_null();
+
+ [Fact]
+ public void Can_set_current_value_to_null_with_object_field()
+ => Can_set_current_value_to_null();
+
+ private void Can_set_current_value_to_null() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -292,10 +319,17 @@ public void Can_set_current_value_to_null()
[Fact]
public void Can_set_and_get_original_value()
+ => Can_set_and_get_original_value();
+
+ [Fact]
+ public void Can_set_and_get_original_value_with_object_field()
+ => Can_set_and_get_original_value();
+
+ private void Can_set_and_get_original_value() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -328,10 +362,17 @@ public void Can_set_and_get_original_value()
[Fact]
public void Can_set_and_get_original_value_starting_null()
+ => Can_set_and_get_original_value_starting_null();
+
+ [Fact]
+ public void Can_set_and_get_original_value_starting_null_with_object_field()
+ => Can_set_and_get_original_value_starting_null();
+
+ private void Can_set_and_get_original_value_starting_null() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1
};
@@ -362,10 +403,17 @@ public void Can_set_and_get_original_value_starting_null()
[Fact]
public void Can_set_original_value_to_null()
+ => Can_set_original_value_to_null();
+
+ [Fact]
+ public void Can_set_original_value_to_null_with_object_field()
+ => Can_set_original_value_to_null();
+
+ private void Can_set_original_value_to_null() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -389,10 +437,17 @@ public void Can_set_original_value_to_null()
[Fact]
public void Can_set_and_clear_modified_on_Modified_entity()
+ => Can_set_and_clear_modified_on_Modified_entity();
+
+ [Fact]
+ public void Can_set_and_clear_modified_on_Modified_entity_with_object_field()
+ => Can_set_and_clear_modified_on_Modified_entity();
+
+ private void Can_set_and_clear_modified_on_Modified_entity() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1
};
@@ -435,10 +490,19 @@ public void Can_set_and_clear_modified_on_Modified_entity()
[InlineData(EntityState.Added)]
[InlineData(EntityState.Deleted)]
public void Can_set_and_clear_modified_on_Added_or_Deleted_entity(EntityState initialState)
+ => Can_set_and_clear_modified_on_Added_or_Deleted_entity(initialState);
+
+ [Theory]
+ [InlineData(EntityState.Added)]
+ [InlineData(EntityState.Deleted)]
+ public void Can_set_and_clear_modified_on_Added_or_Deleted_entity_with_object_field(EntityState initialState)
+ => Can_set_and_clear_modified_on_Added_or_Deleted_entity(initialState);
+
+ private void Can_set_and_clear_modified_on_Added_or_Deleted_entity(EntityState initialState) where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1
};
@@ -472,10 +536,19 @@ public void Can_set_and_clear_modified_on_Added_or_Deleted_entity(EntityState in
[InlineData(EntityState.Detached)]
[InlineData(EntityState.Unchanged)]
public void Can_set_and_clear_modified_on_Unchanged_or_Detached_entity(EntityState initialState)
+ => Can_set_and_clear_modified_on_Unchanged_or_Detached_entity(initialState);
+
+ [Theory]
+ [InlineData(EntityState.Detached)]
+ [InlineData(EntityState.Unchanged)]
+ public void Can_set_and_clear_modified_on_Unchanged_or_Detached_entity_with_object_field(EntityState initialState)
+ => Can_set_and_clear_modified_on_Unchanged_or_Detached_entity(initialState);
+
+ private void Can_set_and_clear_modified_on_Unchanged_or_Detached_entity(EntityState initialState) where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1
};
@@ -507,10 +580,17 @@ public void Can_set_and_clear_modified_on_Unchanged_or_Detached_entity(EntitySta
[Fact]
public void Can_reject_changes_when_clearing_modified_flag()
+ => Can_reject_changes_when_clearing_modified_flag();
+
+ [Fact]
+ public void Can_reject_changes_when_clearing_modified_flag_with_object_field()
+ => Can_reject_changes_when_clearing_modified_flag();
+
+ private void Can_reject_changes_when_clearing_modified_flag() where TWotty : IWotty, new()
{
using (var context = new PrimateContext())
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey",
@@ -592,11 +672,18 @@ public void Can_reject_changes_when_clearing_modified_flag()
[Fact]
public void Can_get_name_generic()
+ => Can_get_name_generic();
+
+ [Fact]
+ public void Can_get_name_generic_with_object_field()
+ => Can_get_name_generic();
+
+ private void Can_get_name_generic() where TWotty : class, IWotty, new()
{
var entry = InMemoryTestHelpers.Instance.CreateInternalEntry(
BuildModel(),
EntityState.Unchanged,
- new Wotty
+ new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -607,11 +694,18 @@ public void Can_get_name_generic()
[Fact]
public void Can_get_current_value_generic()
+ => Can_get_current_value_generic();
+
+ [Fact]
+ public void Can_get_current_value_generic_with_object_field()
+ => Can_get_current_value_generic();
+
+ private void Can_get_current_value_generic() where TWotty : class, IWotty, new()
{
var entry = InMemoryTestHelpers.Instance.CreateInternalEntry(
BuildModel(),
EntityState.Unchanged,
- new Wotty
+ new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -622,8 +716,15 @@ public void Can_get_current_value_generic()
[Fact]
public void Can_set_current_value_generic()
+ => Can_set_current_value_generic();
+
+ [Fact]
+ public void Can_set_current_value_generic_with_object_field()
+ => Can_set_current_value_generic();
+
+ private void Can_set_current_value_generic() where TWotty : class, IWotty, new()
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -641,8 +742,15 @@ public void Can_set_current_value_generic()
[Fact]
public void Can_set_current_value_to_null_generic()
+ => Can_set_current_value_to_null_generic();
+
+ [Fact]
+ public void Can_set_current_value_to_null_generic_with_object_field()
+ => Can_set_current_value_to_null_generic();
+
+ private void Can_set_current_value_to_null_generic() where TWotty : class, IWotty, new()
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -660,8 +768,15 @@ public void Can_set_current_value_to_null_generic()
[Fact]
public void Can_set_and_get_original_value_generic()
+ => Can_set_and_get_original_value_generic();
+
+ [Fact]
+ public void Can_set_and_get_original_value_generic_with_object_field()
+ => Can_set_and_get_original_value_generic();
+
+ private void Can_set_and_get_original_value_generic() where TWotty : class, IWotty, new()
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -682,11 +797,18 @@ public void Can_set_and_get_original_value_generic()
[Fact]
public void Can_set_original_value_to_null_generic()
+ => Can_set_original_value_to_null_generic();
+
+ [Fact]
+ public void Can_set_original_value_to_null_generic_with_object_field()
+ => Can_set_original_value_to_null_generic();
+
+ private void Can_set_original_value_to_null_generic() where TWotty : class, IWotty, new()
{
var entry = InMemoryTestHelpers.Instance.CreateInternalEntry(
BuildModel(),
EntityState.Unchanged,
- new Wotty
+ new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -699,8 +821,15 @@ public void Can_set_original_value_to_null_generic()
[Fact]
public void Can_set_and_clear_modified_generic()
+ => Can_set_and_clear_modified_generic();
+
+ [Fact]
+ public void Can_set_and_clear_modified_generic_with_object_field()
+ => Can_set_and_clear_modified_generic();
+
+ private void Can_set_and_clear_modified_generic() where TWotty : class, IWotty, new()
{
- var entity = new Wotty
+ var entity = new TWotty
{
Id = 1,
Primate = "Monkey"
@@ -972,7 +1101,47 @@ public void Can_set_or_get_original_value_when_property_explicitly_marked_to_be_
Assert.Equal("Monkey", entity.Primate);
}
- private class Wotty
+ private interface IWotty
+ {
+ int Id { get; set; }
+ string Primate { get; set; }
+ string RequiredPrimate { get; set; }
+ string Marmate { get; set; }
+ }
+
+ private class ObjectWotty : IWotty
+ {
+ private object _id;
+ private object _primate;
+ private object _requiredPrimate;
+ private object _marmate;
+
+ public int Id
+ {
+ get => (int)_id;
+ set => _id = value;
+ }
+
+ public string Primate
+ {
+ get => (string)_primate;
+ set => _primate = value;
+ }
+
+ public string RequiredPrimate
+ {
+ get => (string)_requiredPrimate;
+ set => _requiredPrimate = value;
+ }
+
+ public string Marmate
+ {
+ get => (string)_marmate;
+ set => _marmate = value;
+ }
+ }
+
+ private class Wotty : IWotty
{
public int Id { get; set; }
public string Primate { get; set; }
@@ -1096,6 +1265,13 @@ public static IMutableModel BuildModel(
b.HasChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot);
});
+ builder.Entity(
+ b =>
+ {
+ b.Property(e => e.RequiredPrimate).IsRequired();
+ b.HasChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot);
+ });
+
builder.Entity(
b => b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications));
diff --git a/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs
index b3d54a058c3..181e8d8b762 100644
--- a/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs
@@ -43,6 +43,7 @@ private class FakeNavigation : INavigation, IClrCollectionAccessor
public string Name { get; }
public ITypeBase DeclaringType { get; }
public Type ClrType { get; }
+ public Type DeclaredClrType { get; }
public PropertyInfo PropertyInfo { get; }
public FieldInfo FieldInfo { get; }
public IEntityType DeclaringEntityType { get; }
diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs
index 97826693fbb..e07b3ce81bf 100644
--- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs
@@ -30,9 +30,9 @@ private class FakeProperty : IProperty, IClrPropertyGetter
public string Name { get; }
public ITypeBase DeclaringType { get; }
public Type ClrType { get; }
+ public Type DeclaredClrType { get; }
public IEntityType DeclaringEntityType { get; }
public bool IsNullable { get; }
- public bool IsStoreGeneratedAlways { get; }
public ValueGenerated ValueGenerated { get; }
public bool IsConcurrencyToken { get; }
public PropertyInfo PropertyInfo { get; }
diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs
index 77bb5a54342..97a318d1afc 100644
--- a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs
@@ -31,11 +31,9 @@ private class FakeProperty : IProperty, IClrPropertySetter
public string Name { get; }
public ITypeBase DeclaringType { get; }
public Type ClrType { get; }
+ public Type DeclaredClrType { get; }
public IEntityType DeclaringEntityType { get; }
public bool IsNullable { get; }
- public bool IsReadOnlyBeforeSave { get; }
- public bool IsReadOnlyAfterSave { get; }
- public bool IsStoreGeneratedAlways { get; }
public ValueGenerated ValueGenerated { get; }
public bool IsConcurrencyToken { get; }
public PropertyInfo PropertyInfo { get; }
diff --git a/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs b/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs
index 73952c35c3f..26d4c98dac2 100644
--- a/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/NavigationTest.cs
@@ -30,6 +30,7 @@ private class FakeNavigation : INavigation
public string Name { get; }
public ITypeBase DeclaringType { get; }
public Type ClrType { get; }
+ public Type DeclaredClrType { get; }
public PropertyInfo PropertyInfo { get; }
public FieldInfo FieldInfo { get; }
public IEntityType DeclaringEntityType { get; }
diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs
index fe26618a084..f336bd32cc5 100644
--- a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs
@@ -46,9 +46,9 @@ private class FakeProperty : IProperty
public string Name { get; }
public ITypeBase DeclaringType { get; }
public Type ClrType { get; }
+ public Type DeclaredClrType { get; }
public IEntityType DeclaringEntityType { get; }
public bool IsNullable { get; }
- public bool IsStoreGeneratedAlways { get; }
public ValueGenerated ValueGenerated { get; }
public bool IsConcurrencyToken { get; }
public PropertyInfo PropertyInfo { get; }
diff --git a/test/EFCore.Tests/Storage/EnumToNumberConverterTest.cs b/test/EFCore.Tests/Storage/EnumToNumberConverterTest.cs
index 404aef6afff..654f507381b 100644
--- a/test/EFCore.Tests/Storage/EnumToNumberConverterTest.cs
+++ b/test/EFCore.Tests/Storage/EnumToNumberConverterTest.cs
@@ -289,7 +289,7 @@ public void Enum_to_integer_converter_throws_for_bad_types()
CoreStrings.ConverterBadType(
typeof(EnumToNumberConverter).ShortDisplayName(),
"Guid",
- "int, long, short, byte, uint, ulong, ushort, sbyte, double, float, decimal"),
+ "int, long, short, byte, uint, ulong, ushort, sbyte, double, float, decimal, char"),
Assert.Throws(
() => new EnumToNumberConverter()).Message);
}
diff --git a/test/EFCore.Tests/Storage/ValueConverterSelectorTest.cs b/test/EFCore.Tests/Storage/ValueConverterSelectorTest.cs
index 9d491307e3e..40f137c0fcd 100644
--- a/test/EFCore.Tests/Storage/ValueConverterSelectorTest.cs
+++ b/test/EFCore.Tests/Storage/ValueConverterSelectorTest.cs
@@ -32,7 +32,8 @@ public void Can_get_converters_for_int_enums()
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
- (typeof(EnumToNumberConverter), default));
+ (typeof(EnumToNumberConverter), default),
+ (typeof(EnumToNumberConverter), default));
}
[Fact]
@@ -52,7 +53,8 @@ public void Can_get_converters_for_ulong_enums()
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
- (typeof(EnumToNumberConverter), default));
+ (typeof(EnumToNumberConverter), default),
+ (typeof(EnumToNumberConverter), default));
}
[Fact]
@@ -72,7 +74,8 @@ public void Can_get_converters_for_long_enums()
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
- (typeof(EnumToNumberConverter), default));
+ (typeof(EnumToNumberConverter), default),
+ (typeof(EnumToNumberConverter), default));
}
[Fact]
@@ -92,7 +95,8 @@ public void Can_get_converters_for_byte_enums()
(typeof(CompositeValueConverter), new ConverterMappingHints(size: 1)),
(typeof(EnumToNumberConverter), default),
(typeof(EnumToNumberConverter), default),
- (typeof(EnumToNumberConverter), default));
+ (typeof(EnumToNumberConverter), default),
+ (typeof(EnumToNumberConverter), default));
}
[Fact]
@@ -143,7 +147,8 @@ public void Can_get_converters_for_int()
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -162,7 +167,8 @@ public void Can_get_converters_for_uint()
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -181,7 +187,8 @@ public void Can_get_converters_for_sbyte()
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -200,7 +207,8 @@ public void Can_get_converters_for_byte()
(typeof(NumberToBytesConverter), new ConverterMappingHints(size: 1)),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -219,7 +227,8 @@ public void Can_get_converters_for_double()
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -238,7 +247,8 @@ public void Can_get_converters_for_float()
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -257,7 +267,8 @@ public void Can_get_converters_for_decimal()
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
(typeof(CastingConverter), default),
- (typeof(CastingConverter), default));
+ (typeof(CastingConverter), default),
+ (typeof(CastingConverter), default));
}
[Fact]
@@ -366,6 +377,7 @@ public void Can_get_converters_for_bool()
(typeof(BoolToZeroOneConverter), default),
(typeof(BoolToZeroOneConverter), default),
(typeof(BoolToZeroOneConverter), default),
+ (typeof(BoolToZeroOneConverter), default),
(typeof(BoolToStringConverter), new ConverterMappingHints(size: 1)),
(typeof(CompositeValueConverter), new ConverterMappingHints(size: 1)));
}