79

ユーザーデータを取得し、後で使用するためにローカルに保存するアプリケーションを作成しています。アプリケーションはかなり頻繁に開始および停止されるため、アプリケーションの開始/終了時にデータを保存/ロードしたいと考えています。

フラット ファイルを使用した場合は、データを保護する必要がないため (この PC にのみ保存されます)、かなり簡単です。したがって、私が信じるオプションは次のとおりです。

  • フラットファイル
  • XML
  • SQL データベース

フラット ファイルを維持するにはもう少し手間がかかりますが (XML のような組み込みクラスはありません)、XML を使用したことがなく、この比較的簡単な作業には SQL がやり過ぎのように思えます。

他に検討する価値のある方法はありますか? そうでない場合、これらのうちどれが最善の解決策ですか?


編集:問題にもう少しデータを追加するために、基本的に保存したいのは、このような辞書だけです

Dictionary<string, List<Account>> 

ここで、Account は別のカスタム タイプです。

dict を xmlroot としてシリアライズしてから、Account タイプを属性としてシリアライズしますか?


更新 2:

したがって、辞書をシリアライズすることは可能です。複雑なのは、この dict の値が Account 型の複雑なデータ構造のリストであるジェネリック自体であることです。各アカウントは非常に単純で、単なるプロパティの集まりです。

ここでの目標は、これを試して最終的に達成することであると私は理解しています。

<Username1>
    <Account1>
        <Data1>data1</Data1>
        <Data2>data2</Data2>
    </Account1>
</Username1>
<Username2>
    <Account1>
        <Data1>data1</Data1>
        <Data2>data2</Data2>
    </Account1>
    <Account2>
        <Data1>data1</Data1>
        <Data2>data2</Data2>
    </Account2>
 </Username2>

ご覧のとおり、階層は

  • ユーザー名 (dict の文字列) >
  • アカウント (リスト内の各アカウント) >
  • アカウント データ (つまり、クラス プロパティ)。

このレイアウトを a から取得するのDictionary<Username, List<Account>>はややこしい点であり、この質問の本質です。

シリアル化に関する「ハウツー」の回答がたくさんあります。これは、早い段階で明確にしなかった私のせいですが、今は明確な解決策を探しています.

4

19 に答える 19

28

ファイルをJSONとして保存します。名前と値のペアのリストである辞書を保存しているので、これはほとんど json が設計されたものです。
まともな無料の .NET json ライブラリがかなりあります。これは1 つですが、最初のリンクで完全なリストを見つけることができます。

于 2009-12-21T20:19:27.500 に答える
24

それは本当にあなたが保管しているものに依存します。構造化データについて話している場合は、XML か、SQLite や SQL Server Compact Edition などの非常に軽量な SQL RDBMS が適しています。データが些細なサイズを超えて移動する場合、SQL ソリューションは特に魅力的になります。

比較的構造化されていない大量のデータ (たとえば、画像などのバイナリ オブジェクト) を格納している場合は、明らかにデータベースも XML ソリューションも適切ではありませんが、あなたの質問を考えると、後者よりも前者の方が多いと思います。

于 2009-12-21T19:03:25.097 に答える
18

上記はすべて良い答えであり、一般的に問題を解決します。

何百万ものデータにスケールする簡単で無料の方法が必要な場合は、 GitHubまたはNuGetの ESENT マネージド インターフェイス プロジェクトを試してください。

ESENT は、Windows の一部である埋め込み型データベース ストレージ エンジン (ISAM) です。行レベルのロック、先行書き込みロギング、およびスナップショット分離を使用して、信頼性が高く、トランザクション化された、同時実行の高性能データ ストレージを提供します。これは、ESENT Win32 API のマネージ ラッパーです。

非常に使いやすい PersistentDictionary オブジェクトがあります。これは Dictionary() オブジェクトと考えてください。ただし、追加のコードなしで自動的にディスクからロードおよびディスクに保存されます。

例えば:

/// <summary>
/// Ask the user for their first name and see if we remember 
/// their last name.
/// </summary>
public static void Main()
{
    PersistentDictionary<string, string> dictionary = new PersistentDictionary<string, string>("Names");
    Console.WriteLine("What is your first name?");
    string firstName = Console.ReadLine();
    if (dictionary.ContainsKey(firstName))
    {
        Console.WriteLine("Welcome back {0} {1}", firstName, dictionary[firstName]);
    }
    else
    {
        Console.WriteLine("I don't know you, {0}. What is your last name?", firstName);
        dictionary[firstName] = Console.ReadLine();
    }

ジョージの質問に答えるには:

サポートされている鍵の種類

次のタイプのみがディクショナリ キーとしてサポートされています。

Boolean Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 Float Double Guid DateTime TimeSpan 文字列

サポートされている値のタイプ

ディクショナリの値は、キーの種類、キーの種類の Nullable バージョン、Uri、IPAddress、またはシリアル化可能な構造のいずれかです。構造体は、次のすべての基準を満たしている場合にのみシリアライズ可能と見なされます。

• 構造体がシリアライズ可能としてマークされている • 構造体のすべてのメンバーは次のいずれかです。

別の言い方をすれば、直列化可能な構造には、クラス オブジェクトへの参照を含めることはできません。これは、API の一貫性を維持するために行われます。オブジェクトを PersistentDictionary に追加すると、シリアル化によってオブジェクトのコピーが作成されます。元のオブジェクトを変更してもコピーは変更されないため、動作が混乱する可能性があります。これらの問題を回避するために、PersistentDictionary は値型のみを値として受け入れます。

シリアライズ可能 [Serializable] struct Good { public DateTime? 受け取った; 公開文字列名; 公開小数価格; パブリック Uri URL; }

シリアル化できません[Serializable] struct Bad { public byte[] Data; // 配列はサポートされていません public Exception Error; // 参照オブジェクト }

于 2009-12-21T19:41:56.550 に答える
15

XML はシリアライゼーションを介して簡単に使用できます。分離ストレージを使用します。

ユーザーごとの状態を保存する場所を決定する方法も参照してください。レジストリ?アプリデータ?分離ストレージ?

public class UserDB 
{
    // actual data to be preserved for each user
    public int A; 
    public string Z; 

    // metadata        
    public DateTime LastSaved;
    public int eon;

    private string dbpath; 

    public static UserDB Load(string path)
    {
        UserDB udb;
        try
        {
            System.Xml.Serialization.XmlSerializer s=new System.Xml.Serialization.XmlSerializer(typeof(UserDB));
            using(System.IO.StreamReader reader= System.IO.File.OpenText(path))
            {
                udb= (UserDB) s.Deserialize(reader);
            }
        }
        catch
        {
            udb= new UserDB();
        }
        udb.dbpath= path; 

        return udb;
    }


    public void Save()
    {
        LastSaved= System.DateTime.Now;
        eon++;
        var s= new System.Xml.Serialization.XmlSerializer(typeof(UserDB));
        var ns= new System.Xml.Serialization.XmlSerializerNamespaces();
        ns.Add( "", "");
        System.IO.StreamWriter writer= System.IO.File.CreateText(dbpath);
        s.Serialize(writer, this, ns);
        writer.Close();
    }
}
于 2009-12-21T19:02:54.400 に答える
9

コレクションが大きくなりすぎると、Xml のシリアル化が非常に遅くなることがわかりました。ディクショナリをシリアル化する別のオプションは、BinaryReader と BinaryWriter を使用して「独自にロールする」ことです。

開始するためのサンプル コードを次に示します。これらの一般的な拡張メソッドを作成して、任意のタイプのディクショナリを処理できます。これは非常にうまく機能しますが、ここに投稿するには冗長すぎます。

class Account
{
    public string AccountName { get; set; }
    public int AccountNumber { get; set; }

    internal void Serialize(BinaryWriter bw)
    {
        // Add logic to serialize everything you need here
        // Keep in synch with Deserialize
        bw.Write(AccountName);
        bw.Write(AccountNumber);
    }

    internal void Deserialize(BinaryReader br)
    {
        // Add logic to deserialize everythin you need here, 
        // Keep in synch with Serialize
        AccountName = br.ReadString();
        AccountNumber = br.ReadInt32();
    }
}


class Program
{
    static void Serialize(string OutputFile)
    {
        // Write to disk 
        using (Stream stream = File.Open(OutputFile, FileMode.Create))
        {
            BinaryWriter bw = new BinaryWriter(stream);
            // Save number of entries
            bw.Write(accounts.Count);

            foreach (KeyValuePair<string, List<Account>> accountKvp in accounts)
            {
                // Save each key/value pair
                bw.Write(accountKvp.Key);
                bw.Write(accountKvp.Value.Count);
                foreach (Account account in accountKvp.Value)
                {
                    account.Serialize(bw);
                }
            }
        }
    }

    static void Deserialize(string InputFile)
    {
        accounts.Clear();

        // Read from disk
        using (Stream stream = File.Open(InputFile, FileMode.Open))
        {
            BinaryReader br = new BinaryReader(stream);
            int entryCount = br.ReadInt32();
            for (int entries = 0; entries < entryCount; entries++)
            {
                // Read in the key-value pairs
                string key = br.ReadString();
                int accountCount = br.ReadInt32();
                List<Account> accountList = new List<Account>();
                for (int i = 0; i < accountCount; i++)
                {
                    Account account = new Account();
                    account.Deserialize(br);
                    accountList.Add(account);
                }
                accounts.Add(key, accountList);
            }
        }
    }

    static Dictionary<string, List<Account>> accounts = new Dictionary<string, List<Account>>();

    static void Main(string[] args)
    {
        string accountName = "Bob";
        List<Account> newAccounts = new List<Account>();
        newAccounts.Add(AddAccount("A", 1));
        newAccounts.Add(AddAccount("B", 2));
        newAccounts.Add(AddAccount("C", 3));
        accounts.Add(accountName, newAccounts);

        accountName = "Tom";
        newAccounts = new List<Account>();
        newAccounts.Add(AddAccount("A1", 11));
        newAccounts.Add(AddAccount("B1", 22));
        newAccounts.Add(AddAccount("C1", 33));
        accounts.Add(accountName, newAccounts);

        string saveFile = @"C:\accounts.bin";

        Serialize(saveFile);

        // clear it out to prove it works
        accounts.Clear();

        Deserialize(saveFile);
    }

    static Account AddAccount(string AccountName, int AccountNumber)
    {
        Account account = new Account();
        account.AccountName = AccountName;
        account.AccountNumber = AccountNumber;
        return account;
    }
}
于 2009-12-21T20:52:27.690 に答える
9

シリアル化が容易なため、ファイル用の XML リーダー/ライター クラスをお勧めします。

C# でのシリアル化

シリアライゼーション (python では pickling として知られています) は、オブジェクトをバイナリ表現に変換する簡単な方法です。バイナリ表現は、たとえば、ディスクに書き込んだり、ワイヤ経由で送信したりできます。

設定をファイルに簡単に保存する場合などに便利です。

属性でマークすると、独自のクラスをシリアル化でき[Serializable] ます。これは、 としてマークされたものを除く、クラスのすべてのメンバーをシリアル化し [NonSerialized]ます。

以下は、これを行う方法を示すコードです。

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;


namespace ConfigTest
{ [ Serializable() ]

    public class ConfigManager
    {
        private string windowTitle = "Corp";
        private string printTitle = "Inventory";

        public string WindowTitle
        {
            get
            {
                return windowTitle;
            }
            set
            {
                windowTitle = value;
            }
        }

        public string PrintTitle
        {
            get
            {
                return printTitle;
            }
            set
            {
                printTitle = value;
            }
        }
    }
}

次に、おそらく ConfigForm で、ConfigManager クラスを呼び出してシリアル化します。

public ConfigForm()
{
    InitializeComponent();
    cm = new ConfigManager();
    ser = new XmlSerializer(typeof(ConfigManager));
    LoadConfig();
}

private void LoadConfig()
{     
    try
    {
        if (File.Exists(filepath))
        {
            FileStream fs = new FileStream(filepath, FileMode.Open);
            cm = (ConfigManager)ser.Deserialize(fs);
            fs.Close();
        } 
        else
        {
            MessageBox.Show("Could not find User Configuration File\n\nCreating new file...", "User Config Not Found");
            FileStream fs = new FileStream(filepath, FileMode.CreateNew);
            TextWriter tw = new StreamWriter(fs);
            ser.Serialize(tw, cm);
            tw.Close();
            fs.Close();
        }    
        setupControlsFromConfig();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

シリアル化された後、cm.WindowTitle などを使用して構成ファイルのパラメーターを呼び出すことができます。

于 2009-12-21T19:07:01.753 に答える
7

あなたが言及したものの4番目のオプションはバイナリファイルです。難解で難しいように聞こえますが、.NET のシリアル化 API を使用すると、非常に簡単に実行できます。

バイナリ ファイルと XML ファイルのどちらを選択しても、同じシリアライゼーション API を使用できますが、使用するシリアライザーは異なります。

クラスをバイナリ シリアル化するには、[Serializable] 属性でマークするか、ISerializable を実装する必要があります。

XMLで同様のことを行うことができますが、インターフェイスは IXmlSerializable と呼ばれ、属性は System.Xml.Serialization 名前空間の [XmlRoot] およびその他の属性です。

リレーショナル データベースを使用する場合、SQL Server Compact Editionは無料で非常に軽量で、単一のファイルに基づいています。

于 2009-12-21T19:04:10.850 に答える
7

現在のプロジェクトのデータ ストレージのコーディングが完了しました。これが私の5セントです。

バイナリのシリアル化から始めました。処理は遅く (100,000 オブジェクトの読み込みに約 30 秒)、ディスク上にもかなり大きなファイルが作成されていました。ただし、実装には数行のコードが必要であり、すべてのストレージのニーズに対応できました。パフォーマンスを向上させるために、カスタムのシリアライゼーションに移行しました。Code Project で Tim Haynes による FastSerialization フレームワークを見つけました。実際、数倍高速で (読み込みに 12 秒、保存に 8 秒、100K レコード)、必要なディスク容量も少なくなります。このフレームワークは、以前の投稿で GalacticJello によって概説された手法に基づいて構築されています。

その後、SQLite に移行し、100K レコードの読み込みに 6 秒、保存に 4 秒という 2 倍、場合によっては 3 倍高速なパフォーマンスを得ることができました。これには、ADO.NET テーブルのアプリケーション タイプへの解析が含まれます。また、ディスク上のファイルがはるかに小さくなりました。この記事では、ADO.NET から最高のパフォーマンスを引き出す方法について説明しています: http://sqlite.phxsoftware.com/forums/t/134.aspx。INSERT ステートメントを生成することは非常に悪い考えです。私がそれを知った経緯はお察しの通りです。:) 確かに、SQLite の実装にはかなりの時間がかかりました。さらに、コードのほぼすべての行にかかる時間を慎重に測定する必要がありました。

于 2009-12-23T20:34:23.290 に答える
5

私が最初に見たのはデータベースです。ただし、シリアル化はオプションです。バイナリ シリアライゼーションを使用する場合は、回避 BinaryFormatterします。フィールドなどを変更すると、バージョン間で怒る傾向があります。Xml 経由XmlSerialzierは問題なく、サイド バイ サイドの互換性があります (つまり、同じクラス定義で)。コントラクト ベースのバイナリ シリアライゼーションを試したい場合は、protobuf-net を使用します (フラット ファイル シリアライザーを簡単に利用できます)。

于 2009-12-21T20:35:50.750 に答える
4

データが複雑で量が多い場合、またはローカルでクエリを実行する必要がある場合は、オブジェクト データベースが有効なオプションになる可能性があります。Db4oまたはKarvoniteを見ることをお勧めします。

于 2009-12-21T19:40:06.243 に答える
3

このスレッドの回答の多くは、ソリューションをオーバーエンジニアリングしようとしています。私が正しければ、ユーザー設定を保存したいだけです。

これには、.ini ファイルまたは App.Config ファイルを使用します。

私が間違っていて、設定以外のデータを保存している場合は、csv 形式のフラット テキスト ファイルを使用してください。これらは、XML のオーバーヘッドがなく、高速で簡単です。これらはエレガントではなく、うまくスケーリングできず、履歴書の見栄えもよくないため、人々はこれらをうんちするのが好きですが、必要なものによっては、これが最適なソリューションになる可能性があります.

于 2009-12-21T19:37:50.223 に答える
2

データがどのように見えるか、つまり複雑さ、サイズなどを知らなくても... XMLは保守が簡単で、簡単にアクセスできます。私はAccessデータベースを使用しません。フラットファイルは、特にファイル内の複数のデータフィールド/要素を処理している場合、長期にわたって維持するのがより困難です。

私は毎日大量のフラットファイルデータフィードを扱っています。極端な例ではありますが、フラットファイルデータは私が処理するXMLデータフィードよりも維持するのがはるかに困難です。

C#を使用してXMLデータをデータセットにロードする簡単な例:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

XMLデータをクエリするためのオプションとしてLINQtoXMLをチェックアウトすることもできます...

HTH..。

于 2009-12-21T19:15:53.980 に答える
1

ローカル データ ストアを持つ「スタンドアロン」アプリをいくつか実行しました。SQL Server Compact Edition (以前は SQLAnywhere と呼ばれていました) を使用するのが最善だと思います。

軽量で無料です。さらに、他のプロジェクトで再利用可能なデータ アクセス レイヤーの記述に固執することもできます。また、本格的な SQL サーバーのようにアプリをより大きなものにスケーリングする必要がある場合は、接続文字列を変更するだけで済みます。

于 2009-12-21T19:17:15.947 に答える
1

Account オブジェクトの複雑さに応じて、XML またはフラット ファイルのいずれかをお勧めします。

各アカウントに保存する値が数個しかない場合は、次のようにプロパティ ファイルに保存できます。

account.1.somekey=Some value
account.1.someotherkey=Some other value
account.1.somedate=2009-12-21
account.2.somekey=Some value 2
account.2.someotherkey=Some other value 2

...など。プロパティ ファイルは文字列ディクショナリに直接マップされるため、プロパティ ファイルからの読み取りは簡単です。

このファイルをどこに保存するかについては、プログラムのサブフォルダー内の AppData フォルダーに保存するのが最適です。これは、現在のユーザーが常に書き込みアクセスできる場所であり、OS 自体によって他のユーザーから保護されています。

于 2009-12-21T19:44:39.527 に答える
0

私の最初の傾向は、アクセス データベースです。.mdb ファイルはローカルに保存され、必要に応じて暗号化できます。ただし、XML または JSON も多くのシナリオで機能します。フラット ファイルは、読み取り専用で非検索 (前方読み取り専用) の情報にのみ使用します。幅を設定するために csv 形式を好む傾向があります。

于 2009-12-21T19:03:33.757 に答える
0

保存しようとしているデータの量によって異なります。実際には、フラット ファイルと XML の間に違いはありません。XML は、ドキュメントに構造を提供するため、おそらく好ましいでしょう。実際には、

最後のオプションであり、現在多くのアプリケーションで使用されているのは Windows レジストリです。個人的にはお勧めしませんが (レジストリの肥大化、破損、その他の潜在的な問題)、オプションです。

于 2009-12-21T19:06:18.680 に答える
0

シンプルにしてください - あなたが言ったように、フラットファイルで十分です。フラットファイルを使用してください。

これは、要件を正しく分析したことを前提としています。XML ステップとしてのシリアライズをスキップします。単純な辞書にはやり過ぎです。データベースについても同じことです。

于 2009-12-21T20:23:48.100 に答える
0

バイナリ シリアル化ルートを使用する場合は、データムの特定のメンバーにアクセスする必要がある速度を考慮してください。コレクションが小さい場合は、ファイル全体をロードするのが理にかなっていますが、大きい場合は、インデックス ファイルを検討することもできます。

ファイル内の特定のアドレスにあるアカウント プロパティ/フィールドを追跡すると、特にキーの使用法に基づいてそのインデックス ファイルを最適化する場合に、アクセス時間を短縮するのに役立ちます。(おそらく、ディスクに書き込む場合でも。)

于 2009-12-21T19:37:07.353 に答える