Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support value generation for converted types #27759

Merged
merged 2 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1034,7 +1034,7 @@ protected virtual void ValidateCompatible(
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() ?? mapping.TypeMapping.Converter;
var converter = mapping.TypeMapping.Converter;
if (converter != null)
{
defaultValue = converter.ConvertToProvider(defaultValue);
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 SqlServerSequenceValueGeneratorFactory(
/// 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 virtual ValueGenerator Create(
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 SqlServerValueGeneratorSelector(
/// 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 Func<IUpdateEntry, TProperty> CreateCurrentValueGetter<TProperty>
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 Func<IUpdateEntry, TProperty> CreateCurrentValueGetter<TProperty>
}

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