I want to generate big xlsx file, but don't want to keep it in memory of the server and get OutOfMemoryException .
So I read the data from database page by page, generate rows with OpenXmlWriter and send it part by part to the client:
// I use `MemoryStream` as the buffer
OpenXmlWriter = OpenXmlWriter.Create(OutputStream);
OpenXmlWriter.WriteStartElement(new Worksheet());
OpenXmlWriter.WriteStartElement(new SheetData());
foreach(var row in rows)
{
//...write cells with OpenXmlWriter and then
OutputStream.Position = 0;
var buffer = new byte[OutputStream.Length];
OutputStream.Read(buffer, 0, (int)OutputStream.Length);
FlushCalback(buffer);
OutputStream.SetLength(0);
System.Web.HttpContext.Current.Response.BinaryWrite(dataBuffer);
}
But after downloading I found, that it generates only xml markup, while xlsx is actualy "package". I can't find any example how to do that. Is there any solution? Or another libraries ?
UPDATE:
SpreadsheetDocument could help, but it writes ALL the data to the stream after calling Save() method. And it will rewrite everithing after each call.
SpreadsheetDocument = SpreadsheetDocument.Create(OutputStream, SpreadsheetDocumentType.Workbook);
//... generate rows
SpreadsheetDocument.Save();
Related
I have a project where my goal is to produce an .xlsm Excel spreadsheet using .NET and the EEPlus 5.8.14 Excel Spreadsheet library. I can do this using EEPlus's documented techniques, (though some of these I cannot get to work). As I was working on this, I realized that what my code needed to do was relatively small, and it made sense to use an existing .xlsm file as a template and just make changes to what I needed to change using EEPlus.
So now I am including the .xlsm file as a resource compiled into the assembly. This works great, and I can read the file from the resources and produce it from my controller. But once read, this data inside EPPlus seems to be read-only. So while this produces an Excel file:
public ActionResult ExcelFile(){
const string ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Byte[] bytes = Properties.Resources.AssetsEntry;
string fstem = Path.GetRandomFileName();
int unique = 0;
string filePath = String.Format("{0}#AutoGen_{1}_{2}.{3}", Path.GetTempPath(), fstem, ++unique, "xlsm");
var outStream = System.IO.File.OpenWrite(filePath);
var writer = new BinaryWriter(outStream);
writer.Write(bytes);
outStream.Close();
ExcelPackage excelPackage = new ExcelPackage(filePath);
var sheet = excelPackage.Workbook.Worksheets[1];
//place where I might want to change data
//sheet.Cells["B3"].Value = "testing";
var excelData = excelPackage.GetAsByteArray();
var fileName = "ExcelFile.xlsm";
return File(excelData, ContentType, fileName);
}
If I try to uncomment out the second commented-out line, that code fails to change the resulting Excel spreadsheet (though there is no error). How do I go about reading in an Excel spreadsheet and making changes using EEPlus?
UPDATE: I can add new worksheets to an uploaded spreadsheet, and I can alter those added sheets. But I cannot alter data on uploaded worksheets. Fortunately, for this particular project, that is acceptable. But it would be frustrating if I wanted to be able to set up a worksheet in Excel and then populate it programmatically.
When I use c# to read a excel which I use excel to open it, there is a error The process cannot access the file 'xxxx' because it is being used by another process.
Is there any way to do this, I don't wan't open it for every time.
I'm not sure how you're trying to read the xlsx file, so it might not be possible using the library or tool that you are currently using.
It is possible to open the file stream for reading while Excel has the file open. However, you should be aware that it is also inherently dangerous. The process reading the excel file expects to be reading a consistent view of the file. If Excel decides to write to that same file while it is being read, then it is almost assured that the reading process will fail in some catastrophic way. Since an .xlsx file is just a zip file, the most likely result will be a failure in accessing or decompressing one of the .xlsx entries.
Here is an example of how you can do this using a library I maintain: Sylvan.Data.Excel.
var file = "myfile.xlsx";
// DANGEROUS: open it for reading, but allow other processes to write to it at the same time.
var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// gets the workbook type from the filename extension
var type = ExcelDataReader.GetWorkbookType(file);
// create the data reader
var reader = ExcelDataReader.Create(stream, type);
// loop over rows
while (reader.Read())
{
// write out the data in the row
Console.WriteLine("Row: " + reader.RowNumber);
for (int i = 0; i < reader.RowFieldCount; i++)
{
Console.WriteLine(reader.GetString(i));
}
Console.WriteLine();
}
I use Spreadsheetgear to export the results of custom SQL queries as excel files.
Now I want to improve this system: The user will be able to upload an excel template file into the database (currently as varbinary). For example, it could have one worksheet with calculations, then when exporting data into that template it'll fill a different worksheet with the datatable from the query.
Can spreadsheetgear do this? If so, how does it work - mainly how can I load an existing excel file as a Spreadsheetgear workbook/workbookset? I could not find anything in their documentation (though I am still looking).
Edit: Solved.
I create the workbook manually, load the template from the database as a byte[], then open said template with the OpenFromMemory function:
// Create workbookSet
SpreadsheetGear.IWorkbookSet workbookSet = SpreadsheetGear.Factory.GetWorkbookSet();
// Create a new empty workbook in the workbookSet.
SpreadsheetGear.IWorkbook workbook = workbookSet.Workbooks.Add();
if(TemplateID != -1) // If this case requires a template
{
// Get template from SQL database (.xlsx stored as varbinary(max))
byte[] template = GetTemplateByID(VorlagenID);
workbook = workbookSet.Workbooks.OpenFromMemory(template);
}
// Create export worksheet
SpreadsheetGear.IWorksheet worksheet = workbook.Worksheets[0];
worksheet.Name = "Export";
[...]
Templates always use the Worksheet[1] in my case, but it should be easy to create a Worksheet[1] for the export.
Yes it is possible to set up pre-constructed template files using SpreadsheetGear. We use this extensively using .NET / C# / MSSSQL. The method allows you to create quite sophisticated templates and then simply add the required data. This of course includes any calculations you build into the template.
Method 1 - Store the template on a webserver, extract and write the created user spreadsheet to a folder on the web server. Return the filename to allow extraction by code or by the user from the server.
public static String SaveTemplateSpreadsheetToServer()
{
// Open the workbook.
var templatename = HostingEnvironment.MapPath("~/Files/MyTemplate.xlsx");
var workbook = Factory.GetWorkbook(templatename );
// Read and write to the spreadsheet
// Save a copy to disk and return filename
var filename = "The_exported_file.xlsx";
var filePath = HostingEnvironment.MapPath("~/FilesTemp/" + filename);
workbook.SaveAs(filePath, FileFormat.OpenXMLWorkbook);
// close workbook
workbook.Close();
// Return the filename
return fileName;
}
Method 2: Store the template on a webserver, extract and save modified spreadsheet as a byte array. Download directly an attachment
public static byte[] SaveTemplateSpreadsheetToServer()
{
// Open the workbook.
var templatename = HostingEnvironment.MapPath("~/Files/MyTemplate.xlsx");
var workbook = Factory.GetWorkbook(templatename );
// Read and write to the spreadsheet
// Save as byte array and send to user
var byteArray = workbook.SaveToMemory(FileFormat.OpenXMLWorkbook);
// close workbook
workbook.Close();
// Return the byte array
return byteArray;
}
We have done some work with binary template files saved in a database but find it more convenient to work with physical template files on a web server. It is easier to manage changes to the template.
My only caution is to avoid working with very big templates that have lots of "junk" in them (e.g. images). The process becomes affected by the time it takes to load the file into memory prior to the read / write / export activity. Less than 1MB is ideal and less than 2MB is manageable.
Sorry for my English. I used the library Epplus and I really like it. But I've got a problem: Out of Memory. Need to write large amounts of data, no matter what. I want to know is it possible to append to the end of the Excel file is not stored in the memory of all. Or create multiple files and then concatenate into one file. Thanks in advance.
1)if you retrieve your data from database
use a datareader instead of datatable
2)write the excel to a temp file, delete it after done(if it's web environment, use response.writefile then delete it)
3)write the header first then append data to it
something like this (using my phone to type this)
var pck = new ExcelPackage();
var ws = pck.AddSheet("sheet1");
//write header here
pck.saveas(fileinfo);
pck.dispose(); // not sure if function existed
pck= new excelpage(fileino.fullname);
ws = pck.worksheets[1];
var rowIndex =0;
while (reader.read())
{
if (++rowindex % 100000 == 0)
{
// save and re-open
}
//write row here
}
pck.save();
//dispose / send file / delete file etc
I need a way to read a Excel file from a stream. It doesn't seem to work with the ADO.NET way of doing things.
The scenario is that a user uploads a file through a FileUpload and i need to read some values from the file and import to a database.
For several reasons I can't save the file to disk, and there is no reason to do so either.
So, anyone know of a way to read a Excel file from a FileUpload stream?
It seems i found a soultion to the problem myself.
http://www.codeplex.com/ExcelDataReader
This library seems to work nicely and it takes a stream to read the excel file.
ExcelDataReader reader = new ExcelDataReader(ExcelFileUpload.PostedFile.InputStream);
This can be done easily with EPPlus.
//the excel sheet as byte array (as example from a FileUpload Control)
byte[] bin = FileUpload1.FileBytes;
//gen the byte array into the memorystream
using (MemoryStream ms = new MemoryStream(bin))
using (ExcelPackage package = new ExcelPackage(ms))
{
//get the first sheet from the excel file
ExcelWorksheet sheet = package.Workbook.Worksheets[1];
//loop all rows in the sheet
for (int i = sheet.Dimension.Start.Row; i <= sheet.Dimension.End.Row; i++)
{
//loop all columns in a row
for (int j = sheet.Dimension.Start.Column; j <= sheet.Dimension.End.Column; j++)
{
//do something with the current cell value
string currentCellValue = sheet.Cells[i, j].Value.ToString();
}
}
}
SpreadsheetGear can do it:
SpreadsheetGear.IWorkbook workbook = SpreadsheetGear.Factory.GetWorkbookSet().Workbooks.OpenFromStream(stream);
You can try it for yourself with the free evaluation.
Disclaimer: I own SpreadsheetGear LLC
Infragistics has an excel component that can read an excel file from a stream.
I'm using it in a project here and it works well.
Also the open source myXls component could easily be modified to support this. The XlsDocument contstructor only supports loading from a file given by a file name, but it works by creating a FileStream and then reading the Stream, so changing it to support loading from streams should be trivial.
Edit:
I see that you found a solution but I just wanted to note that I updated the source code for the component so that it now can read an excel file directly from a stream. :-)
I use ClosedXML nuget package to read excel content from stream. It has a constructor overload in XLWorkbook class which takes stream pointing to an excel file (aka workbook).
imported namespace at the top of your code file:
using ClosedXML.Excel;
Source code:
var stream = /*obtain the stream from your source*/;
if (stream.Length != 0)
{
//handle the stream here
using (XLWorkbook excelWorkbook = new XLWorkbook(stream))
{
var name = excelWorkbook.Worksheet(1).Name;
//do more things whatever you like as you now have a handle to the entire workbook.
var firstRow = excelWorkbook.Worksheet(1).Row(1);
}
}