Исключение переполнения SqlDataAdapter.Fill()

Мне нужно получить данные из БД и выполнить следующий код:

using (var dataRetrievingCommand = new SqlCommand(selectQuery, connection))
{
    var dataTable = new DataTable("DataTable");
    var sda = new SqlDataAdapter(dataRetrievingCommand);
    sda.Fill(dataTable); //OverflowException here
    return dataTable;
}

Проблема в том, что некоторые столбцы в базе данных имеют тип decimal(38,10), decimal(38, 0) и т. д. Диапазон их значений больше, чем тип decimal в C#, поэтому я получаю OverflowException.

Есть ли способ выбрать эти данные в C# без изменения SQL-запроса и структуры БД?

Если точность значения БД выше, чем у C#, можно округлить значение. если происходит переполнение, я бы хотел вместо этого увидеть NULL или любую другую отметку, что значение не может быть прочитано.

ДЕТАЛИ РАССЛЕДОВАНИЯ:

Вопрос звучит как популярный, но я нашел только один Тема на форуме Microsoft с такой же проблемой и без ответов.

Сама студия SQL Server Management 2008 не может отображать такие значения в режиме редактирования данных - она ​​показывает "" и не позволяет редактировать эти ячейки. введите здесь описание изображенияНо команды SELECT, INSERT и UPDATE работают нормально и отображают все данные.

Десятичные столбцы с точностью 28 или меньше работают нормально, но любой столбец с более высокой точностью дает сбой, даже если значения не вызывают переполнения, как decimal(38, 38), которые можно просто обрезать, как nvarchar обрезается при вставке.

sda.ContinueUpdateOnError = true;

ничего не меняет. Я думаю, что это просто не операция обновления, поэтому это свойство игнорируется.

sda.FillError += (sender, args) =>
{
    args.Continue = true;
};

останавливает генерацию исключений, но данные вообще не возвращаются - только одна бессмысленная строка

 CaseName | Decimal_10_2 | Decimal_18_0 | Decimal_18_18 | Decimal_28_0 | Decimal_28_28 | Decimal_38_0 | Decimal_38_20 | Decimal_38_38 | Float | Int
----------+--------------+--------------+---------------+--------------+---------------+--------------+---------------+---------------+-------+----
 Null     |              |              |               |              |               |              |               |               |       |    

person Sasha    schedule 25.04.2014    source источник


Ответы (1)


Кажется, я нашел решение самостоятельно.

sda.ReturnProviderSpecificTypes = true;

После этого ячейки DataTable содержат значения таких типов, как System.Data.SqlTypes.SqlDecimal, System.Data.SqlTypes.SqlString и т. д., которые можно в дальнейшем преобразовать в собственные типы данных .NET с настраиваемой обработкой ошибок.

Затем я проверяю свойства Precision и Scale значения SqlDecimal и при необходимости использую SqlDecimal.AdjustScale() для округления значения. Доступ к результату можно получить через свойство Value. Смотрите мою реализацию:

public struct DecimalEx
{
    private decimal m_Value;

    public decimal Value
    {
        get { return m_Value; }
    }

    private bool m_IsNull;

    public bool IsNull
    {
        get { return m_IsNull; }
    }

    private bool m_IsOverflow;

    public bool IsOverflow
    {
        get { return m_IsOverflow; }
    }

    private bool m_IsRounded;

    public bool IsRounded
    {
        get { return m_IsRounded; }
    }

    private bool m_IsPositive;

    public bool IsPositive
    {
        get { return m_IsPositive; }
    }

    public DecimalEx(decimal value)
    {
        m_Value = value;
        m_IsNull = false;
        m_IsOverflow = false;
        m_IsRounded = false;
        m_IsPositive = value >= 0;
    }

    public static explicit operator DecimalEx(SqlDecimal dbValue)
    {
        var result = new DecimalEx();
        if (dbValue.IsNull)
        {
            result.m_Value = 0;
            result.m_IsNull = true;
            result.m_IsOverflow = false;
            result.m_IsRounded = false;
            result.m_IsPositive = false;
            return result;
        }
        else
        {
            result.m_IsNull = false;
            result.m_IsPositive = dbValue.IsPositive;
        }

        if (dbValue.Precision > 28)
        {
            result.m_IsRounded = true;

            if (dbValue.Precision - dbValue.Scale <= 28)
            {
                var adjustedValue = SqlDecimal.AdjustScale(dbValue, 28 - dbValue.Precision, true);
                result.m_Value = adjustedValue.Value;
                result.m_IsOverflow = false;
            }
            else
            {
                result.m_Value = 0;
                result.m_IsOverflow = true;
            }
        }
        else
        {
            result.m_Value = dbValue.Value;
            result.m_IsRounded = false;
        }

        return result;
    }

    public override string ToString()
    {
        return ToString(CultureInfo.CurrentCulture);
    }

    public string ToString(IFormatProvider provider)
    {
        if (IsNull)
        {
            return string.Empty;
        }
        if (IsOverflow)
        {
            return "###";
        }
        return Value.ToString(provider);
    }
}

ОБНОВЛЕНИЕ: будьте осторожны с методом AdjustScale, потому что его документация неточна — параметр digits — это не количество цифр в результирующем значении, а изменение количества цифр (поэтому для уменьшения точности на два вы должны пройти digits=-2)

person Sasha    schedule 25.04.2014