在.NET中本地存储数据的最佳方法(C#)
我正在编写一个应用程序,将用户数据存储在本地,以备日后使用。 该应用程序将被启动和停止相当频繁,我想使它保存/加载数据在应用程序的开始/结束。
如果我使用平面文件,这将是相当简单的,因为数据并不需要被保护(它只会被存储在这台PC上)。 我相信的select是这样的:
- 平面文件
- XML
- SQL DB
平面文件需要更多的努力来维护(没有类似于XML的内置类),但是我之前没有使用过XML,而SQL似乎对于这个相对简单的任务来说是过度的。
还有其他的途径值得探索吗? 如果不是,哪个是最好的解决scheme?
编辑:要添加更多的数据的问题,基本上我唯一想要存储的是一个字典,看起来像这样
Dictionary<string, List<Account>>
其中Account是另一个自定义types。
我会序列化字典作为xmlroot,然后帐户types作为属性?
更新2:
所以可以序列化字典。 是什么让它变得复杂的是,这个字典的价值是一个通用的本身,这是一个复杂的数据结构types帐户的列表。 每个帐户都相当简单,只是一堆属性。
这是我的理解,这里的目标是试图结束这个:
<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>
正如你所看到的那样,
- 用户名(字典的string)>
- 帐户(列表中的每个帐户)>
- 账户数据(即类属性)。
从Dictionary<Username, List<Account>>
获得这个布局是棘手的一点,这个问题的本质。
在这里有很多关于序列化的“如何做出”的反应,这是我的错,因为我在一开始就没有把它弄清楚,但现在我正在寻找一个明确的解决scheme。
我会将文件存储为JSON 。 既然你正在存储一个字典,这只是一个名称/值对列表,那么这几乎是什么JSON的devise。
还有一些不错的,免费的.NET JSON库 – 这里有一个,但是你可以在第一个链接上find完整的列表。
这真的取决于你存储的内容。 如果您正在讨论结构化数据,那么XML或像SQLite或SQL Server Compact Edition这样的非常轻量级的SQL RDBMS都能很好地为您服务。 如果数据超出一般规模,那么SQL解决scheme就变得特别引人注目。
如果存储大量相对非结构化的数据(比如图像等二进制对象),那么显然,数据库和XML解决scheme都不合适,但考虑到您的问题,我猜测前者比后者要多。
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(); } }
以上都是很好的答案,一般可以解决问题。
如果您需要一种简单,免费的方法来扩展数百万条数据,请尝试使用CodePlex上的ESENT Managed Interface项目。
ESENT是一个embedded式数据库存储引擎(ISAM),它是Windows的一部分。 它通过行级locking,预写日志logging和快照隔离,提供可靠,事务处理,并发的高性能数据存储。 这是ESENT Win32 API的托pipe包装器。
它有一个很容易使用的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(); }
回答乔治的问题:
支持的密钥types
只有这些types支持字典键:
布尔字节Int16 UInt16 Int32 UInt32 Int64 UInt64浮动双引导DateTime TimeSpanstring
支持的值types
字典值可以是任何键types,键types的可空版本,Uri,IPAddress或可序列化的结构。 一个结构只有符合所有这些标准才被认为是可序列化的:
•结构被标记为可序列化•结构的每个成员都是:1.原始数据types(例如Int32)2.string,Uri或IPAddress 3.可序列化的结构。
或者,换句话说,一个可序列化的结构不能包含任何对类对象的引用。 这样做是为了保持API的一致性。 将对象添加到PersistentDictionary通过序列化来创build对象的副本。 修改原始对象不会修改副本,这会导致混淆行为。 为了避免这些问题,PersistentDictionary将只接受值types的值。
可以序列化 [ 可序列化] struct Good {public DateTime? 接受; 公共string名称; 公共十进制价格; 公开Uri Url; }
不能被序列化 [可序列化]结构不好{公共字节[]数据; //数组不支持public Exception Error; //引用对象}
我build议使用XML读取器/写入器来处理文件,因为它很容易被序列化。
在C#中的序列化
序列化(在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等来调用你的configuration文件的参数。
你提到的第四个选项是二进制文件 。 虽然这听起来很神秘和困难,但使用.NET中的序列化API非常简单。
无论您select二进制文件还是XML文件,都可以使用相同的序列化API,尽pipe您会使用不同的序列化程序。
要二进制序列化一个类,它必须用[Serializable]属性标记或实现ISerializable。
尽pipe这里的接口被称为IXmlSerializable,并且属性是System.Xml.Serialization命名空间中的[XmlRoot]和其他属性,你可以做类似于XML的事情。
如果您想使用关系数据库,则SQL Server Compact Edition是免费且非常轻量级的,并且基于单个文件。
刚刚完成我目前的项目编码数据存储。 这是我的5美分。
我从二进制序列化开始。 速度很慢(100,000个物体的加载时间约为30秒),并且在磁盘上创build了一个相当大的文件。 但是,我花了几行代码来实现,并且覆盖了所有的存储需求。 为了获得更好的性能,我移动了自定义序列化。 Tim Haynes在Code Project上发现了FastSerialization框架。 事实上,它的速度要快几倍(负载12秒,保存8秒,logging100K),磁盘空间更less。 该框架是build立在GalacticJello在之前的文章中概述的技术上的。
然后我搬到SQLite,并有能力得到2有时3倍的性能 – 6秒的负载和4秒的保存,100Klogging。 它包括parsingADO.NET表到应用程序types。 它也给了我更小的文件在磁盘上。 本文解释如何从ADO.NET中获得最佳性能: http : //sqlite.phxsoftware.com/forums/t/134.aspx 。 生成INSERT语句是一个非常糟糕的主意。 你可以猜到我是怎么知道的。 :)事实上,SQLite实现花了我不less时间,仔细测量了几乎每一行代码的时间。
如果你的集合变得太大,我发现Xml序列化变得很慢。 序列化字典的另一个select是使用BinaryReader和BinaryWriter“滚动你自己”。
这里有一些示例代码只是为了让你开始。 你可以使这些通用的扩展方法来处理任何types的字典,它工作得很好,但是在这里过于冗长。
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; } }
如果你的数据很复杂,数量很高或者你需要在本地查询,那么对象数据库可能是一个有效的选项。 我build议看看Db4o或Karvonite 。
我首先要看的是一个数据库。 但是,序列化是一个选项。 如果你去二进制序列化,那么我会避免 BinaryFormatter
– 如果你改变字段等有版本之间生气的倾向等Xml通过XmlSerialzier
会很好,可以并排兼容(即与同一类定义)与protobuf网,如果你想尝试基于合同的二进制序列化(给你一个平面文件序列化没有任何努力)。
在这个线程中的很多答案试图过度工程解决scheme。 如果我是正确的,你只是想存储用户设置。
为此,请使用.ini文件或App.Config文件。
如果我错了,而且存储的数据不仅仅是设置,请使用csv格式的平面文本文件。 这些既快速又简单,没有XML的开销。 因为他们不够高雅,不喜欢优秀,在简历上看起来不怎么样,所以大家喜欢这样做,但是根据你的需求,这可能是最好的解决scheme。
我已经做了几个“独立”的应用程序,有一个本地的数据存储。 我认为最好使用的是SQL Server Compact Edition(以前称为SQLAnywhere)。
这是轻量级和免费的。 另外,你可以坚持编写一个可以在其他项目中重用的数据访问层,如果应用程序需要扩展到更大的SQL服务器,你只需要改变连接string。
我的第一个倾向是访问数据库。 .mdb文件存储在本地,如果认为有必要可以encryption。 尽pipeXML或JSON也适用于很多场景。 平面文件我只能用于只读,非search(只读前向)信息。 我倾向于selectcsv格式来设置宽度。
这取决于您要存储的数据量。 实际上,平面文件和XML之间没有区别。 XML可能会更好,因为它提供了一个文档结构。 在实践中,
最后一个选项,现在很多应用程序是Windowsregistry。 我个人不推荐(registry膨胀,腐败,其他潜在的问题),但它是一个选项。
不知道你的数据是什么样的,比如复杂性,大小等等。XML易于维护,易于访问。 我不会使用Access数据库,并且平面文件在长时间内更难以维护,特别是在处理文件中的多个数据字段/元素时。
我每天都会处理大量的平面文件数据馈送,即使是一个极端的例子,平面文件数据也比我处理的XML数据馈送难得多。
使用C#将XML数据加载到数据集中的一个简单示例:
DataSet reportData = new DataSet(); reportData.ReadXml(fi.FullName);
您也可以查看LINQ to XML作为查询XML数据的选项…
HTH …
如果进入二进制序列化路线,请考虑数据的特定成员需要访问的速度。 如果它只是一个小集合,加载整个文件将是有意义的,但如果它会很大,您可能还会考虑一个索引文件。
跟踪位于文件中特定地址的帐户属性/字段可帮助您加快访问时间,尤其是在您根据密钥使用情况优化索引文件的情况下。 (甚至可能在写入磁盘时)。
根据您帐户对象的灵活性,我会推荐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
…等等。 从属性文件读取应该很容易,因为它直接映射到string字典。
至于在哪里存储这个文件,最好的select是存储到你的程序的子文件夹内的AppData文件夹中。 这是当前用户总是可以写入的位置,并且由操作系统本身保持对其他用户的安全。
保持简单 – 正如你所说,一个平面文件就足够了。 使用平面文件。
假设您已经正确分析了您的需求。 我会跳过序列化的XML步骤,矫枉过正一个简单的字典。 数据库一样的东西。
根据我的经验,在大多数情况下,文件中的JSON就足够了(大多数情况下,您需要存储一个数组或对象,或者只是一个数字或string)。 我很less需要SQLite(需要更多的时间来设置和使用它,大部分时间是过度的)。