SqlBulkCopyが使用されているときにバイナリ列のデータソースとしてストリームを提供する

ado.net c# idatareader sqlbulkcopy sql-server

質問

ストリーミング方式でSqlServer からデータを読み取る必要がある場合は、そのための機能がいくつかあります。 CommandBehavior.SequentialAccessSqlDataReaderを使用する場合、特にバイナリ列データにアクセスする必要がある場合は、そのためにGetStream(int)メソッドがあります。

var cmd = new SqlCommand();
cmd.Connection = connection;
cmd.CommandText = @"select 0x0123456789 as Data";

using (var dr = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
    dr.Read();

    var stream = dr.GetStream(0);
    // access stream
}

しかし、 SqlBulkCopyを使用して SqlServerにデータ供給する必要がある場合、特にストリームをバイナリ列のデータソースとして提供する必要がある場合、逆方向のデータをストリーミングするとどうなりますか?

私は次のことを試みた

var cmd2 = new SqlCommand();
cmd2.Connection = connection;
cmd2.CommandText = @"create table #Test (ID int, Data varbinary(max))";
cmd2.ExecuteNonQuery();

using (SqlBulkCopy sbc = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null))
{
    sbc.DestinationTableName = "#Test";
    sbc.EnableStreaming = true;

    sbc.ColumnMappings.Add(0, "ID");
    sbc.ColumnMappings.Add(1, "Data");

    sbc.WriteToServer(new TestDataReader());
}

TestDataReaderは次のようにIDataReaderを実装します。

class TestDataReader : IDataReader
{
    public int FieldCount { get { return 2; } }
    int rowCount = 1;
    public bool Read() { return (rowCount++) < 3; }
    public bool IsDBNull(int i) { return false; }

    public object GetValue(int i)
    {
        switch (i)
        {
            case 0: return rowCount;
            case 1: return new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89 };
            default: throw new Exception();
        }
    }

    //the rest members of IDataReader
}

期待どおりに機能しました。

しかし、

case 1: return new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89 };

case 1: return new MemoryStream(new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89 });

メッセージで例外System.InvalidOperationExceptionが発生しました。

データソースのMemoryStream型の指定された値は、指定したターゲット列のvarbinary型に変換できません。

IDataReader (またはおそらくDbDataReader )からSqlBulkCopyへのStreamを、バイナリ列のデータソースとして、そのデータをすべてメモリ(バイト配列)に最初にコピーせずに供給する方法はありますか?

受け入れられた回答

これはどこに文書化されているか分かりませんが、 SqlBulkCopyソースコードの検査を短時間行うと、異なるデータリーダーをさまざまな方法で扱うことができます。まず、 SqlBulkCopyはストリーミングとGetStreamサポートしていますが、 IDataReaderインターフェイスにはGetStreamメソッドが含まれていないことがあります。したがって、カスタムIDataReader実装をSqlBulkCopyフィードするとき、バイナリ列をストリームとして扱うことはなく、 Stream型の値を受け入れません。

一方、 DbDataReader このメソッドがあります。あなたが養う場合SqlBulkCopyのインスタンスDbDataReader -inheritedクラスを-それはストリーミング方式ですべてのバイナリ列を扱いますし、それが呼び出されますDbDataReader.GetStream

したがって、あなたの問題を解決するには - DbDataReaderからDbDataReaderように継承します:

class TestDataReader : DbDataReader
{
    public override bool IsDBNull(int ordinal) {
        return false;
    }

    public override int FieldCount { get; } = 2;
    int rowCount = 1;

    public override bool HasRows { get; } = true;
    public override bool IsClosed { get; } = false;

    public override bool Read()
    {
        return (rowCount++) < 3;
    }

    public override object GetValue(int ordinal) {
        switch (ordinal) {
            // do not return anything for binary column here - it will not be called
            case 0:
                return rowCount;
            default:
                throw new Exception();
        }
    }

    public override Stream GetStream(int ordinal) {
        // instead - return your stream here
        if (ordinal == 1)
            return new MemoryStream(new byte[] {0x01, 0x23, 0x45, 0x67, 0x89});
        throw new Exception();
    }
    // bunch of irrelevant stuff

}

人気のある回答

次のコードを参照してください

static int SendOrders(int totalToSend)
    {
      using (SqlConnection con = new SqlConnection(connectionString))
      {
        con.Open();
        using (SqlTransaction tran = con.BeginTransaction())
        {
          var newOrders =
                  from i in Enumerable.Range(0, totalToSend)
                  select new Order
                  {
                    customer_name = "Customer " + i % 100,
                    quantity = i % 9,
                    order_id = i,
                    order_entry_date = DateTime.Now
                  };

          SqlBulkCopy bc = new SqlBulkCopy(con,
            SqlBulkCopyOptions.CheckConstraints |
            SqlBulkCopyOptions.FireTriggers |
            SqlBulkCopyOptions.KeepNulls, tran);

          bc.BatchSize = 1000;
          bc.DestinationTableName = "order_queue";
          bc.WriteToServer(newOrders.AsDataReader()); 

          tran.Commit();
        }
        con.Close();

      }

      return totalToSend;

    }


ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ