2009-12-21 119 views
53

我正在編寫一個應用程序,它將用戶數據存儲在本地以供以後使用。應用程序將經常啓動和停止,並且我想讓它在應用程序的開始/結束時保存/加載數據。在.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://stackoverflow.com/questions/1111724 – Cheeso 2009-12-21 19:28:37

回答

20

我會將文件存儲爲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

13

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

21

見這真的取決於你要存儲什麼。如果您正在討論結構化數據,那麼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

0

我的第一個傾向是訪問數據庫。 .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

7

您提到的第四個選項是二進制文件。儘管這聽起來很神祕和困難,但使用.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

0

這取決於您要存儲的數據量。實際上,平面文件和XML之間沒有區別。 XML可能會更好,因爲它爲文檔提供了一個結構。實際上,

最後一個選項,現在很多應用程序使用Windows註冊表。我個人不推薦它(註冊表膨脹,腐敗,其他潛在問題),但它是一種選擇。

+0

平面文件與XML不同的一個領域是表示分層數據。當然,您可以在平面文件中表示層次結構,但在XML中這樣做更容易。 – itowlson 2009-12-21 19:09:10

8

我推薦使用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

0

的參數,不知道你的數據是什麼樣子,也就是複雜性,大小等..XML易於維護並且易於訪問。我不會使用Access數據庫,並且平面文件在長時間內更難以維護,特別是在您處理文件中的多個數據字段/元素的情況下。

我每天都會處理大量的平面文件數據饋送,而且即使是極端的例子,平面文件數據也比我處理的XML數據饋送難以維護。

加載XML數據的簡單例子爲使用C#中的數據集:

DataSet reportData = new DataSet(); 

reportData.ReadXml(fi.FullName); 

您還可以檢查出的LINQ to XML爲查詢XML數據的選項...

HTH ..

2

我已經完成了幾個具有本地數據存儲的「獨立」應用程序。我認爲最好使用的是SQL Server Compact Edition(以前稱爲SQLAnywhere)。

它輕巧且免費。此外,您可以堅持編寫可在其他項目中重複使用的數據訪問層,如果應用程序需要擴展到更大型的SQL Server,則只需更改連接字符串。

0

如果進入二進制序列化路徑,請考慮數據的特定成員需要訪問的速度。如果它只是一個小集合,加載整個文件將是有意義的,但如果它會很大,您可能還會考慮一個索引文件。

位於文件中特定地址的跟蹤帳戶屬性/字段可幫助您加快訪問時間,尤其是在您根據密鑰使用情況優化索引文件時。 (甚至當你寫入磁盤時)。

3

在這個線程中的很多答案試圖過度工程解決方案。如果我是正確的,您只需要存儲用戶設置。

爲此,請使用.ini文件或App.Config文件。

如果我錯了,而且您存儲的數據不僅僅是設置,請使用csv格式的平面文本文件。這些既快速又簡單,無需XML的開銷。由於他們不夠優雅,不喜歡很好的規模,在簡歷上看起來不太好,所以他們喜歡大肆宣傳,但根據你的需求,這可能是最好的解決方案。

+0

的app.config VS自定義XML: http://stackoverflow.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

4

如果您的數據很複雜,數量較多或者您需要在本地查詢,那麼對象數據庫可能是一個有效的選項。我建議看看Db4oKarvonite

10

以上所有都是很好的答案,一般可以解決問題。

如果您需要一種簡單,免費的方法來擴展到數百萬條數據,請嘗試使用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

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文件夾存儲在您的程序的子文件夾中。這是當前用戶總是可以寫入的位置,並且操作系統本身對其他用戶保持安全。

0

保持簡單 - 正如你所說,一個平面文件就足夠了。使用平面文件。

這是假設您已正確分析了您的要求。我會跳過序列化作爲XML步驟,矯枉過正一個簡單的字典。數據庫也是一樣。

4

我看的第一件事就是數據庫。但是,序列化是一種選擇。如果你去二進制序列化,那麼我會避免BinaryFormatter - 它有一個在版本之間生氣的趨勢,如果你改變字段等Xml通過XmlSerialzier會很好,可以並排兼容(即與如果您想嘗試基於合同的二進制序列化(無需任何費力就可以爲您提供平面文件序列化程序),您可以使用protobuf-net。

5

如果你的集合變得太大,我發現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

剛完成編碼我當前項目的數據存儲。這是我的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的實現讓我花費了相當多的時間,仔細測量了幾乎每一行代碼的時間。

0

根據我的經驗,在大多數情況下,文件中的JSON就足夠了(大多數情況下,您需要存儲數組或對象或只是一個數字或字符串)。我很少需要SQLite(需要更多的時間來設置和使用它,大部分時間它是過度的)。

相關問題