web-dev-qa-db-ja.com

データを.NET(C#)にローカルに保存する最良の方法

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

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

  • フラットファイル
  • XML
  • SQL DB

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

探索する価値のある他の手段はありますか?そうでない場合、これらのどれが最良の解決策ですか?


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

Dictionary<string, List<Account>> 

ここで、アカウントは別のカスタムタイプです。

辞書をxmlrootとしてシリアル化し、次にアカウントタイプを属性としてシリアル化しますか?


アップデート2:

したがって、辞書をシリアル化することが可能です。それを複雑にしているのは、この辞書の値がそれ自体がジェネリックであるということです。これはアカウント型の複雑なデータ構造のリストです。各アカウントはかなりシンプルで、単なるプロパティの集まりです。

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

<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>

あなたが見ることができるように、heirachyは

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

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

シリアル化については、「方法」の回答がたくさんありますが、これは早い段階で明確にしたわけではないので、私のせいですが、今では明確な解決策を探しています。

66
George

ファイルを [〜#〜] json [〜#〜] として保存します。名前と値のペアのリストにすぎない辞書を保存しているので、これはjsonが設計したものです。
かなり多くの無料の.NET jsonライブラリがあります-ここに one がありますが、最初のリンクに完全なリストがあります。

22
zebrabox

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

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

23
Adam Robinson

XMLは、シリアル化を介して簡単に使用できます。 分離ストレージ を使用します。

ユーザーごとの状態を保存する場所を決定する方法?レジストリ?AppData?分離ストレージ?

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();
    }
}
14
Cheeso

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

数百万のデータに簡単かつ無料でスケーリングする方法が必要な場合は、 CodePlex でESENT Managed Interfaceプロジェクトを試してください。

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();
    }

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

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

これらのタイプのみが辞書キーとしてサポートされています:

ブールバイトInt16 UInt16 Int32 UInt32 Int64 UInt64 Float Double Guid DateTime TimeSpan String

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

辞書の値は、任意のキータイプ、Nullableバージョンのキータイプ、Uri、IPAddress、またはシリアル化可能な構造にすることができます。構造は、次のすべての基準を満たしている場合にのみ、シリアル化可能と見なされます。

•構造はシリアル化可能としてマークされます。•構造のすべてのメンバーは次のいずれかです。1.プリミティブデータ型(Int32など)2.文字列、UriまたはIPAddress 3.シリアル化可能な構造。

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

シリアル化可能[Serializable] struct Good {public DateTime?受け取った;パブリック文字列Name;公定価格;パブリックUri Url; }

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

11
GalacticJello

ファイルにはXMLリーダー/ライタークラスをお勧めします。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などを使用して設定ファイルのパラメーターを呼び出すことができます。

8
user195488

コレクションが大きくなりすぎると、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;
    }
}
7
GalacticJello

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

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

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

[〜#〜] xml [〜#〜]で同様のことができますが、インターフェイスはIXmlSerializableと呼ばれ、属性は[XmlRoot]とSystem.Xml.Serializationの他の属性です名前空間。

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

7
Mark Seemann

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

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

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

6
ACH

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

4
Goran

私が最初に見たいのはデータベースです。ただし、シリアル化はオプションです。バイナリシリアル化に行く場合、avoidBinaryFormatter-フィールドなどを変更するとバージョン間で怒ってしまう傾向があります。XmlSerialzier経由のXmlは問題ありません。コントラクトベースのバイナリシリアル化を試してみたい場合は、protobuf-netと(たとえば、同じクラス定義で)互換性があります(フラットファイルシリアライザを簡単に提供できます)。

4
Marc Gravell

このスレッドの回答の多くは、ソリューションを過剰に設計しようとしています。私が正しい場合は、ユーザー設定を保存するだけです。

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

私が間違っていて、設定以外のデータを保存している場合は、csv形式のフラットテキストファイルを使用してください。これらは、XMLのオーバーヘッドなしで高速かつ簡単です。人々は、これらがエレガントではなく、うまくスケールせず、履歴書で見た目も良くないので、これらをうんちするのが好きですが、それはあなたが必要とするものによってはあなたにとって最良の解決策かもしれません。

3
James

ローカルデータストアを持ついくつかの「スタンドアロン」アプリを作成しました。使用するのに最適なのはSQL Server Compact Edition(以前のSQLAnywhere)だったと思います。

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

2
HitLikeAHammer

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

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

0
Todd Richardson

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

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

0
Larry Watanabe

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

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

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自体によって他のユーザーから安全に保たれます。

0
Pablo Venturino

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

0
Matthew Vines

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

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

0
blacksol

私の経験では、ほとんどの場合、ファイル内のJSONで十分です(ほとんどの場合、配列またはオブジェクト、または単一の数値または文字列を格納する必要があります)。 SQLiteを必要とすることはほとんどありません(SQLiteをセットアップして使用するにはより多くの時間を必要としますが、ほとんどの場合はやりすぎです)。

0
Tadej

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

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

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

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

XMLデータを照会するためのオプションとして、LINQ to XMLをチェックアウトすることもできます...

HTH ...

0
Tom Miller