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

时间:2009-12-21 19:00:41

标签: c# .net xml data-storage

我正在编写一个应用程序,它接收用户数据并将其存储在本地以供日后使用。应用程序将经常启动和停止,我想在应用程序开始/结束时保存/加载数据。

如果我使用平面文件,那将是相当简单的,因为数据并不真正需要保护(它只会存储在这台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>

你可以看到heirachy是

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

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

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

19 个答案:

答案 0 :(得分:23)

这实际上取决于你要存储的内容。如果您正在谈论结构化数据,那么XML或非常轻量级的SQL RDBMS(如SQLite或SQL Server Compact Edition)都可以很好地为您服务。如果数据超出了一个微不足道的大小,SQL解决方案就会变得特别引人注目。

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

答案 1 :(得分:22)

我将文件存储为JSON。由于您存储的字典只是一个名称/值对列表,因此这几乎就是json的设计目标 有一些不错的免费.NET json库 - 这里是one但你可以在第一个链接找到一个完整的列表。

答案 2 :(得分:14)

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

答案 3 :(得分:11)

以上所有都是很好的答案,并且通常可以解决问题。

如果您需要一种简单,免费的方式来扩展到数百万条数据,请在CodePlex上试用ESENT管理接口项目。

  

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

回答乔治的问题:

  

支持的密钥类型

     

仅支持这些类型   字典键:

     

Boolean Byte Int16 UInt16 Int32 UInt32   Int64 UInt64 Float   Double Guid DateTime TimeSpan String

     

支持的值类型

     

字典值可以是任何一个   密钥类型,Nullable版本的   密钥类型,Uri,IPAddress或a   可序列化的结构。结构是   如果它只被认为是可序列化的   符合所有这些标准:

     

•结构标记为   可序列化的•每个成员   struct是:   1.原始数据类型(例如Int32)   2. String,Uri或IPAddress   3.可序列化的结构。

     

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

     

可以序列化 [Serializable] struct Good {       公共日期时间?接受;       公共字符串名称;       公共十进制价格;       公共Uri Url; }

     

无法序列化 [Serializable] struct Bad {       public byte []数据; //不支持数组       公共异常错误; //参考对象}

答案 4 :(得分:8)

我推荐用于文件的XML reader / writer类,因为它很容易被序列化。

Serialization in 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等调用配置文件的参数。

答案 5 :(得分:7)

您提到的第四个选项是二进制文件。虽然这听起来很晦涩难懂,但使用.NET中的序列化API非常容易。

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

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

你可以使用 XML 做类似的事情,虽然接口名为IXmlSerializable,属性是[XmlRoot]和System.Xml.Serialization命名空间中的其他属性。

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

答案 6 :(得分:7)

如果你的集合太大,我发现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;
    }
}

答案 7 :(得分:6)

刚刚完成当前项目的数据存储编码。这是我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的实现花了我很多时间,加上仔细测量几乎所有代码行的时间。

答案 8 :(得分:4)

我要看的第一件事是数据库。但是,序列化是一种选择。如果你进行二进制序列化,那么我会避免 BinaryFormatter - 如果你改变字段等,它会在版本之间生气。如果通过XmlSerialzier Xml就可以了,如果你想尝试基于契约的二进制序列化(给你一个平面文件序列化器而不费力),可以和protobuf-net并排兼容(即使用相同的类定义)。

答案 9 :(得分:4)

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

答案 10 :(得分:3)

此线程中的许多答案都试图过度设计解决方案。如果我没错,你只想存储用户设置。

使用.ini文件或App.Config文件。

如果我错了,并且您存储的数据不仅仅是设置,请使用csv格式的平面文本文件。这些都是快速而简单的,没有XML的开销。人们喜欢大便这些因为它们不那么优雅,不能很好地扩展,并且在简历上看起来不那么好,但根据你的需要,它可能是最好的解决方案。

答案 11 :(得分:2)

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

它轻巧自由。此外,您可以坚持编写可在其他项目中重用的数据访问层,如果应用程序需要扩展到更大的类似完整的SQL Server,您只需要更改连接字符串。

答案 12 :(得分:0)

如果您使用二进制序列化路由,请考虑需要访问该数据的特定成员的速度。如果它只是一个小集合,加载整个文件是有意义的,但如果它很大,你也可以考虑一个索引文件。

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

答案 13 :(得分:0)

根据您的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文件夹中,位于程序的子文件夹中。这是当前用户始终可以写入的位置,并且操作系统本身可以保护其他用户免受安全。

答案 14 :(得分:0)

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

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

答案 15 :(得分:0)

不知道您的数据是什么样的,即复杂性,大小等...... XML易于维护且易于访问。我不会使用Access数据库,并且平面文件在长期内更难维护,特别是如果您处理文件中的多个数据字段/元素。

我每天处理大量平面文件数据,即使是一个极端的例子,平面文件数据比我处理的XML数据源更难维护。

使用C#将XML数据加载到数据集中的简单示例:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

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

... HTH

答案 16 :(得分:0)

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

最后一个选项,以及许多应用程序现在使用的是Windows注册表。我个人不推荐它(Registry Bloat,Corruption,其他潜在的问题),但这是一个选择。

答案 17 :(得分:0)

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

答案 18 :(得分:0)

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