我有一个WCF服务,用户可以从中请求大数据文件(存储在启用FileStream的SQL数据库中)。这些文件应该流传输,并且在发送出去之前不加载到内存中。
因此,我有以下方法应返回WCF服务调用的流,以便它可以将Stream返回给客户端。
public static Stream GetData(string tableName, string columnName, string primaryKeyName, Guid primaryKey) { string sqlQuery = String.Format( "SELECT {0}.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() FROM {1} WHERE {2} = @primaryKey", columnName, tableName, primaryKeyName); SqlFileStream stream; using (TransactionScope transactionScope = new TransactionScope()) { byte[] serverTransactionContext; string serverPath; using (SqlConnection sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnString"].ToString())) { sqlConnection.Open(); using (SqlCommand sqlCommand = new SqlCommand(sqlQuery, sqlConnection)) { sqlCommand.Parameters.Add("@primaryKey", SqlDbType.UniqueIdentifier).Value = primaryKey; using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader()) { sqlDataReader.Read(); serverPath = sqlDataReader.GetSqlString(0).Value; serverTransactionContext = sqlDataReader.GetSqlBinary(1).Value; sqlDataReader.Close(); } } } stream = new SqlFileStream(serverPath, serverTransactionContext, FileAccess.Read); transactionScope.Complete(); } return stream; }
我的问题是与TransactionScope和SqlConnection。我现在的操作方式不起作用,我收到一个TransactionAbortedException,说“事务已中止”。我可以在返回Stream之前关闭事务和连接吗?任何帮助表示赞赏,谢谢
编辑:
我为SqlFileStream创建了一个包装器,该包装器实现了IDisposable,以便一旦处理完流,就可以关闭所有内容。似乎工作正常
public class WcfStream : Stream { private readonly SqlConnection sqlConnection; private readonly SqlDataReader sqlDataReader; private readonly SqlTransaction sqlTransaction; private readonly SqlFileStream sqlFileStream; public WcfStream(string connectionString, string columnName, string tableName, string primaryKeyName, Guid primaryKey) { string sqlQuery = String.Format( "SELECT {0}.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT() FROM {1} WHERE {2} = @primaryKey", columnName, tableName, primaryKeyName); sqlConnection = new SqlConnection(connectionString); sqlConnection.Open(); sqlTransaction = sqlConnection.BeginTransaction(); using (SqlCommand sqlCommand = new SqlCommand(sqlQuery, sqlConnection, sqlTransaction)) { sqlCommand.Parameters.Add("@primaryKey", SqlDbType.UniqueIdentifier).Value = primaryKey; sqlDataReader = sqlCommand.ExecuteReader(); } sqlDataReader.Read(); string serverPath = sqlDataReader.GetSqlString(0).Value; byte[] serverTransactionContext = sqlDataReader.GetSqlBinary(1).Value; sqlFileStream = new SqlFileStream(serverPath, serverTransactionContext, FileAccess.Read); } protected override void Dispose(bool disposing) { sqlDataReader.Close(); sqlFileStream.Close(); sqlConnection.Close(); } public override void Flush() { sqlFileStream.Flush(); } public override long Seek(long offset, SeekOrigin origin) { return sqlFileStream.Seek(offset, origin); } public override void SetLength(long value) { sqlFileStream.SetLength(value); } public override int Read(byte[] buffer, int offset, int count) { return sqlFileStream.Read(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) { sqlFileStream.Write(buffer, offset, count); } public override bool CanRead { get { return sqlFileStream.CanRead; } } public override bool CanSeek { get { return sqlFileStream.CanSeek; } } public override bool CanWrite { get { return sqlFileStream.CanWrite; } } public override long Length { get { return sqlFileStream.Length; } } public override long Position { get { return sqlFileStream.Position; } set { sqlFileStream.Position = value; } } }
通常,我可能建议将流包装在一个自定义流中,该流在处理后会关闭事务,但是IIRC WCF不保证哪个线程会做什么,而是TransactionScope特定于线程的。这样,也许更好的选择是将数据复制到MemoryStream(如果不是太大)并返回。Stream.Copy4.0中的方法应该轻而易举,但是请记住在final return(.Position = 0)之前倒回内存流。
TransactionScope
MemoryStream
Stream.Copy
return
.Position = 0
显然,这将是一个很大的问题,如果流是很大的,......但是,如果流是足够大的 是 引起人们的关注,那 个人 我会在它在运行感到关切的TransactionScope 根本 ,因为具有内置的时间限制,并导致可序列化的隔离(默认情况下)。
最后的建议是使用SqlTransaction,它与线程无关。您可以编写一个Stream位于周围的包装器SqlFileStream,然后关闭中的阅读器,事务和连接(以及包装的流)Dispose()。WCF将Close()在处理结果后(通过)进行调用。
SqlTransaction
Stream
SqlFileStream
Dispose()
Close()