Как преобразовать строку даты и времени в дату и время на SQL сервере - PullRequest
1 голос
/ 20 марта 2020

Из моего приложения C# я вызываю хранимую процедуру с TVP. Пара столбцов datetime. Вызов SP может выглядеть следующим образом:

declare @p1 dbo.MyTvp
insert into @p1 values('2020-03-19 00:00:01','2020-03-30 23:59:59')
exec MySp @criteria=@p1

Код выше автоматически генерируется в C#. В SP часть обработки дат:

declare @datefrom datetime;
---
SET @sql = CONCAT(@sql, ' AND date_from >= ''', @datefrom, '''');

SQL Язык интерфейса сервера - немецкий.

Приведенное выше сообщение вызывает ошибку из-за преобразования из varchar в datetime. Однако, если значения даты и времени, которые я передаю, отформатированы следующим образом:

declare @p1 dbo.MyTvp
insert into @p1 values('19.03.2020 00:00:01','30.03.2020 23:59:59')
exec MySp @criteria=@p1

SP работает нормально.

Класс, используемый в качестве источника:

public class MyCriteria
{
    public DateTime DateFrom { get; set; }
}

И тип таблицы:

CREATE TYPE [dbo].[MyTvp] AS TABLE(
    [DateFrom] [datetime] NULL
)

Я преобразую экземпляр MyCriteria в DataTable, используя метод расширения, а затем использую Dapper для выполнения SP:

var criteria = new List<MyCriteria>() { myCriteria }.ToDataTable();
return await conn.QueryAsync<SomeResult>(new CommandDefinition("MySp", new { criteria }, commandType: CommandType.StoredProcedure, cancellationToken: ct));

Я не понимаю, на каком этапе происходит преобразование из datetime в varchar или DateTime в string.

Так как именно мне нужно преобразовать даты в заставить SP работать? Должен ли я выполнять преобразование на уровне БД или в моем приложении C#?

РЕДАКТИРОВАТЬ

Это метод расширения, используемый для преобразования класса в таблицу данных, поэтому что он может быть передан в качестве TVP на SP:

    public static DataTable ToDataTable<T>(this IEnumerable<T> items)
    {
        var dataTable = new DataTable(typeof(T).Name);

        //Get all the properties not marked with Ignore attribute
        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                  .Where(x => x.GetCustomAttributes(typeof(XmlIgnoreAttribute), false).Length == 0).ToList();

        //Set column names as property names
        foreach (var property in properties)
        {
            if (!property.PropertyType.IsEnum && !property.PropertyType.IsNullableEnum())
            {
                var type = property.PropertyType;

                //Check if type is Nullable like int?
                if (Nullable.GetUnderlyingType(type) != null) 
                    type = Nullable.GetUnderlyingType(type);

                dataTable.Columns.Add(property.Name, type);
            }
            else dataTable.Columns.Add(property.Name, typeof(int));
        }

        //Insert property values to datatable rows
        foreach (T item in items)
        {
            var values = new object[properties.Count];
            for (int i = 0; i < properties.Count; i++)
            {
                values[i] = properties[i].GetValue(item, null);
            }

            dataTable.Rows.Add(values);
        }

        return dataTable;
    }

EDIT 2

Проблема в SQL, который генерируется C # / Dapper, который используется для заполнения TVP, переданного SP. Простой тест можно увидеть, выполнив следующее:

DECLARE @test TABLE (
    [DateCol] datetime NOT NULL
);

INSERT INTO @test VALUES ('2020-02-19 00:00:01'); --doesnt work
INSERT INTO @test VALUES (CONVERT(datetime, '2020-02-19 00:00:01', 120)); --works

Функция CONVERT возвращает дату в том же формате, что и первый оператор INSERT. Однако первое утверждение не работает.

1 Ответ

1 голос
/ 20 марта 2020

Из обсуждения в комментариях это звучит так: a: данные в TVP набраны (datetime) и b: в этом случае есть только одна строка; это здорово - это значит, что мы можем значительно упростить; то, что мы хотели бы сделать здесь, это перенести значения из TVP в локальные ресурсы и просто работать с ними. Теперь, основываясь на @datefrom в примере кода, похоже, что вы уже сделали первый шаг, поэтому все, что нам нужно сделать, это исправить, как составляется и исполняется Dynami c SQL. В вопросе у нас есть:

SET @sql = CONCAT(@sql, ' AND date_from >= ''', @datefrom, '''');

, за которым, вероятно, последуют:

EXEC (@sql);

Вместо этого мы можем параметризовать динамику c SQL:

SET @sql = @sql + ' AND date_from >= @datefrom ';

и передать параметры в нашу динамику c SQL:

exec sp_executesql @sql, N'@datefrom datetime', @datefrom

Второй параметр sp_executesql дает строку определения для всех фактические параметры, которые следуют после него последовательно.

Теперь код полностью защищен от SQL внедрения, и вам не нужно беспокоиться о преобразованиях строк / дат.

Обратите внимание, что имена параметров не обязательно должны быть одинаковыми во внутренней и внешней частях, но они часто (для удобства и удобства обслуживания).

...