一尘不染

使用SqlFileStream从WCF服务返回Stream

sql

我有一个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; }
    }
}

阅读 140

收藏
2021-03-17

共1个答案

一尘不染

通常,我可能建议将流包装在一个自定义流中,该流在处理后会关闭事务,但是IIRC
WCF不保证哪个线程会做什么,而是TransactionScope特定于线程的。这样,也许更好的选择是将数据复制到MemoryStream(如果不是太大)并返回。Stream.Copy4.0中的方法应该轻而易举,但是请记住在final
return.Position = 0)之前倒回内存流。

显然,这将是一个很大的问题,如果流是很大的,......但是,如果流是足够大的 引起人们的关注,那 个人
我会在它在运行感到关切的TransactionScope 根本 ,因为具有内置的时间限制,并导致可序列化的隔离(默认情况下)。

最后的建议是使用SqlTransaction,它与线程无关。您可以编写一个Stream位于周围的包装器SqlFileStream,然后关闭中的阅读器,事务和连接(以及包装的流)Dispose()。WCF将Close()在处理结果后(通过)进行调用。

2021-03-17