.NET (C#) でデータをローカルに保存する最良の方法
-
20-09-2019 - |
質問
ユーザーデータを取得し、後で使用できるようにローカルに保存するアプリケーションを作成しています。アプリケーションの起動と停止は頻繁に行われるため、アプリケーションの起動と終了時にデータを保存/ロードできるようにしたいと考えています。
フラット ファイルを使用すると、データを実際に保護する必要がないため (この PC にのみ保存されます)、非常に簡単になります。私が考える選択肢は次のとおりです。
- フラットファイル
- XML
- SQL DB
フラット ファイルの保守にはもう少し労力が必要です (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>
ご覧のとおり、家系図は次のとおりです
- ユーザー名 (辞書の文字列) >
- アカウント (リスト内の各アカウント) >
- アカウント データ (つまり、クラス プロパティ)。
このレイアウトを Dictionary<Username, List<Account>>
これが難しいところであり、この質問の本質でもあります。
ここには連載に関する「ハウツー」の回答がたくさんありますが、これは早い段階で明確にしなかった私の責任ですが、今は明確な解決策を探しています。
他のヒント
それは本当にあなたが保存しているかに依存します。あなたが構造化データの話をしている場合は、SQLiteのまたはSQL Server Compact EditionのようなXMLまたは非常に軽量なSQLのいずれかのRDBMSはあなたのためにうまく動作します。データは些細なサイズを超えて移動した場合にSQLソリューションは、特に魅力的になります。
あなたは明らかに比較的非構造化データ(例えば画像などのバイナリオブジェクト)の大部分を保存している場合はどちらのデータベースもXMLソリューション適切ですが、あなたの質問与えられた私は、それが後者よりも前者よります推測していますます。
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();
}
}
上記のすべては良い答えであり、一般的に問題を解決します。
あなたはデータの百万に拡大するのは簡単、無料の方法が必要な場合は、、にESENT管理インターフェイスのプロジェクトを試してみますCodePlexのでます。
ESENTは、Windowsの一部である埋め込みデータベースストレージエンジン(ISAM)です。これは、行レベルのロック、書き込みアヘッドロギングおよびスナップショット・アイソレーションを有する信頼性の高い、トランザクション、同時、高性能データ・ストレージを提供します。これは、ESENTのWin32 APIのマネージラッパーです。
これは、非常に使いやすいですPersistentDictionaryを目的としています。辞書()オブジェクトとして考えて、それが自動的にロードされたから、余分なコードなしでディスクに保存されます。
例
/// <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型フロート ダブルのGuidのDateTime TimeSpanの文字列
のサポートされている値のタイプの
辞書値のいずれかであることができます キータイプ、NULL可能のバージョン キーの種類、ウリ、たIPAddressまたはA 直列化可能な構造。構造があります その場合のみ、シリアル化可能と考えられ すべてのこれらの基準を満たしています:
•構造体は次のようにマークされています の直列化可能•すべてのメンバー 構造体のいずれかであります: 1.基本データ・タイプ(例えば、Int32)を 2. A列、URIまたはたIPAddress 3.シリアライズ構造
あるいは、別の言い方をすると、 直列化可能な構造を含めることはできません クラスオブジェクトへの参照。この APIの一貫性を維持するために行われます。 にオブジェクトを追加します PersistentDictionaryはのコピーを作成します シリアライズしかしオブジェクト。 元のオブジェクトを変更するとしません につながるコピーを変更 紛らわしい行動。これらを回避するために、 問題PersistentDictionary意志 唯一の値として値の型を受け入れます。
の のシリアル化できる[Serializableを]構造体グッド{ 公共のDateTime?受けました。 公共の文字列の名前。 公共進価格。 公共ウリのURL。 }
{の のシリアライズできません[Serializableを]構造体バッド 公共バイト[]のデータ; //配列はサポートされていません。 公共の例外エラー。 //参照オブジェクト}
ファイルのシリアル化が容易な XML リーダー/ライター クラスをお勧めします。
シリアル化(Pythonで漬物として知られています)は、オブジェクトをバイナリ表現に変換する簡単な方法です。ディスクに書き込むか 電信で送られた。
便利です。設定をファイルに簡単に保存できます。
あなたがそれらをマークすると、あなたはあなた自身のクラスをシリアル化することができます
[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 などを使用して構成ファイルのパラメーターを呼び出すことができます。
あなたが言及したものの4番目のオプションは次のとおりです バイナリファイル. 。それは難解で難しいように思えますが、.NET のシリアル化 API を使用すると非常に簡単です。
バイナリ ファイルを選択するか XML ファイルを選択するかに関係なく、異なるシリアライザを使用することになりますが、同じシリアル化 API を使用できます。
クラスをバイナリ シリアル化するには、クラスを [Serializable] 属性でマークするか、ISerializable を実装する必要があります。
同様のことができます XML, ただし、インターフェイスは IXmlSerializable と呼ばれ、属性は [XmlRoot] および System.Xml.Serialization 名前空間のその他の属性です。
リレーショナルデータベースを使いたい場合は、 SQL Server コンパクト エディション 無料で非常に軽量で、単一のファイルに基づいています。
、私は、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;
}
}
ちょうど私の現在のプロジェクトのための符号化データの格納を終えました。ここに私の5セントです。
私は、バイナリシリアルで開始しました。それは遅い(10万オブジェクトの負荷のために約30秒)だったし、それは同様に、ディスク上の非常に大きなファイルを作成しました。しかし、それが実現するために私に数行のコードを取って、私は私のすべてのストレージのニーズをカバーしました。 より良いパフォーマンスを得るために、私は、カスタム直列に移動しました。コードプロジェクトのティム・ヘインズFastSerializationフレームワークを見つけました。確かに、それはより速く数回です(負荷のための12秒、保存のための8秒、100Kレコードを得た)、それが少ないディスクスペースを取ります。フレームワークは、以前の記事でGalacticJelloによって概説技術の上に構築されます。
それから私は、SQLiteのに移動し、2時には3倍高速なパフォーマンスを得ることができました - ロードと保存のために4秒6秒、100Kレコードを。これは、アプリケーションの種類に構文解析ADO.NETテーブルを含んでいます。また、ディスク上の私にはるかに小さいファイルを与えました。この記事では、ADO.NETのうち、最高のパフォーマンスを取得する方法について説明します。のhttp://sqlite.phxsoftware .COM /フォーラム/ T / 134.aspx に。 INSERT文を生成することは非常に悪い考えです。あなたは私がそれについて知っているようになったかを推測することができます。 :)確かに、SQLiteの実装では、私にはかなりの時間の少しプラスかなり多くすることで、コードのすべての行を取る時間の慎重な測定を取っています。
あなたのデータは、複素量が高いか、そして、有効なオプションであるかもしれないデータベースオブジェクトローカルに照会する必要がある場合。私は db4oののか<のhref = "を見てお勧めしたいhttp://code.msdn.microsoft.com / karvonite」REL = "nofollowをnoreferrer"> Karvonite の
私が見たい最初のものはデータベースです。しかし、直列化はオプションです。あなたがバイナリシリアルのために行く場合、私は にBinaryFormatter
を避けるだろう - それはあなたがなどのフィールドを変更した場合XmlSerialzier
経由XMLは大丈夫だと思う、とサイドバイサイドすることができ、バージョン間怒る傾向がありますあなたは契約ベースのバイナリシリアル化を試してみたい場合は、互換性のある(すなわち、同じクラス定義で)いるProtobufネットで(あなたに何の努力なしでフラットファイルシリアライザを与える)。
ソリューションをoverengineerするには、このスレッドの試みで、回答の多く。私が正しいんだ場合、あなただけのユーザー設定を保存したい。
このため.iniファイルまたはapp.configファイルを使用します。
私が間違っている、とあなただけの設定よりも多くのデータを格納している場合は、、csv形式でフラットなテキストファイルを使用します。これらは、XMLのオーバーヘッドなしに迅速かつ簡単です。彼らはうまくスケーリングしないと履歴書のように良い見ていないが、それはあなたが必要なものに応じてのために最善の解決策になるかもしれない、エレガントではありませんので、これらのうんちうんちのような人々ます。
私は、ローカルデータストアを持っているいくつかの「スタンドアローン」のアプリを行っています。私が使うための最善のことは、SQL ServerのCompact Editionは(以前のSQLAnywhereとして知られている)になると思います。
これは、軽量で、無料です。また、あなたは他のプロジェクトで再利用可能であるプラスアプリは、これまで本格的なSQLサーバのような何か大きなものに拡張する必要がある場合、あなただけの接続文字列を変更する必要があるデータアクセス層を書くことに固執することができます。
私の最初の傾斜は、アクセスデータベースです。 .MDBファイルがローカルに保存され、そしてそれが必要と判断された場合に暗号化することができます。 XMLやJSONにも多くのシナリオのために働くだろうけど。私が唯一の情報(前方のみの読み取り)、読み取り専用、非検索に使用するフラットファイル。私は幅を設定するには、csv形式を好む傾向にある。
それはあなたが保存しようとしているデータの量によって異なります。実際には、フラットファイルとXMLの間に違いはありません。それは、文書に構造を提供するので、XMLは、おそらく望ましいだろう。実際には、
最後のオプション、およびアプリケーションの多くは、今使用しているWindowsのレジストリです。私は個人的に(レジストリ肥大化、汚職、その他の潜在的な問題を)お勧めしませんが、それはオプションです。
あなたのデータは、すなわち複雑さ、大きさなど、どのようなものか知らず... XMLは、保守が容易かつ簡単にアクセスできます。あなたがあなたのファイルに複数のデータ・フィールド/要素を扱っている場合は特に、私は、Accessデータベースを使用することはありません、フラットファイルが長距離にわたり維持することがより困難である。
私は、毎日の良い量で供給し、さらには極端な例が、フラットファイルデータがはるかに困難XMLデータは、私が処理するフィードをより維持することです大型フラットファイルデータを扱うます。
C#を使用してデータセットにロードするXMLデータの簡単な例:
DataSet reportData = new DataSet();
reportData.ReadXml(fi.FullName);
また、XMLデータを照会するためのオプションとしてXMLにLINQをチェックアウトすることができます...
HTH ...
、データムの特定のメンバーがアクセスする必要がある速度を考えてみましょう。それが唯一の小さなコレクションの場合は、ファイル全体をロードすることは意味をなさないだろうが、それは大きなものになる場合、あなたはまた、インデックスファイルを検討するかもしれません。
ファイル内の特定のアドレスに配置されているトラッキングアカウントのプロパティ/フィールドは、キーの使用に基づいて、そのインデックスファイルを最適化する場合は特に、あなたがアクセス時間を短縮することができます。 (おそらくあなたはディスクに書き込む場合でも。)
あなたのAccountオブジェクトのcompelexityに応じて、私は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自体が他のユーザーから安全に保たれています。
それをシンプルに保つ - あなたが言ったように、フラット・ファイルで十分です。フラット・ファイルを使用します。
これは、あなたが正しくあなたの要件を分析したと仮定しています。私は、単純な辞書のためのやり過ぎXMLステップとして、シリアライズをスキップします。データベースのための同じ事ます。
ファイル内のほとんどの場合、JSONでの私の経験では(主にあなたが配列やオブジェクトまたは単に1つの番号または文字列を格納する必要があります)十分です。私はめったに(それを設定し、それを使用するためのより多くの時間を必要とする、それはやり過ぎだ倍の大半)のSQLiteを必要としません。