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

Fix relational mapping hints and add DbType #29967

Merged
merged 1 commit into from
Jan 4, 2023
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
2 changes: 1 addition & 1 deletion src/EFCore.Relational/Storage/RelationalTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public RelationalTypeMappingParameters WithTypeMappingInfo(in RelationalTypeMapp
CoreParameters,
mappingInfo.StoreTypeName ?? StoreType,
StoreTypePostfix,
DbType,
mappingInfo.DbType ?? DbType,
mappingInfo.IsUnicode ?? Unicode,
mappingInfo.Size ?? Size,
mappingInfo.IsFixedLength ?? FixedLength,
Expand Down
42 changes: 38 additions & 4 deletions src/EFCore.Relational/Storage/RelationalTypeMappingInfo.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 System.Data;

namespace Microsoft.EntityFrameworkCore.Storage;

/// <summary>
Expand Down Expand Up @@ -33,7 +35,7 @@ public RelationalTypeMappingInfo(IProperty property)
/// <param name="fallbackUnicode">
/// Specifies Unicode or ANSI for the mapping or <see langword="null" /> for default.
/// </param>
/// <param name="fixedLength">Specifies a fixed length mapping, or <see langword="null" /> for default.</param>
/// <param name="fallbackFixedLength">Specifies a fixed length mapping, or <see langword="null" /> for default.</param>
/// <param name="fallbackSize">
/// Specifies a size for the mapping, in case one isn't found at the core level, or <see langword="null" /> for default.
/// </param>
Expand All @@ -48,17 +50,43 @@ public RelationalTypeMappingInfo(
string? storeTypeName = null,
string? storeTypeNameBase = null,
bool? fallbackUnicode = null,
bool? fixedLength = null,
bool? fallbackFixedLength = null,
int? fallbackSize = null,
int? fallbackPrecision = null,
int? fallbackScale = null)
{
_coreTypeMappingInfo = new TypeMappingInfo(principals, fallbackUnicode, fallbackSize, fallbackPrecision, fallbackScale);

IsFixedLength = fixedLength;
ValueConverter? customConverter = null;
for (var i = 0; i < principals.Count; i++)
{
var principal = principals[i];
if (customConverter == null)
{
var converter = principal.GetValueConverter();
if (converter != null)
{
customConverter = converter;
}
}

if (fallbackFixedLength == null)
{
var fixedLength = principal.IsFixedLength();
if (fixedLength != null)
{
fallbackFixedLength = fixedLength;
}
}
}

var mappingHints = customConverter?.MappingHints;

IsFixedLength = fallbackFixedLength ?? (mappingHints as RelationalConverterMappingHints)?.IsFixedLength;
DbType = (mappingHints as RelationalConverterMappingHints)?.DbType;
StoreTypeName = storeTypeName;
StoreTypeNameBase = storeTypeNameBase;
}
}

/// <summary>
/// Creates a new instance of <see cref="RelationalTypeMappingInfo" />.
Expand Down Expand Up @@ -132,6 +160,7 @@ public RelationalTypeMappingInfo(
StoreTypeName = source.StoreTypeName;
StoreTypeNameBase = source.StoreTypeNameBase;
IsFixedLength = source.IsFixedLength ?? (mappingHints as RelationalConverterMappingHints)?.IsFixedLength;
DbType = source.DbType ?? (mappingHints as RelationalConverterMappingHints)?.DbType;
}

/// <summary>
Expand Down Expand Up @@ -208,6 +237,11 @@ public int? Scale
/// </summary>
public bool? IsFixedLength { get; init; }

/// <summary>
/// The <see cref="DbType"/> of the mapping.
/// </summary>
public DbType? DbType { get; init; }

/// <summary>
/// Indicates whether or not the mapping is part of a key or index.
/// </summary>
Expand Down
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 System.Data;

namespace Microsoft.EntityFrameworkCore.Storage.ValueConversion;

/// <summary>
Expand All @@ -21,24 +23,45 @@ public class RelationalConverterMappingHints : ConverterMappingHints
/// <param name="unicode">Whether or not the mapped data type should support Unicode.</param>
/// <param name="fixedLength">Whether or not the mapped data type is fixed length.</param>
/// <param name="valueGeneratorFactory">An optional factory for creating a specific <see cref="ValueGenerator" />.</param>
/// <param name="dbType">The suggested <see cref="DbType"/>.</param>
public RelationalConverterMappingHints(
int? size = null,
int? precision = null,
int? scale = null,
bool? unicode = null,
bool? fixedLength = null,
Func<IProperty, IEntityType, ValueGenerator>? valueGeneratorFactory = null)
Func<IProperty, IEntityType, ValueGenerator>? valueGeneratorFactory = null,
DbType? dbType = null)
: base(size, precision, scale, unicode, valueGeneratorFactory)
{
IsFixedLength = fixedLength;
DbType = dbType;
}

/// <summary>
/// Adds hints from the given object to this one. Hints that are already specified are
/// not overridden.
/// Creates a new <see cref="ConverterMappingHints" /> instance. Any hint contained in the instance
/// can be <see langword="null" /> to indicate it has not been specified.
/// </summary>
/// <param name="hints">The hints to add.</param>
/// <returns>The combined hints.</returns>
/// <param name="size">The suggested size of the mapped data type.</param>
/// <param name="precision">The suggested precision of the mapped data type.</param>
/// <param name="scale">The suggested scale of the mapped data type.</param>
/// <param name="unicode">Whether or not the mapped data type should support Unicode.</param>
/// <param name="fixedLength">Whether or not the mapped data type is fixed length.</param>
/// <param name="valueGeneratorFactory">An optional factory for creating a specific <see cref="ValueGenerator" />.</param>
[Obsolete("Use the overload with more parameters.")]
public RelationalConverterMappingHints(
int? size,
int? precision,
int? scale,
bool? unicode,
bool? fixedLength,
Func<IProperty, IEntityType, ValueGenerator>? valueGeneratorFactory)
: base(size, precision, scale, unicode, valueGeneratorFactory)
{
IsFixedLength = fixedLength;
}

/// <inheritdoc />
public override ConverterMappingHints With(ConverterMappingHints? hints)
=> hints == null
? this
Expand All @@ -48,10 +71,29 @@ public override ConverterMappingHints With(ConverterMappingHints? hints)
hints.Scale ?? Scale,
hints.IsUnicode ?? IsUnicode,
(hints as RelationalConverterMappingHints)?.IsFixedLength ?? IsFixedLength,
hints.ValueGeneratorFactory ?? ValueGeneratorFactory);
hints.ValueGeneratorFactory ?? ValueGeneratorFactory,
(hints as RelationalConverterMappingHints)?.DbType ?? DbType);

/// <inheritdoc />
public override ConverterMappingHints Override(ConverterMappingHints? hints)
=> hints == null
? this
: new RelationalConverterMappingHints(
Size ?? hints.Size,
Precision ?? hints.Precision,
Scale ?? hints.Scale,
IsUnicode ?? hints.IsUnicode,
IsFixedLength ?? (hints as RelationalConverterMappingHints)?.IsFixedLength,
ValueGeneratorFactory ?? hints.ValueGeneratorFactory,
DbType ?? (hints as RelationalConverterMappingHints)?.DbType);

/// <summary>
/// Whether or not the mapped data type is fixed length.
/// </summary>
public virtual bool? IsFixedLength { get; }

/// <summary>
/// The suggested <see cref="DbType"/>
/// </summary>
public virtual DbType? DbType { get; }
}
37 changes: 29 additions & 8 deletions src/EFCore/Storage/ValueConversion/ConverterMappingHints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ public ConverterMappingHints(
}

/// <summary>
/// Adds hints from the given object to this one. Hints that are already specified are
/// not overridden.
/// Adds hints from the given object to this one. Hints that are already specified are not overridden.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-value-converters">EF Core value converters</see> for more information and examples.
Expand All @@ -49,12 +48,34 @@ public ConverterMappingHints(
public virtual ConverterMappingHints With(ConverterMappingHints? hints)
=> hints == null
? this
: new ConverterMappingHints(
hints.Size ?? Size,
hints.Precision ?? Precision,
hints.Scale ?? Scale,
hints.IsUnicode ?? IsUnicode,
hints.ValueGeneratorFactory ?? ValueGeneratorFactory);
: hints.GetType().IsAssignableFrom(GetType())
? new ConverterMappingHints(
hints.Size ?? Size,
hints.Precision ?? Precision,
hints.Scale ?? Scale,
hints.IsUnicode ?? IsUnicode,
hints.ValueGeneratorFactory ?? ValueGeneratorFactory)
: hints.Override(this);

/// <summary>
/// Adds hints from the given object to this one. Hints that are already specified are overridden.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-value-converters">EF Core value converters</see> for more information and examples.
/// </remarks>
/// <param name="hints">The hints to add.</param>
/// <returns>The combined hints.</returns>
public virtual ConverterMappingHints Override(ConverterMappingHints? hints)
=> hints == null
? this
: GetType().IsAssignableFrom(hints.GetType())
? new ConverterMappingHints(
Size ?? hints.Size,
Precision ?? hints.Precision,
Scale ?? hints.Scale,
IsUnicode ?? hints.IsUnicode,
ValueGeneratorFactory ?? hints.ValueGeneratorFactory)
: hints.With(this);

/// <summary>
/// The suggested size of the mapped data type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#nullable enable

using System.Data;

namespace Microsoft.EntityFrameworkCore;

public class ValueConvertersEndToEndSqlServerTest
Expand Down Expand Up @@ -194,6 +196,78 @@ public WrappedStringToStringConverter()
}
}

[ConditionalFact]
public virtual void Fixed_length_hints_are_respected()
{
Fixture.TestSqlLoggerFactory.Clear();

using var context = CreateContext();

var guid = new Guid("d854227f-7076-48c3-997c-4e72c1c713b9");

var mapping = context.Set<SqlServerConvertingEntity>()
.EntityType
.FindProperty(nameof(SqlServerConvertingEntity.GuidToFixedLengthString))!
.FindRelationalTypeMapping()!;

Assert.Equal("nchar(40)", mapping.StoreType);
Assert.Equal(40, mapping.Size);

Assert.Empty(context.Set<SqlServerConvertingEntity>().Where(e => e.GuidToFixedLengthString != guid));

Assert.Equal(
"""
@__guid_0='d854227f-7076-48c3-997c-4e72c1c713b9' (Nullable = false) (Size = 40)

SELECT [s].[Id], [s].[GuidToDbTypeString], [s].[GuidToFixedLengthString]
FROM [SqlServerConvertingEntity] AS [s]
WHERE [s].[GuidToFixedLengthString] <> @__guid_0
""",
Fixture.TestSqlLoggerFactory.SqlStatements[0],
ignoreLineEndingDifferences: true);

var parameter = Fixture.TestSqlLoggerFactory.Parameters.Single();
}

[ConditionalFact]
public virtual void DbType_hints_are_respected()
{
Fixture.TestSqlLoggerFactory.Clear();

using var context = CreateContext();

var mapping = context.Set<SqlServerConvertingEntity>()
.EntityType
.FindProperty(nameof(SqlServerConvertingEntity.GuidToDbTypeString))!
.FindRelationalTypeMapping()!;

Assert.Equal(DbType.AnsiStringFixedLength, mapping.DbType!);
Assert.Equal(40, mapping.Size);

var guid = new Guid("d854227f-7076-48c3-997c-4e72c1c713b9");

Assert.Empty(context.Set<SqlServerConvertingEntity>().Where(e => e.GuidToDbTypeString != guid));

Assert.Equal(
"""
@__guid_0='d854227f-7076-48c3-997c-4e72c1c713b9' (Nullable = false) (Size = 40) (DbType = AnsiStringFixedLength)

SELECT [s].[Id], [s].[GuidToDbTypeString], [s].[GuidToFixedLengthString]
FROM [SqlServerConvertingEntity] AS [s]
WHERE [s].[GuidToDbTypeString] <> @__guid_0
""",
Fixture.TestSqlLoggerFactory.SqlStatements[0],
ignoreLineEndingDifferences: true);
}

protected class SqlServerConvertingEntity
{
public Guid Id { get; set; }

public Guid GuidToFixedLengthString { get; set; }
public Guid GuidToDbTypeString { get; set; }
}

public class ValueConvertersEndToEndSqlServerFixture : ValueConvertersEndToEndFixtureBase
{
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
Expand Down Expand Up @@ -221,6 +295,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(e => e.NullableEnumerableOfInt).HasDefaultValue(Enumerable.Empty<int>());
b.Property(e => e.EnumerableOfInt).HasDefaultValue(Enumerable.Empty<int>());
});

modelBuilder.Entity<SqlServerConvertingEntity>(
b =>
{
b.Property(e => e.GuidToFixedLengthString).HasConversion(
new GuidToStringConverter(
new RelationalConverterMappingHints(
size: 40, fixedLength: true)));

b.Property(e => e.GuidToDbTypeString).HasConversion(
new GuidToStringConverter(
new RelationalConverterMappingHints(
size: 40, unicode: false, dbType: DbType.AnsiStringFixedLength)));
});
}
}
}
Expand Down