65.9K
CodeProject 正在变化。 阅读更多。
Home

C# SQL 客户端的大文本统一接口

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2009年7月26日

CPOL

2分钟阅读

viewsIcon

24234

downloadIcon

1

本文为 C# SqlClient 和文件流中处理大型文本提供了一个简单的接口。

引言

我遇到的问题是:我们有文本文件和文本 blob。文件在 C# 中通过 TextReaderTextWriter 处理。文件的内容也应该存储在 SQL Server 数据库中。我们也有相反的任务:从 SQL 读取并放入文件。移动数据的主要思想很简单:从源获取一块文本作为 char[BUFFER_SIZE] 并将其放入目标。 SQL 和 C# 接口都支持这个(见下图)。

CharsHandler1.png

我们还可能希望将实现从 SQL 存储更改为文件存储。因此,我们提出了一个统一的接口,并提供了它的两种实现:一种用于 C# 文本流,另一种用于 SQL Server 数据库。

接口本身是

public interface ICharsHandler { 
      char[]  GetChars(long offset, int length);  // read data chunk 
      void PutChars(long offset, char[] buffer); // put data chunk
      void Close(); // release the resources (recordset, connection or stream)
}

文本写入器的实现如下

public class StreamTextHandler : ICharsHandler
{
    TextReader reader;
    TextWriter writer;
    char[] buffer;

    public StreamTextHandler(TextWriter wr, TextReader rd)
    {
        reader = rd;
        writer = wr;
    }

    #region ICharsHandler Members

    public char[] GetChars(long offset, int length)
    {
        if (reader == null)
            throw new InvalidOperationException("Can't read data");
        if (buffer == null || buffer.Length != length)
            buffer = new char[length];
        int cnt = reader.Read(buffer, (int) offset, length);
        if (cnt < length)
        {
            char[] nv = new char[cnt];
            Array.Copy(buffer, nv, cnt);
            return nv;
        }
        return buffer;
    }

    public void PutChars(long offset, char[] buffer)
    {
        if (writer == null)
            throw new InvalidOperationException("Can't write data");
        writer.Write(buffer, (int) offset, buffer.Length);
    }

    public void Close()
    {
        if (reader != null) reader.Close();
        if (writer != null) writer.Close();
    }

    #endregion
}

也许,将此接口拆分为 "reader" 和 "writer" 会是个好主意。

Microsoft SqlClient 的实现如下

class SqlTextHandler : ICharsHandler
{
    SqlCommand readCommand;
    SqlCommand writeCommand;
    int column;
    SqlDataReader rd;
    bool previousConn = false;
    
    public SqlTextHandler(SqlCommand cmd, SqlCommand wr, int _column)
    {
        readCommand = cmd;
        writeCommand = wr;
        column = _column;
        previousConn = (wr != null) ? 
        wr.Connection.State == ConnectionState.Open: 
             cmd.Connection.State == ConnectionState.Open;
    }

    protected void OpenReader()
    {
        readCommand.Connection.Open();
        rd = readCommand.ExecuteReader(CommandBehavior.SequentialAccess | 
                                       CommandBehavior.SingleRow);
        rd.Read();
    }
    // We assume that the input command
    // contain variables: @Value, @Offset and @Length
    protected void OpenWriter()
    {
        SqlParameter Out = 
          writeCommand.Parameters.Add("@Value", SqlDbType.NVarChar);
        SqlParameter OffsetParam = 
          writeCommand.Parameters.Add("@Offset", SqlDbType.BigInt);
        SqlParameter LengthParam = 
          writeCommand.Parameters.Add("@Length", SqlDbType.Int);
        writeCommand.Connection.Open();
    }

    char[] buffer;

    #region ICharsHandler Members

    public char[] GetChars(long offset, int length)
    {
        if (rd == null) OpenReader();
        if (buffer == null || buffer.Length != length)
        {
            buffer = new char[length];
        }
        long cnt = rd.GetChars(column, offset, buffer, 0, length);
        if (cnt < length)
        {
            char[] nv = new char[cnt];
            Array.Copy(buffer, nv, cnt);
            return nv;
        }
        return buffer;
    }

    public void PutChars(long offset, char[] buffer)
    {
        if (writeCommand.Parameters.Count < 4) OpenWriter();
        writeCommand.Parameters["@Length"].Value = buffer.Length;
        writeCommand.Parameters["@Value"].Value = buffer;
        writeCommand.Parameters["@Offset"].Value = offset;
        writeCommand.ExecuteNonQuery();
    }

    public void Close()
    {
        if (rd != null) rd.Close();
        if (!previousConn)
        {
            if (readCommand != null) readCommand.Connection.Close();
            if (writeCommand != null) writeCommand.Connection.Close();
        }
    }

    #endregion
}

我们提供了两个 SQL 命令,cmdReader 用于读取文本,cmdWriter 用于写入文本。

下面的代码显示了 SqlTextHandler 的输入参数示例。更新 T-SQL 命令使用 .WRITE 子句。 两个 SQL 语句在下面的示例中都已加粗

public ICharsHandler GetTextHandler(long id)
{
    SqlConnection _connection = new System.Data.SqlClient.SqlConnection();
    _connection.ConnectionString = 
       MyApp.Properties.Settings.Default.MyAppConnectionString;

    SqlCommand cmdWriter = new SqlCommand("UPDATE dbo.MessageUnit" + 
         " SET plainText .WRITE (@Value, @Offset, @Length) WHERE id = @id ", 
         _connection);
    cmdWriter.Parameters.Add(new SqlParameter("@id", id));
    SqlCommand cmdReader = new SqlCommand(
         "SELECT  plainText FROM dbo.MessageUnit WHERE id = @id",
         _connection);
    cmdReader.Parameters.Add(new SqlParameter("@id", id));
    return new SqlTextHandler(cmdReader, cmdWriter, 0);
}

另一种实现可以基于 UPDATETEXT SQL 命令,但它已被宣布在未来版本的 SQL Server 中过时。

应该提到两个可能的要求

  • 使用正确的 SQL 表列类型 nvarchar(MAX)varchar(MAX)。 否则,SQL Server 会报告一个错误操作。
  • 该列的值应该被初始化(作为空字符串)。 如果初始值为 null,则 PutChars 操作也会失败。

用法示例代码可能如下所示

void MoveText(ICharHandler source, ICharHandler target)
{
    long offset = 0;
    for (; ; )
    {
        char[] buffer = source.GetChars(offset, BUFFER_SIZE);
        ptext.PutChars(offset, buffer);
        if (buffer.Length < BUFFER_SIZE) break;
        offset += BUFFER_SIZE;
    }
}

最后的说明是

  • 一旦我们有了两个处理程序,我们就可以将它们组合成一个处理程序,这样一次 PutChars 操作将写入两个逻辑流。
  • 同样的想法可以很容易地应用于二进制数据。到目前为止,我们将使用 byte[] 缓冲区,而不是 char[] 缓冲区,并且我们将处理 C# 二进制流,而不是文本流。
© . All rights reserved.