在.NET中本地存储数据的最佳方式(C#)

问题描述:

我正在编写一个应用程序,它将用户数据存储在本地以供以后使用。应用程序将经常启动和停止,并且我想让它在应用程序的开始/结束时保存/加载数据。在.NET中本地存储数据的最佳方式(C#)

如果我使用平面文件,这将是相当直接的,因为数据并不需要保护(它只会存储在这台PC上)。我相信,这些选项是这样的:

  • 平面文件
  • XML
  • SQL数据库

平面文件需要多一点的努力,以保持(无内置类像XML),但我之前没有使用过XML,对于这个相对简单的任务来说,SQL似乎过分了。

有没有其他的途径值得探索?如果不是,哪个是最好的解决方案?


编辑:要多一点数据添加到这个问题,基本上我想保存的唯一的事情是,看起来像这样

Dictionary<string, List<Account>> 

其中帐户是另一个自定义类型的字典。

我会序列化字典作为xmlroot,然后帐户类型作为属性?


更新2:

所以这是可能的序列化的字典。让它变得复杂的是,这个字典的值是一个通用本身,它是一个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> 

正如你可以看到heirachy是

  • 用户名(字典的字符串)>
  • 帐户(列表中的每个帐户)>
  • 帐户数据(即类属性)。

Dictionary<Username, List<Account>>获取此布局是棘手的问题,也是这个问题的实质。

这里有很多关于序列化的'如何做'的回应,这是我的错,因为我没有在早期就更清楚,但现在我正在寻找一个明确的解决方案。

+0

给予更多ditails的那种应用程序和数据存储,也excpected sizr – 2009-12-21 19:03:51

+2

序列化的字典:http://*.com/questions/1111724 – Cheeso 2009-12-21 19:28:37

我会将文件存储为JSON。既然你正在存储一个字典,它只是一个名称/值对列表,那么这几乎是json的设计目的。
有不少像样的,免费的.NET json库 - 这里是one,但您可以在第一个链接上找到完整列表。

+0

JSON是本地存储有点不寻常,但它绝对是一个很好的解决方案。尤其是像Newtonsoft的Json.NET – AFract 2014-06-30 10:21:30

+0

库,而不是依靠第三方库,我会使用内置的数据集类型是超级简单写入到磁盘(见下汤姆·米勒的例子) – docesam 2016-12-12 07:24:45

XML易于使用,通过序列化。使用Isolated storage

How to decide where to store per-user state? Registry? AppData? Isolated Storage?

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

这是不是很便携,也不整齐 – 2009-12-21 19:20:50

+3

它看起来像剪切粘贴代码,所以你可以成为第一张海报。如果坚持你的链接,会更好。 – 2009-12-21 19:38:37

+7

嗯,是的,我把它从我写的应用程序中删除了,就这么做了。 Roboto,你有什么问题? – Cheeso 2009-12-21 22:57:10

见这真的取决于你要存储什么。如果您正在讨论结构化数据,那么XML或像SQLite或SQL Server Compact Edition这样的非常轻量级的SQL RDBMS都可以为您效劳。如果数据超出一般规模,SQL解决方案将变得特别引人注目。

如果你存储的是大量的非结构化数据(比如图像等二进制对象),那么显然,数据库和XML解决方案都不合适,但是考虑到你的问题,我猜测它的前者比后者。

+1

通过AppData \ Roaming结束了一个免费的SQLite ADO.NET提供程序是http://sqlite.phxsoftware.com/ – 2009-12-21 19:48:49

+0

XML配置文件必须是结构化的? – 2009-12-21 19:59:53

+0

@Roboto:根据定义,XML是结构化的。但这并不意味着您必须以高度结构化的方式使用它们。 – 2009-12-21 20:07:57

我的第一个倾向是访问数据库。 .mdb文件存储在本地,如果认为有必要可以加密。尽管XML或JSON也适用于很多场景。平面文件我只能用于只读,非搜索(只读前向)信息。我倾向于选择csv格式来设置宽度。

+1

出于好奇,为什么要使用Access,除非它已经存在,或者您需要从Access访问?否则,看起来像一个轻量级的SQL引擎会更可取,尤其是对于可用的SQLite和SQL Server CE等进程内选项。 – 2009-12-21 19:09:08

+0

JET引擎 - 它允许你使用.MDB文件,我认为,它安装了windows,它使得.MDB文件作为一种解决方案具有吸引力,而且事实上,它们很容易挖掘访问如果你需要。但是,它早于SQL Server CE的当前版本,它可以是.DLL“xcopy”部署,因此是获得广泛类似结果的更好方法。 – Murph 2009-12-21 19:16:46

+12

朋友不让朋友使用Access – 2009-12-21 19:21:26

您提到的第四个选项是二进制文件。尽管这听起来很神秘和困难,但使用.NET中的序列化API非常简单。

无论您选择二进制文件还是XML文件,都可以使用相同的序列化API,尽管您会使用不同的序列化程序。

要二进制序列化一个类,它必须用[Serializable]属性标记或实现ISerializable。

你可以做XML类似的东西,虽然那里的接口称为IXmlSerializable的,而属性[XmlRoot],并在System.Xml.Serialization命名空间中的其他属性。

如果要使用关系数据库,SQL Server Compact Edition是免费且非常轻量级且基于单个文件。

+0

Flat file!=文本文件。我认为这将属于“平面文件”类别。 – 2009-12-21 19:10:07

+2

无论您是否正在处理XML文件 – 2009-12-21 19:14:18

+2

,您都可以二进制序列化一个类,除非您需要序列化的对象是人类可读的,这是最可靠的方法。它被序列化为一个小文件,并且总是看起来是最快的方法,就代码的运行速度而言。马克是对的,这看起来很神秘和困难,但它根本就不是。并且二进制序列化捕获ENTIRE对象,甚至它的私有成员,哪些XML序列化没有。 – CubanX 2009-12-21 20:11:38

这取决于您要存储的数据量。实际上,平面文件和XML之间没有区别。 XML可能会更好,因为它为文档提供了一个结构。实际上,

最后一个选项,现在很多应用程序使用Windows注册表。我个人不推荐它(注册表膨胀,腐败,其他潜在问题),但它是一种选择。

+0

平面文件与XML不同的一个领域是表示分层数据。当然,您可以在平面文件中表示层次结构,但在XML中这样做更容易。 – itowlson 2009-12-21 19:09:10

我推荐使用XML读取器/写入器来处理文件,因为它很容易被序列化。

Serialization in C#

序列化(称为酸洗在 蟒)是一个 对象转换为二进制表示的是 然后可以一种简单的方法例如写入磁盘或 通过电线发送。

它很有用,例如,方便将设置保存到文件。

如果您使用[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您的配置文件等

+5

只是为了澄清:Serializable和NonSerialized对XmlSerializer没有任何影响;它们仅用于System.Runtime.Serialization(例如二进制序列化)。XmlSerializer将公共字段和(读写)属性序列化,而不是内部状态:类不需要该属性,而XmlIgnore而不是NonSerialized来排除字段或属性。 – itowlson 2009-12-21 19:14:23

+0

@itowlson:正确。 XML序列化使用反射来生成特殊的类来执行序列化。 – 2009-12-21 19:17:07

+0

这将有助于阅读代码的时候,如果它是不任意大小缩进... – 2009-12-23 20:00:45

的参数,不知道你的数据是什么样子,也就是复杂性,大小等..XML易于维护并且易于访问。我不会使用Access数据库,并且平面文件在长时间内更难以维护,特别是在您处理文件中的多个数据字段/元素的情况下。

我每天都会处理大量的平面文件数据馈送,而且即使是极端的例子,平面文件数据也比我处理的XML数据馈送难以维护。

加载XML数据的简单例子为使用C#中的数据集:

DataSet reportData = new DataSet(); 

reportData.ReadXml(fi.FullName); 

您还可以检查出的LINQ to XML为查询XML数据的选项...

HTH ..

我已经完成了几个具有本地数据存储的“独立”应用程序。我认为最好使用的是SQL Server Compact Edition(以前称为SQLAnywhere)。

它轻巧且免费。此外,您可以坚持编写可在其他项目中重复使用的数据访问层,如果应用程序需要扩展到更大型的SQL Server,则只需更改连接字符串。

如果进入二进制序列化路径,请考虑数据的特定成员需要访问的速度。如果它只是一个小集合,加载整个文件将是有意义的,但如果它会很大,您可能还会考虑一个索引文件。

位于文件中特定地址的跟踪帐户属性/字段可帮助您加快访问时间,尤其是在您根据密钥使用情况优化索引文件时。 (甚至当你写入磁盘时)。

在这个线程中的很多答案试图过度工程解决方案。如果我是正确的,您只需要存储用户设置。

为此,请使用.ini文件或App.Config文件。

如果我错了,而且您存储的数据不仅仅是设置,请使用csv格式的平面文本文件。这些既快速又简单,无需XML的开销。由于他们不够优雅,不喜欢很好的规模,在简历上看起来不太好,所以他们喜欢大肆宣传,但根据你的需求,这可能是最好的解决方案。

+0

的app.config VS自定义XML: http://*.com/questions/1565898/app-config-vs-custom-xml-file – 2009-12-21 19:43:40

+0

我正在做一些稍微比设置更复杂的东西。每个用户可能有多个与他们的名字相关的“账户”。字典将此名称(字符串)链接到与其关联的帐户列表。我会为每个用户存储一堆帐户。它可以使用XML,但我不太清楚如何去做。 – George 2009-12-21 19:55:03

+0

在这种情况下,我会使用前面提到的XmlSerializer类。如果你对OOP有很好的把握,那应该很容易。这里有一个很好的例子:http://www.jonasjohn.de/snippets/csharp/xmlserializer-example.htm – James 2009-12-22 23:11:13

如果您的数据很复杂,数量较多或者您需要在本地查询,那么对象数据库可能是一个有效的选项。我建议看看Db4oKarvonite

以上所有都是很好的答案,一般可以解决问题。

如果您需要一种简单,免费的方法来扩展到数百万条数据,请尝试使用CodePlex上的ESENT Managed Interface项目。

ESENT是一个嵌入式数据库存储引擎(ISAM),它是Windows的一部分。它通过行级锁定,预写日志和快照隔离提供可靠的,事务处理,并发的高性能数据存储。这是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浮法 双人的Guid的DateTime时间跨度字符串

支持的值类型

字典值可以是任意的 键类型,所述 密钥类型的可为空的版本,URI的IPAddress或 序列化的结构。这样的结构是 只考虑串行化,如果它 满足所有这些标准:

•结构被标记为 序列化•的 结构的每个成员可以是: 1.一种原始数据类型(例如Int32)已 2 。字符串,Uri或IP地址 3.可串行化的结构。

或者换句话说,一个 可串行结构不能包含任何对类对象的引用。完成此 以保持API一致性。 将对象添加到 PersistentDictionary通过序列化创建对象 的副本。 修改原始对象不会修改 的副本,这会导致 混淆行为。为了避免那些 问题,PersistentDictionary将 只接受值类型作为值。

可串行化 [可序列化] struct好的{0} {0} {0} public DateTime?接受; 公共字符串名称; 公共小数点价格; public Uri Url; }

无法序列化 [可序列化] struct bad { public byte [] Data; //数组不支持 公共异常错误; //引用对象}

+0

这种方法基本上是一个持久的字典更换内置的通用性。这是一个非常优雅的解决方案,但它如何处理OP示例中的复杂对象?它是否将所有内容存储在字典中,或者只是字典本身? – George 2009-12-21 19:57:22

+0

这可能会导致尝试保存类型Account列表的最终目标不足。关键是没问题,但是使泛型可序列化很难:/。 – George 2009-12-21 20:22:11

+0

任何其他实现此功能的人都可以从知道您可以使用Nuget获取ManagedEsent中受益。然后你需要引用Esent.Collections.DLL和Esent.ISAM.DLL。然后添加“使用Microsoft.Isam.Esent.Collections.Generic;”获取PersistentDictionary类型。 DLL可以有藏品从下载选项上http://managedesent.codeplex.com下载 – 2017-05-03 14:14:01

根据您的Account对象的灵活性,我会推荐XML或Flat文件。

如果只是一对夫妇的值存储为每个帐户,你可以把它们存储在一个属性文件,像这样:

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文件夹存储在您的程序的子文件夹中。这是当前用户总是可以写入的位置,并且操作系统本身对其他用户保持安全。

保持简单 - 正如你所说,一个平面文件就足够了。使用平面文件。

这是假设您已正确分析了您的要求。我会跳过序列化作为XML步骤,矫枉过正一个简单的字典。数据库也是一样。

我看的第一件事就是数据库。但是,序列化是一种选择。如果你去二进制序列化,那么我会避免BinaryFormatter - 它有一个在版本之间生气的趋势,如果你改变字段等Xml通过XmlSerialzier会很好,可以并排兼容(即与如果您想尝试基于合同的二进制序列化(无需任何费力就可以为您提供平面文件序列化程序),您可以使用protobuf-net。

如果你的集合变得太大,我发现Xml序列化变得很慢。序列化你的字典的另一个选择是使用BinaryReader和BinaryWriter“滚动你自己”。

下面是一些示例代码,以帮助您入门。你可以使这些通用的扩展方法来处理任何类型的Dictionary,并且它工作得很好,但是在这里过于冗长。

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; 
    } 
} 
+0

谢谢,这看起来存储数据像迄今为止最好的解决方案。与反序列化/序列化保持同步,你的意思是什么?在修改文件时更新文件?此功能只会在应用程序启动和退出时使用以保存字典,请您澄清一下吗? 否则非常感谢。 – George 2009-12-22 01:59:09

+0

想了一下之后,我意识到这意味着序列化和反序列化的逻辑应该是相同的。就这些。 – George 2009-12-22 14:48:45

+0

是的,就是这样。因此,如果您添加另一个属性来序列化/反序列化,请记住,您必须将代码添加到Serialize/Deserialize方法中,并将它们保持相同的顺序。 maintenence的位,但在Xml序列的性能没有可比性(几分钟反序列化,并使用xml,使用BinaryReader在几秒钟,与几十万的字典项)。 – GalacticJello 2009-12-22 16:08:35

刚完成编码我当前项目的数据存储。这是我的5美分。

我以二进制序列化开始。速度很慢(100,000个物体的加载时间大约为30秒),它也在磁盘上创建了一个非常大的文件。但是,这花了我几行代码来实现,并且我覆盖了所有的存储需求。 为了获得更好的性能,我移动了自定义序列化。 Tim Haynes在Code Project上发现了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的实现让我花费了相当多的时间,仔细测量了几乎每一行代码的时间。

根据我的经验,在大多数情况下,文件中的JSON就足够了(大多数情况下,您需要存储数组或对象或只是一个数字或字符串)。我很少需要SQLite(需要更多的时间来设置和使用它,大部分时间它是过度的)。