For an asp.Net MVC project, I will need to handle large files( mostly 200-300Mo, sometime 1Go).
I will store them in the database(for backup reasons/consistency reason).
I'm concerned by performance issue, so I want to avoid all I can to have a array of byte anywhere in the program, the goal is then to work with stream every where.
I've a layered application, which mostly means that I've several "DataStore", which are responsible to connect and retrieve/insert/update data from the database.
Since EF doesn't support Filestream for now, I'm handling the "File part" through simple Sql requests. I've read a good article on filestream usage here: http://blog.tallan.com/2011/08/22/using-sqlfilestream-with-c-to-access-sql-server-filestream-data/
And I've some additional questions, which I hope you can help me/point me to the good direction:
- Since I've a layered application, once I've my SQLFileStream object instantiated, could I dispose SqlCommand/Sql Connection/Transaction scope?
- If not, how I'm supposed to close them?
- In the previous link, there is an example which show how to use it with ASP. But since I'm using ASP.Net MVC, isn't there an helper which is directly able to stream a file to the browser? Because I found many example of return binary data to browser, but for now, all example I found make basically something like
Stream.ToArray()
to fill an array of byte and return it to the browser. I found that I can return aFileStreamResult
which can take in parameter aStream
. Is that the right direction?
(I'm not currently concerned by uploading big files, since they are inserted by an heavy client in the database)
EDIT
(Sorry for the dirty code, it's only to not have 50 different methods here. I've made a few more try, and I'm currently stuck with the "read" part, because of separated part(where we generate the layer and where we consume it):
SqlConnection conn = GetConnection();
conn.Open();
SqlCommand cmd = new SqlCommand(_selectMetaDataRequest, conn);
cmd.Parameters.Add(_idFile, SqlDbType.Int).Value = idFile;
SqlDataReader rdr = cmd.ExecuteReader();
rdr.Read();
string serverPath = rdr.GetSqlString(0).Value;
byte[] serverTxn = rdr.GetSqlBinary(1).Value;
rdr.Close();
return new SqlFileStream(serverPath, serverTxn, FileAccess.Read);
But I get an exception at rdr.GetSqlBinary(1).Value
because GET_FILESTREAM_TRANSACTION_CONTEXT return null. I found here that this is due the missing transaction.
I tried with a "TransactionScope"+its .Complete();
call. Doesn't change anything.
I tried to do a BEGIN TRANSACTION like showed in the previous link:
SqlConnection connection = GetConnection(); connection.Open(); SqlCommand cmd = new SqlCommand();
cmd.CommandText = "BEGIN TRANSACTION";
cmd.CommandType = CommandType.Text;
cmd.Connection = connection;
cmd.ExecuteNonQuery();
cmd = new SqlCommand(_selectMetaDataRequest, connection);
cmd.Parameters.Add(_idFile, SqlDbType.Int).Value = idFile;
SqlDataReader rdr = cmd.ExecuteReader();
rdr.Read();
string serverPath = rdr.GetSqlString(0).Value;
byte[] serverTxn = rdr.GetSqlBinary(1).Value;
rdr.Close();
SqlFileStream sqlFileStream = new SqlFileStream(serverPath, serverTxn, FileAccess.Read);
cmd = new SqlCommand(); cmd.CommandText = "COMMIT TRANSACTION"; cmd.CommandType = CommandType.Text; cmd.Connection = connection; cmd.ExecuteNonQuery();
But it crashes on the first "ExecuteNonQuery" with the exception "A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back."
But it's the FIRST query executed!