Как мне указать использование определенных сервером значений в Табличном параметре, если мой источник - DataReader? - PullRequest
1 голос
/ 23 апреля 2019

Мой TVP выглядит так ::

create type p_tvp as table
(
   id int identity not null,
   value float not null
)

У меня есть некоторый datareader, который выдает список типа double (не фактический SQL-запрос):

 using (var conn = new SqlConnection(_srcString)
 using (var cmd = new SqlCommand("select * from (values(1e),(2e),(3e))f(v)", conn))
 {
       await conn.OpenAsync();

       using(var rdr = await _cmd.ExecuteReaderAsync())
       using(var tCon = new SqlConnection(_targetString))
       using(var tcmd = new SqlCommand("MyStoredProcedure",tCon))
       {
         await tCon.OpenAsync();
         tcmd.CommandType = CommandType.StoreProcedure;
         var param = tcmd.Parameters.AddWithValue(@tvp, rdr);
         param.SqlDbType = SqlDbType.Structured;
         param.TypeName = "dbo.p_tpv";
         await tcmd.ExecuteNonQuery();
       }
 }

Ошибка, которую я получаю, заключается в том, что мне не хватает параметров. В качестве TVP, на который я нацеливаюсь, есть 2 столбца. Если я добавляю фиктивный столбец, я получаю ошибку, что не могу вставить идентификатор в параметры с табличным значением.

Я не могу переопределить TVP.

Другой вопрос указывает DataTable в качестве источника, в ответе используется IEnumerable<SqlDataRecord>. К сожалению, ни один не решает мой случай наличия DbDataReader. Я могу преобразовать в SqlDataRecord при необходимости, но я бы хотел избежать дополнительных шагов, так как не могу легко контролировать источник DbDataReader.

1 Ответ

0 голосов
/ 09 мая 2019

Как отметил Фабио в комментарии к вопросу, вы можете использовать IEnumerable<SqlDataRecord>, чтобы настроить пользовательскую структуру данных с помощью SqlDataRecord, а затем передать результаты из SqlDataReader в TVP.

В следующем примере в столбце «id» TVP используется конструктор SqlMetaData, который позволяет установить useServerDefault в true. yield break должен безопасно завершиться, если в исходном запросе не было возвращено ни одной строки.

private IEnumerable<SqlDataRecord> SendRowsToProc(SqlDataReader reader)
{
  if (!reader.HasRows)
  {
    yield break;
  }

  SqlDataRecord resultRow = new SqlDataRecord(new SqlMetaData[] {
             new SqlMetaData("id", SqlDbType.Int, true, true, SortOrder.Unspecified, 0),
             new SqlMetaData("value", SqlDbType.Float)
                                                    });

  while (reader.Read())
  {
    resultRow.SetDouble(1, reader.GetDouble(0));

    yield return resultRow;
  }
}

Итак, вместо того, чтобы передавать rdr в качестве «значения» TVP SqlParameter, вы передаете: SendRowsToProc(rdr).


Другой вариант для рассмотрения, если вы действительно хотите передать rdr в качестве значения TVP:

  1. Используйте другой UDTT, который имеет тот же столбец ID INT NOT NULL, но это , а не , помеченный как IDENTITY
  2. В вашем операторе SELECT (у источника) начните список столбцов с:

    ROW_NUMBER() OVER (ORDER BY @@MICROSOFTVERSION) AS [ID]
    

Это должно обеспечить те же функции, что и столбец IDENTITY в настоящее время. Конечно, это не будет работать, если вы выполняете хранимую процедуру, но для случая, указанного в вопросе, это должно быть хорошо.

...