6

Accessデータベースからテーブルを読み込んでから、そのテーブルのデータをいくつかのテキストファイルに並べ替えようとしています。クリンチャーは、書き込むファイル名が各レコードの値に依存することです。これは正式に私の最初のC#アプリケーションであるため、私を「グリーン」と見なすことができます。また、コードを打ち出すことができるようになるまで、Accessデータベースで作業していることにも言及する必要があります。最終的には、数百万のレコードを持つSQLサーバーからプルされます。

私は今コードを動作させていますが、問題はファイルのオープン/クローズ操作がたくさんあることです。これらのファイルはネットワークドライブに書き込まれるため、書き込み用に各ファイルを1回だけ開きます。これは基本的にサーバー上で実行される接着剤アプリです-したがって、他にもいくつかの制限があります-ローカルドライブに保存してからネットワークにコピーすることはできません。プルする前にクエリを並べ替えることができません。実行中にサーバーリソースに悪影響を与えることはできません。

おそらくこれを行うための最良の方法は、ハッシュテーブルを使用することです。ファイルが開かれているかどうかを確認し、開かれていない場合は、ファイルを開いてファイルハンドルをハッシュテーブルに保存します。その後、終了したら一度にすべて閉じます。ただし、複数のStreamWriterオブジェクトを同時に使用する方法の例を見つけることができません。

私はこれに対する答えを比較的簡単に見つけることを期待していましたが、彼の解決策を見つけることができないようです。私の疑いは、StreamWriterがこれに使用するのに間違ったクラスであるということです。

私が見つけた最も近い前の質問は、CodeProjectページからのものです。そのページでは、ファイルの手を開いたままにしておくことは悪いことであり、避けるべきであると彼らは言っていますが、そのページは理由を説明しておらず、代替例も提供していません。データセット全体をメモリにロードしてから操作することをお勧めしますが、テーブルにデータが多すぎるため、これはオプションではありません。

これが私がこれまでに持っているものです。

String strConnection;
String strQuery;
String strPunchFileNameTemplate;

// Define our Variables
strConnection = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=ClockData.accdb";
strQuery = @"SELECT * FROM ClockPunches";   
strPunchFileNameTemplate = @"C:\PUNCHES\%READER%.TXT";      

// OleDbConnection implements iDisposable interface, so we must scope out its usage.
// Set up Connection to our data source
using (OleDbConnection ConnObj = new OleDbConnection(strConnection))    {

    // Create a Command with our Query String
    OleDbCommand CmdObj = new OleDbCommand(strQuery,ConnObj);

    // Open our Connection
    ConnObj.Open();

    // OledbDataReader implements iDisposable interface, so we must scope out its usage.
    // Execute our Reader
    using (OleDbDataReader ReaderObj = CmdObj.ExecuteReader(CommandBehavior.KeyInfo))   {

        // Load the source table's schema into memory (a DataTable object)
        DataTable TableObj = ReaderObj.GetSchemaTable();

        // Parse through each record in the Reader Object
        while(ReaderObj.Read()) {

            // Extract PunchTime, CardNumber, and Device to separate variables
            DateTime dtTime = ReaderObj.GetDateTime(ReaderObj.GetOrdinal("PunchTime"));
            Int16 intID = ReaderObj.GetInt16(ReaderObj.GetOrdinal("CardNumber"));
            String strReader = ReaderObj.GetString(ReaderObj.GetOrdinal("Device"));

            // Translate the device name into a designated filename (external function)
            strReader = GetDeviceFileName(strReader);

            // Put our dynamic filename into the path template
            String pathStr = strPunchFileNameTemplate.Replace("%READER%",strReader);

            // Check to see if the file exists.  New files need an import Header
            Boolean FileExistedBool = File.Exists(pathStr);

            // StreamWrite implements iDisposable interface, so we must scope out its usage.
            // Create a Text File for each Device, Append if it exists
            using (StreamWriter outSR = new StreamWriter(pathStr, true))    {

                // Write our Header if required
                if (FileExistedBool == false)   {
                    outSR.WriteLine("EXAMPLE FILE HEADER");
                }

                // Set up our string we wish to write to the file
                String outputStr = dtTime.ToString("MM-dd-yyyy HH:mm:ss") + " " + intID.ToString("000000");

                // Write the String
                outSR.WriteLine(outputStr);

                // End of StreamWriter Scope - should automatically close
            }
        }
        // End of OleDbDataREader Scope - should automatically close
    }
    // End of OleDbConnection Scope - should automatically close
}
4

4 に答える 4

2

それはあなた自身が陥った非常に興味深い問題です。

ファイル ハンドラーのキャッシュの問題は、膨大な量のファイル ハンドラーによってシステムのリソースが枯渇し、プログラムやウィンドウのパフォーマンスが低下することです。

データベース内のデバイスの数が多すぎない (100 未満) 場合は、ハンドルをキャッシュしても安全だと思います。

別の方法として、100 万件のレコードをキャッシュしてさまざまなデバイスに配布し、一部を保存してからさらにレコードを読み取ることもできます。

次のように、レコードを Dictionary に配置できます。

class PunchInfo
{  
    public PunchInfo(DateTime time, int id)
    {
        Id = id;
        Time = time;
    }
    public DateTime Time;
    public int Id;
}

Dictionary<string, List<PunchInfo>> Devices;
int Count = 0;
const int Limit = 1000000;
const int LowerLimit = 90 * Limit / 100;
void SaveRecord(string device, int id, DateTime time)
{
   PunchInfo info = new PunchInfo(time, id);
   List<PunchInfo> list;
   if (!Devices.TryGetValue(device, out list))
   {
      list = new List<PunchInfo>();
      Devices.Add(device, list);
   }
   list.Add(info);
   Count++;
   if (Count >= Limit)
   {
       List<string> writeDevices = new List<string>();
       foreach(KeyValuePair<string, List<PunchInfo>> item in Devices)
       {
           writeDevices.Add(item.Key);
           Count -= item.Value.Count;
           if (Count < LowerLimit) break;
       }

       foreach(string device in writeDevices)
       {
          List<PunchInfo> list = Devices[device];
          Devices.Remove(device);
          SaveDevices(device, list);
       }
    }
}

void SaveAllDevices()
{
    foreach(KeyValuePair<string, List<PunchInfo>> item in Devices)
        SaveDevices(item.Key, item.Value);
    Devices.Clear();
}

このようにして、ファイルを開いたり閉じたりすることを避け、多くのファイルを開いておくことができます。

100 万件のレコードは 20 MB のメモリを消費しますが、問題なく 1000 万件まで簡単に増やすことができます。

于 2013-03-19T20:41:00.430 に答える
1

ライターの配列を設定する必要があります。これは、その方法の例です。

namespace example
{
    class Program
    {
    public static StreamWriter[] writer = new StreamWriter[3];

    static void Main(string[] args)
    {
        writer[0] = new StreamWriter("YourFile1.txt");
        writer[1] = new StreamWriter("YourFile2.txt");
        writer[2] = new StreamWriter("YourFile3.txt");

        writer[0].WriteLine("Line in YourFile1.");
        writer[1].WriteLine("Line in YourFile2.");
        writer[2].WriteLine("Line in YourFile3.");

        writer[0].Close();
        writer[1].Close();
        writer[2].Close();
    }
}

}

于 2016-08-18T01:46:33.100 に答える
0

データをメモリに保持し、特定のスリーソルドに達した場合にのみディスクに書き込むことをお勧めします

const int MAX_MEMORY_BUFFER = 100000; // To be defined according to you memory limits
String strConnection;
String strQuery;
String strPunchFileNameTemplate;

strConnection = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=ClockData.accdb";
strQuery = @"SELECT * FROM ClockPunches";   
strPunchFileNameTemplate = @"C:\PUNCHES\%READER%.TXT";      

Dictionary<string, StringBuilder> data = new Dictionary<string, StringBuilder>();

using (OleDbConnection ConnObj = new OleDbConnection(strConnection))    
{
    OleDbCommand CmdObj = new OleDbCommand(strQuery,ConnObj);
    ConnObj.Open();

    using (OleDbDataReader ReaderObj = CmdObj.ExecuteReader(CommandBehavior.KeyInfo))   
    {
        while(ReaderObj.Read()) 
        {
            DateTime dtTime = ReaderObj.GetDateTime(ReaderObj.GetOrdinal("PunchTime"));
            Int16 intID = ReaderObj.GetInt16(ReaderObj.GetOrdinal("CardNumber"));
            String strReader = ReaderObj.GetString(ReaderObj.GetOrdinal("Device"));

            strReader = GetDeviceFileName(strReader);

            bool dataPresent = data.ContainsKey(strReader);
            if (dataPresent == false)   
            {
                StringBuilder sb = new StringBuilder("EXAMPLE FILE HEADER\r\n");
                data.Add(strReader, sb);
            }

            String outputStr = dtTime.ToString("MM-dd-yyyy HH:mm:ss") + " " + intID.ToString("000000");
            StringBuilder sb = data[strReader];
            sb.AppendLine(outputStr);
            if(sb.Length > MAX_MEMORY_BUFFER)
            {
                String pathStr = strPunchFileNameTemplate.Replace("%READER%",strReader);
                using(StreamWriter sw = new StremWriter(pathStr, true) // Append mode
                {
                    // Write the buffer and set the lenght to zero
                    sw.WriteLine(sb.ToString());
                    sb.Length = 0;
                }
            }
        }
    }

    // Write all the data remaining in memory
    foreach(KeyValuePair<string, StringBuilder> info in data)
    {
        if(info.Value.Length > 0)
        {
          String pathStr = strPunchFileNameTemplate.Replace("%READER%",info.Key);
          using(StreamWriter sw = new StremWriter(pathStr, true) // Append mode
          {
              sw.WriteLine(info.Value.ToString());
          }
        }
    }
}

このコードはテストする必要がありますが、一般的な考え方をお伝えしたいと思います。このようにして、IO 操作のバランスをとることができます。メモリバッファを増やすことで下げる、またはその逆。もちろん、データの保存に使用できるメモリも考慮する必要があります。

于 2013-03-19T20:23:19.670 に答える
0

1 つのプロセスが 100 個または 1000 個のファイル ハンドルを長期間にわたって開いている場合、これは一般的に疑わしいと見なされます。しかし、時代は変わり、これはもはや問題ではありません。ですから、状況がそれを要求するなら、それを実行してください。

私は、これらのファイルのデータを分析するプロセスで、100 個、1000 個、さらには 5000 個のファイルを開いたままにしておく立場にありました。そして、これは何時間も続くでしょう。ファイルの読み書きのパフォーマンスが低下するかどうかを Windows OS で測定しました。そして、これはまったく当てはまりません。最新のマシンのメモリ リソースが利用できるようになったため、OS 側のメモリに 5000 個のファイル記述子を保持しても問題は発生しなくなりました。OSはそれらをソートし続け(推測します)、これらの記述子のルックアップはlog(n)であるため、測定可能なものは何も起こりません。

これらのハンドル (ファイル記述子の構造体) を開いておく方が、メモリをデータでいっぱいにしてから、ファイルごとにディスクにフラッシュするよりも確実に優れています。

于 2013-03-19T20:39:43.850 に答える