C# SQL 客户端的大文本统一接口
本文为 C# SqlClient 和文件流中处理大型文本提供了一个简单的接口。
引言
我遇到的问题是:我们有文本文件和文本 blob。文件在 C# 中通过 TextReader
和 TextWriter
处理。文件的内容也应该存储在 SQL Server 数据库中。我们也有相反的任务:从 SQL 读取并放入文件。移动数据的主要思想很简单:从源获取一块文本作为 char[BUFFER_SIZE]
并将其放入目标。 SQL 和 C# 接口都支持这个(见下图)。
我们还可能希望将实现从 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# 二进制流,而不是文本流。