Код Entity Framework Первое усечение моих десятичных знаков

Я использую Entity Framework 6.x, используя подход Code First в приложении MVC 5. В этой конкретной ситуации моя модель (среди прочего) содержит два свойства с именами Широта и Долгота:

[Required, Range(-90, +90)]
public decimal Latitude { get; set; }

[Required, Range(-180, +180)]
public decimal Longitude { get; set; }

И когда я выполнил миграцию, у меня получилось что-то вроде этого

CreateTable("ResProperty"), c => new {
        :
    Latitude = c.Decimal(nullable: false, precision: 10, scale: 8),
    Longitude = c.Decimal(nullable: false, precision: 11, scale: 8),
        :
})
... other stuff

поэтому широта и долгота состоят из 8 десятичных цифр. Первый - с 2 целыми числами (максимум 90), а второй - с 3 целыми числами (максимум 180).

После выполнения команды Update-Database столбцы моей таблицы отображаются как:

Latitude decimal(10,8)
Longitude decimal(11,8)

мне это кажется хорошим. Теперь, на мой взгляд, у меня есть карта и код Javascript, который позволяет пользователю перемещать маркер. Это тоже отлично работает. Когда маркер перемещается, поля Широта и Долгота заполняются обновленным значением, которое (Javascript) содержит более 12 десятичных цифр. Это не имеет значения, AFAIK, потому что мой масштаб - 8 десятичных знаков.

После нажатия кнопки отправки и вызова метода Create или Edit POST я проверяю экземпляр модели и подтверждаю, что фактические значения, переданные в модель контроллеру, верны, у них более чем достаточно десятичных цифр (тех, которые в коде Javascript место). Значит, значение правильное.

Теперь ... проблема в том, что после выполнения db.SaveChanges () база данных обновляется - и я подтвердил, что произошла фактическая запись / обновление, - но каким-то образом внутри EF игнорирует мои фактические значения и записывает усеченную широту / долготу округлено ТОЛЬКО до ДВУХ десятичных цифр, поэтому моя Широта отображается в БД как 09.500000000, все остальные десятичные цифры обнуляются, потому что, похоже, произошло округление.

// Prior to SaveChanges()
Latitude = 9.08521879
Longitude = -79.51658792
// After SaveChanges()
Latitude = 9.08000000
Longitude = -79.51000000

Почему округляется, если я указал правильный масштаб и точность, а столбец также имеет правильный масштаб и точность? почему SaveChanges изменяет мои значения?

Я нашел это сообщение (http://weiding331.blogspot.com/2014/01/entity-framework-decimal-value.html), что является той же проблемой, но я не знаю, как я могу это исправить (если это так), потому что я уже выполнил несколько миграций и добавлений данных после рассматриваемая таблица была "перенесена".

Подводя итоги

  • Тип данных модели правильный (десятичный)
  • Код миграции базы данных имеет правильную точность / масштаб (шир. 10/8, долгота 11/8).
  • Столбцы базы данных SQL имеют правильную точность / масштаб (широта 10/8, длинная 11/8).
  • Значения, переданные в модель, содержат не менее 8 десятичных цифр как для широты, так и для долготы.
  • фактическая запись / обновление значения происходит в базе данных без ошибок, но ...
  • Значения, записанные в базе данных для этих двух столбцов, усекаются до ДВУХ десятичных цифр, а остальные наименее значимые десятичные цифры отображаются как ноль (0).

person Lord of Scripts    schedule 15.10.2014    source источник
comment
Вы указали масштаб и точность в своей модели? Взгляните на этот вопрос: stackoverflow.com/questions/3504660/? С другой стороны, EF поддерживает типы пространственных данных ...   -  person Pawel    schedule 16.10.2014
comment
Я не думаю, что смогу больше использовать OnModelCreating, потому что эта конкретная миграция была на несколько уровней ниже в моем списке миграции. Я добавил его после смерти и запустил Update-Database (без новой миграции), но не увидел, что это решает проблему. Кроме того, столбцы SQL в БД показали правильную точность, но EF усекал ее внутри во время SaveChanges ().   -  person Lord of Scripts    schedule 17.10.2014


Ответы (2)


EF имеет специальное свойство для SqlProviderServices (реализация для поставщика SqlClient для SQL Server) - TruncateDecimalsToScale. Значение по умолчанию - истина, поэтому, возможно, вы можете изменить его на ложное значение. Например:

public class DbContextConfiguration : DbConfiguration
    {
        public DbContextConfiguration()
        {
            var now = SqlProviderServices.Instance;
            SqlProviderServices.TruncateDecimalsToScale = false;
            this.SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
        }
    }

    [DbConfigurationType(typeof(DbContextConfiguration))]
    public class MyContext : DbContext
    { ... }

Подробнее об этом: https://msdn.microsoft.com/en-us/library/system.data.entity.sqlserver.sqlproviderservices.truncatedecimalstoscale%28v=vs.113%29.aspx

person Adrian Tarnowski    schedule 23.08.2016
comment
Это похоже на то, что я ищу, но не могли бы вы объяснить, как здесь устанавливается значение? Теперь есть переменная, которая, похоже, не используется, и свойство, похоже, установлено в статическом методе. Это лучший способ установить это значение? Я удивлен, что для такой важной функции нет дополнительной документации по ней - усечение десятичных знаков, безусловно, является серьезной проблемой. Спасибо. - person Mark007; 08.10.2016
comment
@ Mark007 это глобальная конфигурация. Конечно, это не лучшее место для установки этих вещей, но вы знаете, что это EF;) - person Adrian Tarnowski; 21.03.2017
comment
Это сработало и решило проблему, но я думаю, что мне нужно что-то ILSpy и точно посмотреть, что происходит в какой-то момент. - person Mark007; 23.03.2017
comment
Считается ли это ошибкой? Я не понимаю, почему это не так ... в конце концов, масштабирование правильное, но оно все еще некорректно обрезается? - person Lopsided; 17.11.2017

Для хранения пространственных данных я бы порекомендовал DbGeography класс, созданный для этого типа данных.

https://docs.microsoft.com/en-us/dotnet/api/system.data.entity.spatial.dbgeography?view=entity-framework-6.2.0


Как уже упоминалось, проблема усечения может быть решена с помощью SqlProviderServices.TruncateDecimalsToScale = false;, как указал @AdrianTarnowski. Однако я хотел бы показать, почему это происходит и почему Entity Framework 6.X обрезает десятичные значения вместо округления по умолчанию.

Для проверки я использую такую ​​базовую программу:

class Program
{
    static void Main(string[] args)
    {
        var dbContext = new ApplicationDbContext();
        dbContext.TestValues.Add(new TestValue()
        {
            Value = 0.0005m
        });
        dbContext.TestValues.Add(new TestValue()
        {
            Value = 0.0001m
        });
        dbContext.TestValues.Add(new TestValue()
        {
            Value = 0.0007m
        });
        dbContext.SaveChanges();
    }
}

public class TestValue
{
    public int Id { get; set; }

    public decimal Value { get; set; }
}

public class DbContextConfiguration : DbConfiguration
{
    public DbContextConfiguration()
    {
        var providerInstance = SqlProviderServices.Instance;
        SqlProviderServices.TruncateDecimalsToScale = true;
        this.SetProviderServices(SqlProviderServices.ProviderInvariantName, SqlProviderServices.Instance);
    }
}

[DbConfigurationType(typeof(DbContextConfiguration))]
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext() : base("ApplicationContext")
    {
        Database.Log = s => Debug.WriteLine(s);
    }

    public DbSet<TestValue> TestValues { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TestValue>().Property(x => x.Value).HasPrecision(18, 3);

        base.OnModelCreating(modelBuilder);
    }
}

По умолчанию это выглядит так: SqlProviderServices.TruncateDecimalsToScale = true;. Это сделано для предотвращения выхода из строя существующих приложений, зависящих от этого поведения.

https://docs.microsoft.com/en-us/dotnet/api/system.data.entity.sqlserver.sqlproviderservices.truncatedecimalstoscale?redirectedfrom=MSDN&view=entity-framework-6.2.0#overloads < / а>

Когда TruncateDecimalsToScale является нормальным (TruncateDecimalsToScale = true;), вставка из структуры сущностей выглядит так в Database.Log из DbContext:

INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()


-- @0: '0,0005' (Type = Decimal, Precision = 18, Scale = 3)

Однако, глядя на SQL Server Profiler, фактические отправляемые данные - это 0 для каждого значения сверху.

exec sp_executesql N'INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()',N'@0 decimal(18,3)',@0=0

Вместо этого переход на SqlProviderServices.TruncateDecimalsToScale = false; Database.Log с DbContext выглядит так:

INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()


-- @0: '0,0005' (Type = Decimal)

Теперь SQL Server Profiler выглядит лучше и имеет правильные значения:

exec sp_executesql N'INSERT [dbo].[TestValues]([Value])
VALUES (@0)
SELECT [Id]
FROM [dbo].[TestValues]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()',N'@0 decimal(4,4)',@0=5

Обратите внимание, что на EntityFrameworkCore это не влияет. Здесь по умолчанию используется округление.

POC:

class Program
{
    static void Main(string[] args)
    {
        using (var dbContext = new ApplicationDbContext())
        {
            dbContext.TestValues.Add(new TestValue()
            {
                Value = 0.0005m
            });
            dbContext.TestValues.Add(new TestValue()
            {
                Value = 0.0001m
            });
            dbContext.TestValues.Add(new TestValue()
            {
                Value = 0.0007m
            });
            dbContext.SaveChanges();
        }
    }
}

public class TestValue
{
    public int Id { get; set; }

    public decimal Value { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<TestValue> TestValues { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer("data source=localhost;initial catalog=;persist security info=True;User Id=;Password=;", providerOptions => providerOptions.CommandTimeout(60));
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TestValue>().Property(x => x.Value).HasColumnType("decimal(18, 3)");

        base.OnModelCreating(modelBuilder);
    }
}
person Ogglas    schedule 18.07.2019