WCF流式传输大量对象

时间:2017-10-23 13:05:18

标签: c# wcf stream streaming chunking

我有一个WCF服务,它查询数据库并返回大量记录。有太多记录,服务器耗尽内存并在返回之前失败。

所以当我从数据库中取回记录时,我想要将记录发回,或者一次取回一组号码。

为了更加清晰,我无法收集提取到服务器上的集合中的呼叫记录,因为在收集所有记录之前服务器内存不足。我想尝试一次又一次地将它们一个接一个地发回去。

  

例如,以块:

     
      
  1. 获取前1000条记录
  2.   
  3. 添加到收藏集
  4.   
  5. 将集合发送给客户
  6.   
  7. 清除收藏
  8.   
  9. 获取下一个1000条记录,然后从第2步重复
  10.   

所以我的想法是Web服务代码看起来像这样:

Public IEnumerable<Customer> GetAllCustomers()
{
     // Setup Query
     string query = PrepareQuery();

     // Create Connection
     connection = new SqlConnection(ConnectionString);
     connection.Open();

     var sqlcommand = connection.CreateCommand();
     sqlcommand.CommandText = query.ToString();

     // Read Results
     var reader = sqlcommand.ExecuteReader();
     while (reader.Read())
     {
         Customer customer = new Customer();
         foreach (var column in Columns)
         {
             int fieldIndex = reader.GetOrdinal(column);
             object value = reader.GetValue(fieldIndex);
             customer[column.Name] = value;
         }

         yield return customer;
     }
}

我不想考虑分页,因为SQL服务器上的Order By很慢。

寻找在WCF中执行此操作的方法

2 个答案:

答案 0 :(得分:3)

我想你回答了自己的问题。有两种方法可以实现,流或块。 您可以在wcf中进行流式传输 - 请参阅https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/large-data-and-streaming 你有一个Stream要写入,所以你需要自己处理如何在该流上编码数据,以及如何在客户端解码它。 另一种方法是你进行分块/分页。您只需修改服务即可接受以下服务:页码或其他方式来指示需要哪个页面。 你做哪一个取决于应用程序,例如多少数据?客户的本质是什么?是否可以使用一些字段来打开?等等 下面是一些用于制作流的psudo代码,可以在服务器端执行此操作。它基于以下示例:https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-enable-streaming 我不会为你编写完整的可编译代码,但这是它的要点。 在服务器中: public Stream GetBigData() {     返回new BigDataStream(); } BigDataStream(未示出的方法未显示): class BigDataStream:Stream {     public BigDataStream()     {         //打开数据库连接         //运行您的查询         //获取DataReader     }     //你需要一个缓冲区来对Read之间的数据进行编码     列表与LT;字节&GT; _encodeBuffer = new List&lt; byte&gt;();     public override int Read(byte [] buffer,int offset,int count)     {         //从DataReader中读取并填充_encodeBuffer         //直到_encodeBuffer至少包含count个字节         //(或直到没有更多记录)         // 例如:         while(_encodeBuffer.Count&lt; count&amp;&amp; _reader.Read())         {             //(1)             //将记录编码为字节数组。这该怎么做?             //你可以读入一个类然后使用数据             //例如,合同序列化。如果你这样做,你             //可能会更容易预先添加一个整数             //指定以下编码消息的长度。             //这将使客户端更容易反序列化它。             //(2)             //附加编码记录字节(加上任何长度前缀             //等)到_encodeBuffer         }         //从_encodeBuffer中删除最多的第一个计数字节         //并在请求的偏移处将它们复制到缓冲区中         //返回添加的字节数     }     public override void Close()     {         //关闭阅读器+数据库连接         base.Close();     } }

答案 1 :(得分:0)

感谢mikelegg&amp; Reniuz 帮助找到解决方案。我希望我可以给他们正确的答案,但我担心下一个开发人员阅读这个问题不会完全受益。那么我最终得到了什么。

  1. 设置服务器和客户端的配置文件(关注链接:Large Data and Streaming
  2. 关注this解决方案,可以从here
  3. 下载源代码

    我必须稍微更改DBRowStream.DBThreadProc方法,以便发布源代码:

    DBRowStream类:

        void DBThreadProc(object o)
        {
            SqlConnection con = null;
            SqlCommand com = null;
    
            try
            {
                con = new System.Data.SqlClient.SqlConnection(/*ConnectionString*/);
                com = new SqlCommand();
                com.Connection = con;
                com.CommandText = PrepareQuery();
                con.Open();
                SqlDataReader reader = com.ExecuteReader();
    
                int count = 0;
    
                MemoryStream memStream = memStream1;
                memStreamWriteStatus = 1;
                readyToWriteToMemStream1.WaitOne();
    
                while (reader.Read())
                {
                    // Populate
                    Customer customer = new Customer();
                    foreach (var column in Columns)
                    {
                        int fieldIndex = reader.GetOrdinal(column);
                        object value = reader.GetValue(fieldIndex);
                        customer[column.Name] = value;
                    }                   
    
                    // Serialize: I used a custom Serializer 
                    // but BinaryFormatter should be fine
                    DBDataFormatter.Serialize(memStream, customer);
    
                    count++;
    
                    if (count == PAGESIZE) // const int PAGESIZE = 10000
                    {
                        switch (memStreamWriteStatus)
                        {
                            case 1: // done writing to stream 1
                                {
                                    memStream1.Position = 0;
                                    readyToSendFromMemStream1.Set();
                                    // write stream 1 is done...waiting for stream 2 
                                    readyToWriteToMemStream2.WaitOne();
                                    memStream = memStream2;
                                    memStream.Position = 0;
                                    memStream.SetLength(0); // Added:To Reset the stream. Else was getting garbage data back
                                    memStreamWriteStatus = 2;
    
                                    break;
                                }
                            case 2: // done writing to stream 2
                                {
                                    memStream2.Position = 0;
                                    readyToSendFromMemStream2.Set();
                                    // Write on stream 2 is done...waiting for stream 1
                                    readyToWriteToMemStream1.WaitOne();
                                    // done waiting for stream 1 
                                    memStream = memStream1;
                                    memStreamWriteStatus = 1;
                                    memStream.Position = 0;
                                    memStream.SetLength(0); // Added: Reset the stream. Else was getting garbage data back
    
                                    break;
                                }
                        }
                        count = 0;
                    }
                }
    
                if (count > 0)
                {
                    switch (memStreamWriteStatus)
                    {
                        case 1: // done writing to stream 1
                            {
                                memStream1.Position = 0;
                                readyToSendFromMemStream1.Set();
                                // END write stream 1 is done...waiting for stream 2 
                                break;
                            }
                        case 2: // done writing to stream 2
                            {
                                memStream2.Position = 0;
                                readyToSendFromMemStream2.Set();
                                // END write stream 2 is done...waiting for stream 1 
                                break;
                            }
                    }
                }
                bDoneWriting = true;
                bCanRead = false;
            }
            catch
            {
                throw;
            }
            finally
            {
                if (com != null)
                {
                    com.Dispose();
                    com = null;
                }
                if (con != null)
                {
                    con.Close();
                    con.Dispose();
                    con = null;
                }
            }
        }
    

    然后是客户方:

    private static void TestGetRecordsAndDump()
    {
        const string FILE_NAME = "Records.CSV";
        File.Delete(FILE_NAME);
        var file = File.AppendText(FILE_NAME);
        long count = 0;
        try
        {
            ServiceReference1.ServiceClient service = new ServiceReference1.DataServiceClient();
            var stream = service.GetDBRowStream();
    
            Console.WriteLine("Records Retrieved : ");
            Console.WriteLine("File Size (MB)    : ");
    
            var canDoLastRead = true;
            while (stream.CanRead && canDoLastRead)
            {
               try
               {
                   Customer customer = DBDataFormatter.Deserialize(stream); // Used custom Deserializer, but BinaryFormatter should be fine
    
                   file.Write(customer.ToString());
    
                   count++;
               }
               catch
               {
                    canDoLastRead = false; // Bug: stream.CanRead is not set to false at the end of stream, so I do this trick to know if I finished retruning all records.
               }
               finally
               {
                   Console.SetCursorPosition("Records Retrieved : ".Length, 0);
                   Console.Write(string.Format("{0}               ", count));
                   Console.SetCursorPosition("File Size (MB)    : ".Length, 1);
                   Console.Write(string.Format("{0:G}             ", file.BaseStream.Length / 1024f / 1024f));     
               }
            }
            finally
            {
                file.Close();
            }
        }
    }
    

    有一个我似乎无法解决的错误,stream.CanRead未设置为false,然后所有记录都已返回,无法解决原因,但至少现在,我可以查询大型数据集,并返回所有记录,而不是服务器或客户端内存不足。