How to store data locally in .NET (C#) [closed]
Asked Answered
T

19

81

I'm writing an application that takes user data and stores it locally for use later. The application will be started and stopped fairly often, and I'd like to make it save/load the data on application start/end.

It'd be fairly straightforward if I used flat files, as the data doesn't really need to be secured (it'll only be stored on this PC). The options I believe are thus:

  • Flat files
  • XML
  • SQL DB

Flat files require a bit more effort to maintain (no built in classes like with XML), however I haven't used XML before, and SQL seems like overkill for this relatively easy task.

Are there any other avenues worth exploring? If not, which of these is the best solution?


Edit: To add a little more data to the problem, basically the only thing I'd like to store is a Dictionary that looks like this

Dictionary<string, List<Account>> 

where Account is another custom type.

Would I serialize the dict as the xmlroot, and then the Account type as attributes?


Update 2:

So it's possible to serialize a dictionary. What makes it complicated is that the value for this dict is a generic itself, which is a list of complex data structures of type Account. Each Account is fairly simple, it's just a bunch of properties.

It is my understanding that the goal here is to try and end up with this:

<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>

As you can see the heirachy is

  • Username (string of dict) >
  • Account (each account in the List) >
  • Account data (ie class properties).

Obtaining this layout from a Dictionary<Username, List<Account>> is the tricky bit, and the essence of this question.

There are plenty of 'how to' responses here on serialisation, which is my fault since I didn't make it clearer early on, but now I'm looking for a definite solution.

Truncated answered 21/12, 2009 at 19:0 Comment(2)
give more ditails about the kind of application, and the data stored, also the excpected sizrNightrider
For serializing a dictionary: stackoverflow.com/questions/1111724Honourable
L
29

I'd store the file as JSON. Since you're storing a dictionary which is just a name/value pair list then this is pretty much what json was designed for.
There a quite a few decent, free .NET json libraries - here's one but you can find a full list on the first link.

Lover answered 21/12, 2009 at 20:19 Comment(2)
json is a bit unusual for local storage, but it's definitely a great solution. Especially with a library like Newtonsoft's Json.NETSelry
instead of relying on a 3rd party library i would store the data using the built-in dataset type which is super simple to write to disk (see Tom Miller's example below)Acyclic
B
24

It really depends on what you're storing. If you're talking about structured data, then either XML or a very lightweight SQL RDBMS like SQLite or SQL Server Compact Edition will work well for you. The SQL solution becomes especially compelling if the data moves beyond a trivial size.

If you're storing large pieces of relatively unstructured data (binary objects like images, for example) then obviously neither a database nor XML solution are appropriate, but given your question I'm guessing it's more of the former than the latter.

Bouldin answered 21/12, 2009 at 19:3 Comment(2)
XML config files have to be structured?Cannell
@Roboto: XML, by definition, is structured. That doesn't mean you have to use them in a highly-structured manner, however.Bouldin
S
18

All of the above are good answers, and generally solve the problem.

If you need an easy, free way to scale to millions of pieces of data, try out the ESENT Managed Interface project on GitHub or from NuGet.

ESENT is an embeddable database storage engine (ISAM) which is part of Windows. It provides reliable, transacted, concurrent, high-performance data storage with row-level locking, write-ahead logging and snapshot isolation. This is a managed wrapper for the ESENT Win32 API.

It has a PersistentDictionary object that is quite easy to use. Think of it as a Dictionary() object, but it is automatically loaded from and saved to disk without extra code.

For example:

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

To answer George's question:

Supported Key Types

Only these types are supported as dictionary keys:

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

Supported Value Types

Dictionary values can be any of the key types, Nullable versions of the key types, Uri, IPAddress or a serializable structure. A structure is only considered serializable if it meets all these criteria:

• The structure is marked as serializable • Every member of the struct is either: 1. A primitive data type (e.g. Int32) 2. A String, Uri or IPAddress 3. A serializable structure.

Or, to put it another way, a serializable structure cannot contain any references to a class object. This is done to preserve API consistency. Adding an object to a PersistentDictionary creates a copy of the object though serialization. Modifying the original object will not modify the copy, which would lead to confusing behavior. To avoid those problems the PersistentDictionary will only accept value types as values.

Can Be Serialized [Serializable] struct Good { public DateTime? Received; public string Name; public Decimal Price; public Uri Url; }

Can’t Be Serialized [Serializable] struct Bad { public byte[] Data; // arrays aren’t supported public Exception Error; // reference object }

Sachet answered 21/12, 2009 at 19:41 Comment(4)
This method is basically replacing the built in generic with a persistent dictionary. It's a pretty elegant solution, but how does it handle complex objects like in the OP example? Does it store everything inside the dict, or just the dict itself?Truncated
This could fall short in the ultimate goal of trying to save the lists of type Account. The key's would be fine, but making the generic serializable could be hard :/.Truncated
Anyone else implementing this might benefit from knowing that you can use Nuget to get ManagedEsent. Then you need to reference Esent.Collections.DLL and Esent.ISAM.DLL. Then add "using Microsoft.Isam.Esent.Collections.Generic;" to get PersistentDictionary type. The collections DLL may have to be downloaded from downloads option on managedesent.codeplex.comDunt
"ManagedEsent" has become "Microsoft.Database.ManagedEsent". You should use "Microsoft.Database.Collections.Generic" from nuget instead, because it includes both ManagedEsent and ISAM.Limitary
H
15

XML is easy to use, via serialization. Use Isolated storage.

See also 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();
    }
}
Honourable answered 21/12, 2009 at 19:2 Comment(6)
This isn't very portable nor neatCannell
It looks like cut-paste code so you can be the first poster. Would have been better to just stick with your links.Cannell
Well, yeah, I cut it right out of the app that I wrote, that does this. Roboto, what's yer problem?Honourable
It doesn't even use Dependency Injection! Didn't you get the memo?Wildon
I agree with the XML recommendation, but after trying it myself I hit a limitation of Isolated Storage - it creates different files depending which assembly the code is running from. Ended up just using AppData\Roaming via Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"{AppName}\{FileName}.XML");Coppola
You now have a broken link.Rash
C
9

I recommend XML reader/writer class for files because it is easily serialized.

Serialization in C#

Serialization (known as pickling in python) is an easy way to convert an object to a binary representation that can then be e.g. written to disk or sent over a wire.

It's useful e.g. for easy saving of settings to a file.

You can serialize your own classes if you mark them with [Serializable] attribute. This serializes all members of a class, except those marked as [NonSerialized].

The following is code to show you how to do this:

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

You then, in maybe a ConfigForm, call your ConfigManager class and Serialize it!

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

After it has been serialized, you can then call the parameters of your config file using cm.WindowTitle, etc.

Cannell answered 21/12, 2009 at 19:7 Comment(4)
Just to clarify: Serializable and NonSerialized don't have any effect on XmlSerializer; they're used only for System.Runtime.Serialization (e.g. binary serialisation). XmlSerializer serialises public fields and (read-write) properties, not internal state: no attribute needed on the class, and XmlIgnore instead of NonSerialized to exclude a field or property.Polyneuritis
@itowlson: Correct. XML serialization uses reflection to generate special classes to perform the serialization.Cannell
It would help when reading the code if it was indented without the random size...Jeannettajeannette
@Lasse: Not sure what you mean, but if it is too hard to read then you can edit itCannell
S
9

If your collection gets too big, I have found that Xml serialization gets quite slow. Another option to serialize your dictionary would be "roll your own" using a BinaryReader and BinaryWriter.

Here's some sample code just to get you started. You can make these generic extension methods to handle any type of Dictionary, and it works quite well, but is too verbose to post here.

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;
    }
}
Sachet answered 21/12, 2009 at 20:52 Comment(3)
Thanks, this looks like the best solution so far. What do you mean by keep in sync with Deserialize/Serialize? As in updating the file when its modified? This function will only be used on application start and exit to save the dict, so could you please clarify that? Otherwise thanks a lot.Truncated
After thinking about it for a bit, I've realised it means that the logic for serialising and deserialising should be the same. That is all.Truncated
Yes, that's all it means. So if you add another property to serialize/deserialize, just keep in mind that you have to add code to both the Serialize/Deserialize method, and keep them in the same order. A bit of maintenence, but the performance over Xml serialization is no comparison (several minutes to deserialize using xml, to a couple seconds using BinaryReader, with a few hundred thousand dictionary items).Sachet
A
7

A fourth option to those you mention are binary files. Although that sounds arcane and difficult, it's really easy with the serialization API in .NET.

Whether you choose binary or XML files, you can use the same serialization API, although you would use different serializers.

To binary serialize a class, it must be marked with the [Serializable] attribute or implement ISerializable.

You can do something similar with XML, although there the interface is called IXmlSerializable, and the attributes are [XmlRoot] and other attributes in the System.Xml.Serialization namespace.

If you want to use a relational database, SQL Server Compact Edition is free and very lightweight and based on a single file.

Altdorf answered 21/12, 2009 at 19:4 Comment(3)
Flat file != text file. I would think this would fall under the category of "flat file".Bouldin
You can binary serialize a class whether or not you are dealing with XML filesCannell
unless you need the serialized object to be human readable, this is the most reliable way to go. It serializes to a tiny file, and always seemed the fastest way to do it, in terms of how fast the code ran. And Mark is right, it seems arcane and difficult, but it is not at all. And binary serialization captures the ENTIRE object, even its private members, which XML serialization does not.Museology
E
7

Just finished coding data storage for my current project. Here is my 5 cents.

I started with binary serialization. It was slow (about 30 sec for load of 100,000 objects) and it was creating a pretty big file on the disk as well. However, it took me a few lines of code to implement and I got my all storage needs covered. To get better performance I moved on custom serialization. Found FastSerialization framework by Tim Haynes on Code Project. Indeed it is a few times faster (got 12 sec for load, 8 sec for save, 100K records) and it takes less disk space. The framework is built on the technique outlined by GalacticJello in a previous post.

Then I moved to SQLite and was able to get 2 sometimes 3 times faster performance – 6 sec for load and 4 sec for save, 100K records. It includes parsing ADO.NET tables to application types. It also gave me much smaller file on the disk. This article explains how to get best performance out of ADO.NET: http://sqlite.phxsoftware.com/forums/t/134.aspx. Generating INSERT statements is a very bad idea. You can guess how I came to know about that. :) Indeed, SQLite implementation took me quite a bit of time plus careful measurement of time taking by pretty much every line of the code.

Enfeeble answered 23/12, 2009 at 20:34 Comment(0)
F
5

The first thing I'd look at is a database. However, serialization is an option. If you go for binary serialization, then I would avoid BinaryFormatter - it has a tendency to get angry between versions if you change fields etc. Xml via XmlSerialzier would be fine, and can be side-by-side compatible (i.e. with the same class definitions) with protobuf-net if you want to try contract-based binary serialization (giving you a flat file serializer without any effort).

Friel answered 21/12, 2009 at 20:35 Comment(0)
I
4

If your data is complex, high in quantity or you need to query it locally then object databases might be a valid option. I'd suggest looking at Db4o or Karvonite.

Interact answered 21/12, 2009 at 19:40 Comment(0)
B
3

A lot of the answers in this thread attempt to overengineer the solution. If I'm correct, you just want to store user settings.

Use an .ini file or App.Config file for this.

If I'm wrong, and you are storing data that is more than just settings, use a flat text file in csv format. These are fast and easy without the overhead of XML. Folks like to poo poo these since they aren't as elegant, don't scale nicely and don't look as good on a resume, but it might be the best solution for you depending on what you need.

Brynne answered 21/12, 2009 at 19:37 Comment(3)
app.config vs custom XML: #1566398Cannell
I'm doing something slightly more complex than just settings. Each user might have multiple 'accounts' associated with their name. The dictionary links this name (string) to the list of accounts associated with them. I'd be storing a bunch of accounts for each user. It could work with xml but i'm not quite sure how to go about it.Truncated
In that case, I'd use the XmlSerializer class as mentioned. If you have a good grasp on OOP, it should be easy. Here's a good example: jonasjohn.de/snippets/csharp/xmlserializer-example.htmBrynne
E
2

Without knowing what your data looks like, i.e. the complexity, size, etc...XML is easy to maintain and easily accessible. I would NOT use an Access database, and flat files are more difficult to maintain over the long haul, particularly if you are dealing with more than one data field/element in your file.

I deal with large flat-file data feeds in good quantities daily, and even though an extreme example, flat-file data is much more difficult to maintain than the XML data feeds I process.

A simple example of loading XML data into a dataset using C#:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

You can also check out LINQ to XML as an option for querying the XML data...

HTH...

Egis answered 21/12, 2009 at 19:15 Comment(0)
C
1

I have done several "stand alone" apps that have a local data store. I think the best thing to use would be SQL Server Compact Edition (formerly known as SQLAnywhere).

It's lightweight and free. Additionally, you can stick to writing a data access layer that is reusable in other projects plus if the app ever needs to scale to something bigger like full blown SQL server, you only need to change the connection string.

Coastal answered 21/12, 2009 at 19:17 Comment(1)
I don't understand what you mean by "formerly known as SQLAnywhere". I don't see the connection between the 2 products. en.wikipedia.org/wiki/SQL_Anywhere & en.wikipedia.org/wiki/SQL_Server_CompactRa
G
1

Depending on the compelexity of your Account object, I would recomend either XML or Flat file.

If there are just a couple of values to store for each account, you could store them on a properties file, like this:

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

... and so forth. Reading from a properties file should be easy, as it maps directly to a string dictionary.

As to where to store this file, the best choise would be to store into AppData folder, inside a subfolder for your program. This is a location where current users will always have access to write, and it's kept safe from other users by the OS itself.

Grandmotherly answered 21/12, 2009 at 19:44 Comment(0)
G
0

My first inclination is an access database. The .mdb files are stored locally, and can be encrypted if that is deemed necessary. Though XML or JSON would also work for many scenarios. Flat files I would only use for read only, non-search (forward read only) information. I tend to prefer csv format to set width.

Goober answered 21/12, 2009 at 19:3 Comment(5)
Out of curiousity, why would you use Access unless it was already present or you needed access to it from, well, Access? Otherwise it seems like a lightweight SQL engine would be more advisable, especially with in-process options like SQLite and SQL Server CE available.Bouldin
The JET engine - which allows you to use .MDB files is, I think, installed with windows which is what makes use of .MDB files attactive as a solution, that and the fact that they're easy to dig into with access if you need to. However that predates the current incarnation of SQL Server CE which can be a .DLL "xcopy" deployment and is therefore a better way to achieve a broadly similar result.Debit
Friends don't let friends use AccessCannell
@Murph: Yes, JET is a Windows component now, but (as you point out) the XCOPY deployment (and in-process hosting) of SQL Server CE seem to eliminate any advantage that JET has, while retaining the disadvantages (limited and odd SQL syntax support, many ORM's don't support it, etc.). So...I guess my question still stands as to why you'd recommend it :)Bouldin
As nearly all of my teachers stated: Access is not a real database :)Lipkin
A
0

It depends on the amount of data you are looking to store. In reality there's no difference between flat files and XML. XML would probably be preferable since it provides a structure to the document. In practice,

The last option, and a lot of applications use now is the Windows Registry. I don't personally recommend it (Registry Bloat, Corruption, other potential issues), but it is an option.

Agentive answered 21/12, 2009 at 19:6 Comment(1)
One area where flat files differ from XML is representing hierarchical data. You can represent hierarchies in flat files, sure, but it's easier to do so in XML.Polyneuritis
F
0

If you go the binary serialization route, Consider the speed at which a particular member of the datum needs to be accessed. If it is only a small collection, loading the whole file will make sense, but if it will be large, you might also consider an index file.

Tracking Account Properties/fields that are located at a specific address within the file can help you speed up access time, especially if you optimize that index file based on key usage. (possibly even when you write to disk.)

Freyah answered 21/12, 2009 at 19:37 Comment(0)
R
0

Keep it simple - as you said, a flat file is sufficient. Use a flat file.

This is assuming that you have analyzed your requirements correctly. I would skip the serializing as XML step, overkill for a simple dictionary. Same thing for a database.

Rikki answered 21/12, 2009 at 20:23 Comment(0)
R
0

In my experience in most cases JSON in a file is enough (mostly you need to store an array or an object or just a single number or string). I rarely need SQLite (which needs more time for setting it up and using it, most of the times it's overkill).

Roche answered 12/5, 2017 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.