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

Read double as float on SQL Server when precision is less than 25 #25881

Merged
merged 1 commit into from
Sep 8, 2021
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 @@ -4,6 +4,8 @@
using System;
using System.Data;
using System.Data.Common;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal
Expand All @@ -16,6 +18,9 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal
/// </summary>
public class SqlServerDoubleTypeMapping : DoubleTypeMapping
{
private static readonly MethodInfo _getFloatMethod
= typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetFloat), new[] { typeof(int) })!;

/// <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
Expand Down Expand Up @@ -73,6 +78,30 @@ protected override string GenerateNonNullSqlLiteral(object value)
: literal;
}

/// <summary>
/// The method to use when reading values of the given type. The method must be defined
/// on <see cref="DbDataReader" /> or one of its subclasses.
/// </summary>
/// <returns> The method to use to read the value. </returns>
public override MethodInfo GetDataReaderMethod()
=> Precision is <= 24 ? _getFloatMethod : base.GetDataReaderMethod();

/// <summary>
/// Gets a custom expression tree for reading the value from the input data reader
/// expression that contains the database value.
/// </summary>
/// <param name="expression"> The input expression, containing the database value. </param>
/// <returns> The expression with customization added. </returns>
public override Expression CustomizeDataReaderExpression(Expression expression)
{
if (Precision is <= 24)
{
expression = Expression.Convert(expression, typeof(double));
}

return base.CustomizeDataReaderExpression(expression);
}

/// <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,45 @@ private static MappedScaledSeparatelyDataTypes CreateMappedScaledSeparatelyDataT
DecimalAsNumeric3 = 103m
};

[ConditionalFact]
public virtual void Can_insert_and_read_back_double_types_with_precision()
{
using (var context = CreateContext())
{
context.Set<DoubleDataTypes>().Add(CreateDoubleDataTypes(77));

Assert.Equal(1, context.SaveChanges());
}

var parameters = DumpParameters();
Assert.Equal(
@"@p0='77'
@p1='83.33000183105469' (Size = 25)
@p2='83.30000305175781' (Size = 3)",
parameters,
ignoreLineEndingDifferences: true);

using (var context = CreateContext())
{
AssertDoubleDataTypes(context.Set<DoubleDataTypes>().Single(e => e.Id == 77), 77);
}
}

private static void AssertDoubleDataTypes(DoubleDataTypes entity, int id)
{
Assert.Equal(id, entity.Id);
Assert.Equal(83.3f, entity.Double3);
Assert.Equal(83.33f, entity.Double25);
}

private static DoubleDataTypes CreateDoubleDataTypes(int id)
=> new()
{
Id = id,
Double3 = 83.3f,
Double25 = 83.33f
};

[ConditionalFact]
public virtual void Can_insert_and_read_back_all_mapped_data_types_with_precision_and_scale()
{
Expand Down Expand Up @@ -2965,6 +3004,9 @@ public virtual void Columns_have_expected_data_types()
BuiltInNullableDataTypesShadow.TestString ---> [nullable nvarchar] [MaxLength = -1]
DateTimeEnclosure.DateTimeOffset ---> [nullable datetimeoffset] [Precision = 7]
DateTimeEnclosure.Id ---> [int] [Precision = 10 Scale = 0]
DoubleDataTypes.Double25 ---> [float] [Precision = 53]
DoubleDataTypes.Double3 ---> [real] [Precision = 24]
DoubleDataTypes.Id ---> [int] [Precision = 10 Scale = 0]
EmailTemplate.Id ---> [uniqueidentifier]
EmailTemplate.TemplateType ---> [int] [Precision = 10 Scale = 0]
MappedDataTypes.BoolAsBit ---> [bit]
Expand Down Expand Up @@ -3599,6 +3641,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
b.Property(e => e.DecimalAsNumeric3).HasPrecision(3);
});

modelBuilder.Entity<DoubleDataTypes>(
b =>
{
b.Property(e => e.Id).ValueGeneratedNever();
b.Property(e => e.Double3).HasPrecision(3);
b.Property(e => e.Double25).HasPrecision(25);
});

modelBuilder.Entity<MappedPrecisionAndScaledSeparatelyDataTypes>(
b =>
{
Expand Down Expand Up @@ -4118,6 +4168,14 @@ protected class MappedScaledSeparatelyDataTypes
public decimal DecimalAsNumeric3 { get; set; }
}

protected class DoubleDataTypes
{
public int Id { get; set; }

public double Double3 { get; set; }
public double Double25 { get; set; }
}

protected class MappedPrecisionAndScaledDataTypes
{
public int Id { get; set; }
Expand Down