テーブルに約1000万行あるレガシーシステムがあります。そのテーブルにはtext
タイプの列があり、それらのほとんどは標準テキストですが、約50万行にRTFマークアップがあります。 RTF形式のテキストをプレーンテキストに変換する必要があります。
私の現在の方法は、SqlDataAdapter
を使用してDataTableにクエリを読み込み、winforms RichTextBox
コントロールを使用して変換を行うC#プログラムです。
void bw_DoWork(object sender, DoWorkEventArgs e)
{
count = 0;
rtbRTFToPlain = new RichTextBox();
using (SqlDataAdapter ada = new SqlDataAdapter("select note_guid, notes from client_notes", Globals.SQLConnectionString))
using(SqlCommandBuilder cmb = new SqlCommandBuilder(ada))
{
DataTable dt = new DataTable();
ada.UpdateCommand = cmb.GetUpdateCommand();
ada.Fill(dt);
int reportEvery = dt.Rows.Count / 100;
if (reportEvery == 0)
reportEvery = 1;
foreach (DataRow row in dt.Rows)
{
if (count % reportEvery == 0)
bw.ReportProgress(count / reportEvery);
try
{
if (((string)row["notes"]).TrimStart().StartsWith("{") == true)
{
rtbRTFToPlain.Rtf = (string)row["notes"];
row["notes"] = rtbRTFToPlain.Text;
}
}
catch
{
}
count++;
}
bw.ReportProgress(100);
this.Invoke(new Action(() =>
{
this.ControlBox = false;
this.Text = "Updating database please wait";
}));
ada.Update(dt);
}
}
これは小さなテーブルでうまく機能しますが、これがこのような大きなデータセット(一部のrtfファイルは埋め込み画像で数メガバイトになる可能性がある)を含むテーブルで実行する必要があったのはこれが初めてであり、OutOfMemoryを取得していますC#プログラムのエラー。
クエリをより小さなバッチに分割できることはわかっていますが、RTFの書式設定を取り除くために欠けていたより良い方法があるかどうかを確認したいと思いました。
現在のソリューションと同じことをする必要がありますが、一度にデータの小さなチャンクのみをクエリする必要がありますか、これを行うより良い方法はありますか?
変換するCLR関数を作成してしまいました。
私は this library を見つけ、それを微調整して、ロギングや描画メソッドなどの不要なものを削除しました。これにより、安全であるとマークすることができました。
それから私はこの小さなクラスを作りました。
using System.Data.SqlTypes;
using Itenso.Rtf.Converter.Text;
using Itenso.Rtf.Support;
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString RtfToPlainText(SqlString text)
{
if (text.Value.StartsWith(@"{\rtf"))
{
RtfTextConverter textConverter = new RtfTextConverter();
RtfInterpreterTool.Interpret(text.Value, textConverter);
return textConverter.PlainText;
}
else
return text;
}
}
そしてこれをSQLで実行しました
sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
CREATE Assembly ConversionsSqlExtensionsAssembly
from 'E:\Code\ConversionsSqlExtensions\bin\Debug\ConversionsSqlExtensions.dll'
WITH PERMISSION_SET = safe
go
CREATE function RtfToPlainText(@value nvarchar(max))
returns nvarchar(max)
AS EXTERNAL NAME ConversionsSqlExtensionsAssembly.StoredProcedures.RtfToPlainText
そして、それは高速で素晴らしい働きをします!
Itenso RTF DLLを使用してScott Chamberlainと同じことを行いましたが、私の場合、SQL 2008R2データベースでこれをSAFEとしてマークする前に、実行する必要のあった作業がさらに多くありました。 。
まず、Scottと同様に、System.Drawingへの参照を削除する必要がありました。そのための最も簡単な方法は、参照を削除し、再コンパイルしてから、ライブラリを使用していたコードの一部を書き直すことでした。ほとんどの場合、それを使用していたVOID関数からすべてのコードを削除しただけで、Drawing/Colorオブジェクトを単に「オブジェクト」オブジェクトに変更できなかった場合。
私がしなければならないもう1つのことは、安全とマークすることもできないライブラリSystem.DirectoryServicesを参照するため、log4netへのすべての参照を削除することでした。これは少し難しいですが、一般的に私は同じアプローチをとりました。
最後に、それを行った後、SAFE CLR関数では許可されていない静的な値の設定について苦情がありました。したがって、コードを更新してすべてのStatic値をREADONLYに変更し、それが機能しました(これは、コードの「ロギング」セクションのほとんどすべてで、私はまったく気にしませんでした)。
私の最終的なCLRコードは次のようになりました。
[Microsoft.SqlServer.Server.SqlFunction]
[return: SqlFacet(MaxSize=-1)]
public static SqlChars RTFFix([SqlFacet(MaxSize=-1)]string rtfField)
{
SqlChars returnChars;
try
{
RtfTextConverter textConverter = new RtfTextConverter();
RtfInterpreterTool.Interpret(rtfField, textConverter);
returnChars = new SqlChars(new SqlString(textConverter.PlainText.Trim()));
}
catch (Exception e)
{
returnChars = new SqlChars(new SqlString(rtfField));
}
return returnChars;
}
DataTableの代わりにDataReaderを使用すると、行をすべてメモリにロードする代わりに、一度に1行ずつ処理できます。それはあなたのメモリ不足エラーをバイパスするはずです。
マークアップっぽい文字列からテキストをスクレイピングする小さなSQL関数を作成しました: http://cookingwithsql.com/index.php?option=com_content&task=view&id=65&Itemid=6
残念ながら、最大8000文字の文字列データのみを処理します。おそらく、SQL 2005以降で実行している場合は、varchar(max)を使用するように変更できます。
使用法:
select dbo.ScrapeText('<I love SQL> gobbldygook font 12 blah blah') as 'Result'
Result
----------------------------
I love SQL
クイックリファレンスとして、ここにソースを掲載します。
use master
IF (object_id('dbo.ScrapeText') IS NOT NULL)
BEGIN
PRINT 'Dropping: dbo.ScrapeText'
DROP function dbo.ScrapeText
END
GO
PRINT 'Creating: dbo.ScrapeText'
GO
CREATE FUNCTION dbo.ScrapeText
(
@string varchar(8000)
)
returns varchar(8000)
AS
BEGIN
---------------------------------------------------------------------------------------------------
-- Title: ScrapeText
--
-- Date Created: April 4, 2006
--
-- Author: William McEvoy
--
-- Description: This function will attempt to remove markup language formatting from a string. This is
-- accomplished by concetenating all text contained between greater than and less
-- than signs within the formatted text.
--
---------------------------------------------------------------------------------------------------
-- Date Revised:
-- Author:
-- Reason:
---------------------------------------------------------------------------------------------------
declare @text varchar(8000),
@PenDown char(1),
@char char(1),
@len int,
@count int
select @count = 0,
@len = 0,
@text = ''
---------------------------------------------------------------------------------------------------
-- M A I N P R O C E S S I N G
---------------------------------------------------------------------------------------------------
-- Add tokens
select @string = '>' + @string + '<'
-- Replace Special Characters
select @string = replace(@string,' ',' ')
-- Parse out the formatting codes
select @len = len(@string)
while (@count <= @len)
begin
select @char = substring(@string,@count,1)
if (@char = '>')
select @PenDown = 'Y'
else
if (@char = '<')
select @PenDown = 'N'
else
if (@PenDown = 'Y')
select @text = @text + @char
select @count = @count + 1
end
RETURN @text
END
GO
IF (object_id('dbo.ScrapeText') IS NOT NULL)
PRINT 'Function created.'
ELSE
PRINT 'Function NOT created.'
GO