Skip to content

Commit

Permalink
Support value generation for converted types
Browse files Browse the repository at this point in the history
Providers may need to be updated to support this.

Fixes #11597
  • Loading branch information
ajcvickers committed Apr 5, 2022
1 parent 2500217 commit 1baf270
Show file tree
Hide file tree
Showing 21 changed files with 3,680 additions and 378 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,14 @@ public CosmosValueGeneratorSelector(ValueGeneratorSelectorDependencies dependenc
/// 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.
/// </summary>
public override ValueGenerator Create(IProperty property, IEntityType entityType)
protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType)
{
var type = property.ClrType.UnwrapNullableType().UnwrapEnumType();

if (property.GetJsonPropertyName() == ""
&& type == typeof(int))
&& clrType == typeof(int))
{
return new TemporaryNumberValueGeneratorFactory().Create(property, entityType);
}

return base.Create(property, entityType);
return base.FindForType(property, entityType, clrType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ private static bool IsIdentifyingPrincipal(IEntityType dependentEntityType, IEnt
in StoreObjectIdentifier storeObject)
{
var value = property.GetDefaultValue(storeObject);
var converter = property.GetValueConverter() ?? property.FindRelationalTypeMapping(storeObject)?.Converter;
var converter = property.FindRelationalTypeMapping(storeObject)?.Converter;

return converter != null
? converter.ConvertToProvider(value)
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Metadata/IColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public virtual bool TryGetDefaultValue(out object? defaultValue)
continue;
}

var converter = property.GetValueConverter() ?? PropertyMappings.First().TypeMapping.Converter;
var converter = PropertyMappings.First().TypeMapping.Converter;

if (converter != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2455,7 +2455,7 @@ protected virtual IEnumerable<string> GetSchemas(IRelationalModel model)
: type.UnwrapNullableType().GetDefaultValue();

private static ValueConverter? GetValueConverter(IProperty property, RelationalTypeMapping? typeMapping = null)
=> property.GetValueConverter() ?? (property.FindRelationalTypeMapping() ?? typeMapping)?.Converter;
=> (property.FindRelationalTypeMapping() ?? typeMapping)?.Converter;

private static IEntityType GetMainType(ITable table)
=> table.EntityTypeMappings.First(t => t.IsSharedTablePrincipal).EntityType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,58 +40,48 @@ public RelationalValueGeneratorSelector(ValueGeneratorSelectorDependencies depen
{
}

/// <summary>
/// Creates a new value generator for the given property.
/// </summary>
/// <param name="property">The property to get the value generator for.</param>
/// <param name="entityType">
/// The entity type that the value generator will be used for. When called on inherited properties on derived entity types,
/// this entity type may be different from the declared entity type on <paramref name="property" />
/// </param>
/// <returns>The newly created value generator.</returns>
public override ValueGenerator Create(IProperty property, IEntityType entityType)
/// <inheritdoc />
protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType)
{
if (property.ValueGenerated != ValueGenerated.Never)
{
var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType();

if (propertyType.IsInteger()
|| propertyType == typeof(decimal)
|| propertyType == typeof(float)
|| propertyType == typeof(double))
if (clrType.IsInteger()
|| clrType == typeof(decimal)
|| clrType == typeof(float)
|| clrType == typeof(double))
{
return _numberFactory.Create(property, entityType);
}

if (propertyType == typeof(DateTime))
if (clrType == typeof(DateTime))
{
return new TemporaryDateTimeValueGenerator();
}

if (propertyType == typeof(DateTimeOffset))
if (clrType == typeof(DateTimeOffset))
{
return new TemporaryDateTimeOffsetValueGenerator();
}

if (property.GetDefaultValueSql() != null)
{
if (propertyType == typeof(Guid))
if (clrType == typeof(Guid))
{
return new TemporaryGuidValueGenerator();
}

if (propertyType == typeof(string))
if (clrType == typeof(string))
{
return new TemporaryStringValueGenerator();
}

if (propertyType == typeof(byte[]))
if (clrType == typeof(byte[]))
{
return new TemporaryBinaryValueGenerator();
}
}
}

return base.Create(property, entityType);
return base.FindForType(property, entityType, clrType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ public interface ISqlServerSequenceValueGeneratorFactory
/// 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.
/// </summary>
ValueGenerator Create(
ValueGenerator? TryCreate(
IProperty property,
Type clrType,
SqlServerSequenceValueGeneratorState generatorState,
ISqlServerConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@ public class SqlServerSequenceValueGeneratorFactory : ISqlServerSequenceValueGen
/// 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.
/// </summary>
public virtual ValueGenerator Create(
public virtual ValueGenerator? TryCreate(
IProperty property,
Type type,
SqlServerSequenceValueGeneratorState generatorState,
ISqlServerConnection connection,
IRawSqlCommandBuilder rawSqlCommandBuilder,
IRelationalCommandDiagnosticsLogger commandLogger)
{
var type = property.ClrType.UnwrapNullableType().UnwrapEnumType();

if (type == typeof(long))
{
return new SqlServerSequenceHiLoValueGenerator<long>(
Expand Down Expand Up @@ -103,8 +102,6 @@ public class SqlServerSequenceValueGeneratorFactory : ISqlServerSequenceValueGen
rawSqlCommandBuilder, _sqlGenerator, generatorState, connection, commandLogger);
}

throw new ArgumentException(
CoreStrings.InvalidValueGeneratorFactoryProperty(
nameof(SqlServerSequenceValueGeneratorFactory), property.Name, property.DeclaringEntityType.DisplayName()));
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,61 @@ public new virtual ISqlServerValueGeneratorCache Cache
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override ValueGenerator Select(IProperty property, IEntityType entityType)
=> property.GetValueGeneratorFactory() == null
&& property.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.SequenceHiLo
? _sequenceFactory.Create(
property,
Cache.GetOrAddSequenceState(property, _connection),
_connection,
_rawSqlCommandBuilder,
_commandLogger)
: base.Select(property, entityType);
{
if (property.GetValueGeneratorFactory() != null
|| property.GetValueGenerationStrategy() != SqlServerValueGenerationStrategy.SequenceHiLo)
{
return base.Select(property, entityType);
}

var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType();

var generator = _sequenceFactory.TryCreate(
property,
propertyType,
Cache.GetOrAddSequenceState(property, _connection),
_connection,
_rawSqlCommandBuilder,
_commandLogger);

if (generator != null)
{
return generator;
}

var converter = property.GetTypeMapping().Converter;
if (converter != null
&& converter.ProviderClrType != propertyType)
{
generator = _sequenceFactory.TryCreate(
property,
converter.ProviderClrType,
Cache.GetOrAddSequenceState(property, _connection),
_connection,
_rawSqlCommandBuilder,
_commandLogger);

if (generator != null)
{
return generator.WithConverter(converter);
}
}

throw new ArgumentException(
CoreStrings.InvalidValueGeneratorFactoryProperty(
nameof(SqlServerSequenceValueGeneratorFactory), property.Name, property.DeclaringEntityType.DisplayName()));
}

/// <summary>
/// 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.
/// </summary>
public override ValueGenerator Create(IProperty property, IEntityType entityType)
protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType)
=> property.ClrType.UnwrapNullableType() == typeof(Guid)
? property.ValueGenerated == ValueGenerated.Never || property.GetDefaultValueSql() != null
? new TemporaryGuidValueGenerator()
: new SequentialGuidValueGenerator()
: base.Create(property, entityType);
: base.FindForType(property, entityType, clrType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ public override IEnumerable<IAnnotation> For(IColumn column, bool designTime)
}

private static bool HasConverter(IProperty property)
=> (property.GetValueConverter() ?? property.FindTypeMapping()?.Converter) != null;
=> property.FindTypeMapping()?.Converter != null;
}
15 changes: 11 additions & 4 deletions src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,16 @@ private static PropertyAccessors CreateGeneric<TProperty>(IPropertyBase property
var storeGeneratedIndex = propertyBase.GetStoreGeneratedIndex();
if (storeGeneratedIndex >= 0)
{
var comparer = (propertyBase as IProperty)?.GetValueComparer()
?? ValueComparer.CreateDefault(propertyBase.ClrType, favorStructuralComparisons: true);

if (useStoreGeneratedValues)
{
currentValueExpression = Expression.Condition(
Expression.Equal(
currentValueExpression,
comparer.ExtractEqualsBody(
comparer.Type != currentValueExpression.Type
? Expression.Convert(currentValueExpression, comparer.Type)
: currentValueExpression,
Expression.Constant(default(TProperty), typeof(TProperty))),
Expression.Call(
entryParameter,
Expand All @@ -94,8 +99,10 @@ private static PropertyAccessors CreateGeneric<TProperty>(IPropertyBase property
}

currentValueExpression = Expression.Condition(
Expression.Equal(
currentValueExpression,
comparer.ExtractEqualsBody(
comparer.Type != currentValueExpression.Type
? Expression.Convert(currentValueExpression, comparer.Type)
: currentValueExpression,
Expression.Constant(default(TProperty), typeof(TProperty))),
Expression.Call(
entryParameter,
Expand Down
8 changes: 0 additions & 8 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1472,9 +1472,6 @@
<data name="ValueCannotBeNull" xml:space="preserve">
<value>The value for property '{1_entityType}.{0_property}' cannot be set to null because its type is '{propertyType}' which is not a nullable type.</value>
</data>
<data name="ValueGenWithConversion" xml:space="preserve">
<value>Value generation is not supported for property '{entityType}.{property}' because it has a '{converter}' converter configured. Configure the property to not use value generation using 'ValueGenerated.Never' or 'DatabaseGeneratedOption.None' and specify explicit values instead.</value>
</data>
<data name="VisitIsNotAllowed" xml:space="preserve">
<value>Calling '{visitMethodName}' is not allowed. Visit the expression manually for the relevant part in the visitor.</value>
</data>
Expand Down
66 changes: 66 additions & 0 deletions src/EFCore/ValueGeneration/Internal/ConvertedValueGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.ValueGeneration.Internal;

/// <summary>
/// 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.
/// </summary>
public class ConvertedValueGenerator : ValueGenerator
{
private readonly ValueGenerator _providerGenerator;
private readonly ValueConverter _converter;

/// <summary>
/// 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.
/// </summary>
public ConvertedValueGenerator(
ValueGenerator providerGenerator,
ValueConverter converter)
{
_providerGenerator = providerGenerator;
_converter = converter;
}

/// <summary>
/// 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.
/// </summary>
protected override object? NextValue(EntityEntry entry)
=> _converter.ConvertFromProvider(_providerGenerator.Next(entry));

/// <summary>
/// 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.
/// </summary>
public override async ValueTask<object?> NextAsync(EntityEntry entry, CancellationToken cancellationToken = default)
=> _converter.ConvertFromProvider(await _providerGenerator.NextAsync(entry, cancellationToken).ConfigureAwait(false));

/// <summary>
/// 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.
/// </summary>
public override bool GeneratesTemporaryValues
=> _providerGenerator.GeneratesTemporaryValues;

/// <summary>
/// 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.
/// </summary>
public override bool GeneratesStableValues
=> _providerGenerator.GeneratesStableValues;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class TemporaryNumberValueGeneratorFactory : ValueGeneratorFactory
/// <returns>The newly created value generator.</returns>
public override ValueGenerator Create(IProperty property, IEntityType entityType)
{
var type = property.ClrType.UnwrapNullableType().UnwrapEnumType();
var type = (property.GetValueConverter()?.ProviderClrType ?? property.GetTypeMapping().ClrType).UnwrapEnumType();

if (type == typeof(int))
{
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore/ValueGeneration/ValueGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.ValueGeneration.Internal;

namespace Microsoft.EntityFrameworkCore.ValueGeneration;

/// <summary>
Expand Down Expand Up @@ -91,4 +93,12 @@ public abstract class ValueGenerator
/// </remarks>
public virtual bool GeneratesStableValues
=> false;

/// <summary>
/// Wraps this <see cref="ValueGenerator" /> such that it processes values converted with the given <see cref="ValueConverter" />.
/// </summary>
/// <param name="converter"> The value converter. </param>
/// <returns> A new value generator that works with the converted values. </returns>
public virtual ValueGenerator WithConverter(ValueConverter converter)
=> new ConvertedValueGenerator(this, converter);
}
Loading

0 comments on commit 1baf270

Please sign in to comment.