Как параметризовать 30.12.1899 в SQL Собственный клиент сервера, когда DataTypeCompatility включен? - PullRequest
7 голосов
/ 15 марта 2020

Короткая версия

Попытка передать значение datetime 12/30/1899 на SQL Сервер, происходит сбой с Недопустимый формат даты - но только для собственных драйверов клиента и только в режиме DataTypeCompatiblity .

Длинная версия

При попытке использовать параметризованные запросы в ADO для SQL Сервер:

SELECT ?

Я параметризирую значение datetime как adDBTimeStamp:

//Language agnostic, vaguely C#-like pseudo-code
void TestIt()
{
   DateTime dt = new DateTime("3/15/2020");
   VARIANT v = DateTimeToVariant(dt);

   Command cmd = new Command();
   cmd.CommandText = "SELECT ? AS SomeDate";
   cmd.Parameters.Append(cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);

   Connection cn = GetConnection();
   cmd.Set_ActiveConnection(cn);
   cmd.Execute(out recordsAffected, EmptyParam, adExecuteNoRecords);
}

И это прекрасно работает, когда дата 3/15/2020.

Вы создаете VARIANT с VType из 7 (VT_DATE) и значением, которое является 8-байтовое значение с плавающей запятой:

VARIANT
   Int32  vt = 7; //VT_DATE
   Double date = 0;

Но это не сработает 12/30/1899

Если я выполню один и тот же тестовый код с одной конкретной датой-временем, произойдет ошибка:

void TestIt()
{
   DateTime dt = new DateTime("12/30/1899");
   VARIANT v = DateTimeToVariant(dt);

   Command cmd = new Command();
   cmd.CommandText = "SELECT ? AS SomeDate";
   cmd.Parameters.Append(cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);

   Connection cn = GetConnection();
   cmd.Set_ActiveConnection(cn);
   cmd.Execute(out recordsAffected, EmptyParam, adExecuteNoRecords);
}

Поставщик ADO OLEDB генерирует исключение (т. Е. Еще до того, как он достигает SQL Сервер):

Invalid date format

Но это не происходит со всеми SQL Серверными поставщиками OLEDB

При отладке этой проблемы я понял, что это происходит не со всеми поставщиками SQL серверов OLEDB. Microsoft обычно имеет 4 поставщика OLE DB для SQL сервера:

  • SQLOLEDB: поставщик Microsoft OLE DB для SQL сервера (поставляется с Windows с Windows 2000)
  • SQLNCLI: SQL Собственный клиент сервера (поставляется с SQL Server 2005)
  • SQLNCLI10: SQL Собственный клиент сервера 11.0 (поставляется с SQL Server 2008)
  • SQLNCLI11: SQL Собственный клиент сервера 12.0 (поставляется с SQL Server 2012)
  • MSOLEDBSQL: драйвер Microsoft OLE DB для SQL Сервер (поставляется с SQL) Server 2016)

При попытке работы с разными провайдерами отлично работает для некоторых:

  • SQLOLEDB: Работает
  • SQLNCLI11 (без DataTypeCompatibility): Работает
  • SQLNCLI11 (с DataTypeCompatibility on): Сбой

DataTypeCompatibility?

Да. ActiveX Data Objects (ADO), дружественная оболочка COM для недружественного COM OLEDB API, не понимает новые типы данных date, time, xml, datetime2, datetimeoffset. Были созданы новые константы типов данных OLEDB для представления этих новых типов. Поэтому любые существующие приложения OLEDB не будут понимать новые константы.

С этой целью новое ключевое слово поддерживается "родными" драйверами OLE DB:

  • DataTypeCompatibility=80

, который можно добавить в строку подключения:

"Поставщик = SQLNCLI11; Источник данных = отвертка; Идентификатор пользователя = hatguy; Пароль = hunter2; DataTypeCompatibility = 80;"

Это дает указание драйверу OLEDB возвращать только те типы данных OLEDB, которые существовали, когда OLEDB был впервые изобретен:

| SQL Server data type | SQLOLEDB        | SQLNCLI                        | SQLNCLI                           |
|                      |                 |                                | w/DataTypeCompatibility=80        |
|----------------------|-----------------|--------------------------------|-----------------------------------|
| Xml                  | adLongVarWChar  | 141 (DBTYPE_XML)               | adLongVarChar                     |
| datetime             | adDBTimeStamp   | adDBTimeStamp                  | adDBTimeStamp                     |
| datetime2            | adVarWChar      | adDBTimeStamp                  | adVarWChar                        |
| datetimeoffset       | adVarWChar      | 146 (DBTYPE_DBTIMESTAMPOFFSET) | adVarWChar                                  |
| date                 | adVarWChar      | adDBDate                       | adVarWChar                        |
| time                 | adVarWChar      | 145 (DBTYPE_DBTIME2)           | adVarWChar                        |
| UDT                  |                 | 132 (DBTYPE_UDT)               | adVarBinary (documented,untested) |
| varchar(max)         | adLongVarChar   | adLongVarChar                  | adLongVarChar                     |
| nvarchar(max)        | adLongVarWChar  | adLongVarWChar                 | adLongVarWChar                    |
| varbinary(max)       | adLongVarBinary | adLongVarBinary                | adLongVarBinary                   |
| timestamp            | adBinary        | adBinary                       | adBinary                          |

И есть сбой

Когда:

  • пытается параметризовать datetime значение
  • со значением 12/30/1899
  • при использовании "родного клиента" драйвера
  • и DataTypeCompatilibty включен
  • сам драйвер задыхается от значения
  • когда его значение действительно отлично.

Нет ничего плохого в том, чтобы пытаться использовать дату '12/30 / 1899`:

  • SELECT CAST('18991230' AS datetime) отлично работает
  • отлично работает в оригинальном драйвере OLE DB
  • отлично работает в "родных" драйверах OLE DB
  • в родном драйвере происходит сбой с DataTypeCompatibility на

Очевидно, что это ошибка в драйверах Microsoft OLE DB. Но это абсолютная правда, что Microsoft никогда, когда-либо , когда-либо , КОГДА-ЛИБО , исправит ошибку.

Итак, как обойти это?

Я могу обнаружить эту особую дату и время, чтобы попытаться обойти эту ошибку в наших слоях доступа к данным.

  • Но мне нужно значение, которое я могу поместить в VARIANT структуру,
  • , которая представляет 12/30/1899 12:00:00 AM
  • , которая работает под SQOLEDB
  • и ниже SQLNCLI xx драйверы
  • и ниже MSOLEDBSQL драйвер
  • в DataTypeCompatibilityMode
  • (и что, черт возьми, даже при выключенном режиме - хотя использование ADO без него недопустимо)

T- SQL генерируется драйвером

Когда драйвер OLE DB действительно не удосуживается сделать то, что я говорю, мы можем профилировать сгенерированный RP C:

SQOLEDB

exec sp_executesql N'SELECT @P1 AS SomeDate',N'@P1 datetime','1899-12-30 00:00:00'

SQLNCLI11

exec sp_executesql N'SELECT @P1 AS SomeDate',N'@P1 datetime2(0)','1899-12-30 00:00:00'

CMRE (Delphi)

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  ComObj,
  ActiveX,
  ADOdb,
  ADOint,
  Variants;

function GetConnection(Provider: string; DataTypeCompatibility: Boolean): _Connection;
var
    connectionString: string;
begin
{
    SQLOLEDB - Default provider with Windows
    SQLNCLI11 - SQL Server 2008 native client
}
    connectionString := 'Provider='+Provider+'; Data Source=screwdriver;User ID=hydrogen;Password=hunter2;';
    if DataTypeCompatibility then
        connectionString := connectionString+'DataTypeCompatibility=80';

    Result := CoConnection.Create;
    Result.Open(connectionString, '', '', adConnectUnspecified);
end;

procedure Test(ProviderName: string; DataTypeCompatibility: Boolean);
var
    dt: TDateTime;
    v: OleVariant;
    cmd: _Command;
    cn: _Connection;
    recordsAffected: OleVariant;
    s: string;
begin
    dt := EncodeDate(1899, 12, 30);// 12/30/1899 12:00:00 AM (also known in Delphi as zero)
    v := dt; //the variant is of type VT_DATE (7)

    cmd := CoCommand.Create;
    cmd.CommandText := 'SELECT ? AS SomeDate';
    cmd.Parameters.Append(cmd.CreateParameter('', adDBTimeStamp, adParamInput, 0, v));

    try
        cn := GetConnection(ProviderName, DataTypeCompatibility);
    except
        on E: Exception do
            begin
                WriteLn('Provider '+ProviderName+' not installed: '+E.message);
                Exit;
            end;
    end;

    if SameText(ProviderName, 'SQLOLEDB') then
        s := ''
    else if DataTypeCompatibility then
        s := ' (with DataTypeCompatibility)'
    else
        s := ' (without DataTypeCompatibility)';

    cmd.Set_ActiveConnection(cn);
    try
        cmd.Execute({out}recordsAffected, EmptyParam, adExecuteNoRecords);
        WriteLn('Provider '+ProviderName+s+': success.');
    except
        on E:Exception do
            begin
                WriteLn('Provider '+ProviderName+s+' failed: '+E.Message);
            end;
    end;

end;

procedure Main;
begin
    CoInitialize(nil);

    Test('SQLOLEDB', False);        //SQL Server client that ships with Windows since 2000

    Test('SQLNCLI', False);     //SQL Server 2005 native client
    Test('SQLNCLI', True);      //SQL Server 2005 native client, w/ DataTypeCompatibilty

    Test('SQLNCLI10', False);   //SQL Server 2008 native client
    Test('SQLNCLI10', True);    //SQL Server 2008 native client, w/ DataTypeCompatibilty

    Test('SQLNCLI11', False);   //SQL Server 2012 native client
    Test('SQLNCLI11', True);    //SQL Server 2012 native client, w/ DataTypeCompatibilty

    Test('MSOLEDBSQL', False);  //SQL Server 2016 native client
    Test('MSOLEDBSQL', True);   //SQL Server 2016 native client, w/ DataTypeCompatibilty
end;


begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
    WriteLn('Press enter to close');
    ReadLn;
end.

И хотя это не Delphi -специфический c вопрос; Я использую Delphi. Так что он помечен как Delphi. Если вы жалуетесь Я собираюсь задушить ваш язык.

Примечание : Это не ADO. net, это ADO. Не управляется. NET Framework Class Library, это собственный Win32 API-интерфейс COM OLE DB.

1 Ответ

0 голосов
/ 22 марта 2020

BrakNicku получил ответ.

Установите NumericScale свойство вашего параметра в любое значение в диапазоне 1-7.

Изменение кода с:

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);

до

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);
p.NumericScale = 1;

работает.

Работает даже с драйвером SQLOLEDB для SQL Server 2000.

Точность и масштаб различных типов данных

Возвращая наборы строк с SQL Сервера, содержащего различные типы данных, я могу спросить OLEDB, какие Precision и NumericScale различных типов данных T- SQL:

SQL Server type   ADO type               Precision  NumericScale  DefinedSize
----------------  ---------------------  ---------  ------------  -----------
int               adInteger (3)          10         255           4
real              adSingle (4)           7          255           4
money             adCurrency (6)         19         255           8
bit               adBoolean (11)         255        255           2
tinyint           adUnsignedTinyInt (17) 3          255           1
bigint            adBigInt (20)          19         255           8
uniqueidentifier  adGUID (72)            255        255           16
char(35)          adChar (129)           255        255           35
nchar(35)         adWChar (130)          255        255           35
decimal(15,5)     adNumeric (131)        15         5             19
datetime          adDBTimeStamp (135)    23         3             16
varchar(35)       adVarChar (200)        255        255           35
text              adLongVarChar (201)    255        255           2147483647
varchar(max)      adLongVarChar (201)    255        255           2147483647
nvarchar(35)      adVarWChar (202)       255        255           35
nvarchar(max)     adLongVarWChar (203)   255        255           1073741823
xml               adLongVarWChar (203)   255        255           1073741823
image             adLongVarBinary (205)  255        255           2147483647
varbinary(max)    adLongVarBinary (205)  255        255           2147483647

Поскольку SQL Сервер возвращает поле datetime с NumericScale из 3 ; там может быть добродетелью в изменении:

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);
p.NumericScale = 1;

на

Parameter p = cmd.CreateParameter("", adDBTimeStamp, adParamInput, 0, v);
p.NumericScale = 3;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...