SqlBulkCopy가 사용될 때 스트림을 이진 열의 데이터 원본으로 제공

ado.net c# idatareader sqlbulkcopy sql-server

문제

하나는 스트리밍 방식으로 SQLSERVER에서 데이터를 읽을 필요가있는 경우, 그 몇 가지 기능이있다. CommandBehavior.SequentialAccess 와 함께 SqlDataReader 를 사용하는 경우와 특히 이진 열 데이터에 액세스해야하는 경우에는이를위한 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
}

그러나 하나의 사용 SQLSERVER 데이터 공급해야하는 경우, 반대 방향으로 데이터를 스트리밍에 대한 SqlBulkCopy , 스트림 이진 열에 대한 데이터의 소스로 공급되어야하는 경우 특히?

나는 다음을 시도했다.

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 를 구현 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 )에서 StreamSqlBulkCopyStream 을 공급하는 방법이 있습니까? 먼저 모든 데이터를 메모리 (바이트 배열)에 복사하지 않고 이진 열의 데이터 원본으로 사용합니까?

수락 된 답변

이것이 어디에서나 문서화되어 있는지 확신 할 수 없지만, SqlBulkCopy 소스 코드를 면밀히 SqlBulkCopy 보면 다른 데이터 판독기를 다른 방식으로 처리한다는 것을 알 수 있습니다. 첫째, SqlBulkCopy 는 스트리밍과 GetStream 지원하지만 IDataReader 인터페이스에는 GetStream 메서드가 포함되어 있지 않습니다. 따라서 사용자 지정 IDataReader 구현을 SqlBulkCopy 공급할 때 이진 열을 스트림으로 처리하지 않고 Stream 형식의 값을 허용하지 않습니다.

반면에 - DbDataReader 이 방법을 가지고있다. DbDataReader -inherited class의 인스턴스 인 SqlBulkCopy 를 피드하면 모든 이진 열을 스트리밍 방식으로 처리하고 DbDataReader.GetStream 을 호출합니다.

그래서 귀하의 문제를 해결하려면 - 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는 합법적입니까? 예, 이유를 알아보십시오.