Write directly to zip file from SqlDataReader

Oct 20, 2010 at 10:50 PM

Hi all, can i? and are there any examples of how to write a zipentry directly to a zipfile from a SqlDataReader/StreamWriter?

currently i'm reading a database records/columns - writing these to a delimeted file as i go. Once the DataReader is exhausted i close the streamwriter create a zip file and add my data file to that zip and save it then i delete the uncompressed file.

Now it works great, but i'm sure there must be a more elegant way of doing it. i.e write directly to the zipentry.

can anyone point me in the right direction, provide a snippet, after reading various parts of the documentation and examples i'm still not quite sure if it can be done

Many thanks

 

Coordinator
Oct 24, 2010 at 12:39 PM
Edited Oct 24, 2010 at 12:44 PM

Yes, you can create the zip file without the intermediate steps of creating, writing, and then deleting a filesystem file.

One of the aspects of the DotNetZip interface is that it allows you to create zip files using streams as the source of the zip entry data.   Huh? What does that mean?  Well, normally you would use a filesystem file as the "source" for data in a zipentry - keeping in mind that a zipentry simply represents a single compressed file (or a directory) within a zipfile.  You probably call ZipFile.AddFile() and pass the name of the "temporary" filesystem file.  What happens at the time your application calls ZipFile.Save() is that DotNetZip opens the filesystem file, reads the data, compresses it, and then writes the compressed form, with other metadata into the zipfile.  When it finishes reading the data, it closes the source filesystem file, then writes the "terminator" data into the zipfile.  This is the normal, "vanilla" way to create a zipfile using DotNetZip.

What you want is for DotNetZip to get the data for the ZipEntry from something other than a filesystem file.  No problem.  This is designed into the class interface. There are several options.

Option 1: use ZipFile.AddEntry() and pass a Stream.   In this case, at the time of ZipFile.Save(), DotNetZip reads the Stream.  This model assumes you have a readable System.IO.Stream that contains the raw (uncompressed) data for the zip entry, and at the time of the call to .Save(), the Position on that Stream is set at the beginning of the actual entry data. Typically this means a Position of 0 (Zero).  A typical use case here is to employ a MemoryStream, which is just an in-memory buffer that exposes Read() and Write() methods like a stream.  So what you might do is read from your SqlDataReader, write into the MemoryStream, then call .Seek() on the MemoryStream to reset the Position back to 0 (also can just set the Position property to 0 = it's the same thing), then call ZipFile.AddEntry with that MemoryStream, specifying the name of the entry within the zipfile you'd like to use.  Does that make sense?  This still uses a temporary "container" for the data you get from SQL. In this case it's a MemoryStream, not a filesystem file.  You eliminate the housekeeping associated with creating and deleting the file, which is good, but still, using this approach the data makes one more stop (in the MemoryStream) than is necessary.

Option 2: use the ZipFile.AddEntry() overload method that accepts a WriteDelegate. In this case, at the time of ZipFile.Save(), DotNetZip will invoke your code to write into the zip file, and you provide this code in the form of a Ionic.Zip.WriteDelegate. (If you haven't used delegates before, don't worry.  Think of them as typed method pointers. In this case the WriteDelegate is just a piece of code that you provide, which gets called by DotNetZip during the call to ZipFile.Save()).  With this option, there is no redundant movement of data, as I described above. And, with the WriteDelegate, you get to use all the fanciness of C# 3.0, with anonymous methods for the delegate if you like.  Or, if you use VB, or if you otherwise don't want anonymous methods, you can specify a formal method name.  Check out the examples in the documentation to see what I mean.  The WriteDelegate accepts two parameters, a string and a stream.  The string represents the entryName, the name of the zipentry within the zipfile. The stream represents the stream that your code must write the uncompressed zipentry data into.   In your case, within the WriteDelegate, you would/could ... 1) open the SqlDataReader, 2) create a StreamWriter from the passed stream, 3) do a loop in which you read from the SqlDataReader and write into the StreamWriter, 4) close the SqlDataReader. 

Check out the documentation on ZipFile.AddEntry(). 

Ask if you have more questions.

Good luck.

Oct 27, 2010 at 10:12 PM

Cheeso, Thanks for such a full answer.

I had looked at the AddEntry method using the write delegate but i hadn't thought of running my query from within it. I will have a play with restructuring my app.

I will get a snippet working and post an example for future forum searchers.

Thanks again

 

 

Nov 25, 2012 at 6:10 PM

You can see sample code regarding Option 1 and (the better) Option 2 mentioned above at:

http://zoomicon.wordpress.com/2012/11/23/howto-write-directly-into-a-zip-stream-using-dotnetzip/