web-dev-qa-db-fra.com

Meilleure façon de stocker des données localement dans .NET (C #)

J'écris une application qui prend les données de l'utilisateur et les stocke localement pour une utilisation ultérieure. L’application sera démarrée et arrêtée assez souvent, et j’aimerais qu’elle enregistre/charge les données au début/à la fin de l’application.

Ce serait assez simple si j'utilisais des fichiers plats, car les données n'ont pas vraiment besoin d'être sécurisées (elles ne seront stockées que sur ce PC). Les options que je crois sont donc:

  • Fichiers plats
  • XML
  • Base de données SQL

La maintenance des fichiers plats nécessite un peu plus d'effort (pas de classes intégrées comme avec XML), mais je n'avais jamais utilisé XML auparavant, et SQL semble excessif pour cette tâche relativement facile.

Existe-t-il d'autres pistes à explorer? Si non, laquelle de ces solutions est la meilleure?


Edit: Pour ajouter un peu plus de données au problème, la seule chose que je voudrais stocker est un dictionnaire qui ressemble à ceci

Dictionary<string, List<Account>> 

où compte est un autre type personnalisé.

Serais-je sérialiser le dict en tant que xmlroot, puis le type de compte en tant qu'attributs?


Mise à jour 2:

Il est donc possible de sérialiser un dictionnaire. Ce qui est compliqué, c’est que la valeur de ce dict est un générique lui-même, qui est une liste de structures de données complexes de type Account. Chaque compte est assez simple, c'est juste un groupe de propriétés.

Je crois comprendre que le but ici est d’essayer de finir avec ceci:

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

Comme vous pouvez le voir, l'héritage est 

  • Nom d'utilisateur (chaîne de dict)> 
  • Compte (chaque compte dans la liste)> 
  • Données de compte (propriétés de classe). 

Obtenir cette mise en page à partir d'un Dictionary<Username, List<Account>> est la partie la plus délicate et l’essence même de cette question.

Il y a beaucoup de réponses "comment faire" ici sur la sérialisation, ce qui est de ma faute puisque je ne l'ai pas précisé plus tôt, mais je cherche maintenant une solution définitive.

64
George

Je stockais le fichier sous la forme _ JSON . Puisque vous stockez un dictionnaire qui est juste une liste de paires nom/valeur, c’est à peu près ce pour quoi json a été conçu.
Il y a pas mal de librairies .NET json gratuites et décentes - voici one mais vous pouvez trouver une liste complète sur le premier lien.

22
zebrabox

Cela dépend vraiment de ce que vous stockez. Si vous parlez de données structurées, alors un fichier XML ou un SGBDB très léger tel que SQLite ou SQL Server Compact Edition convient parfaitement. La solution SQL devient particulièrement intéressante si les données dépassent une taille triviale.

Si vous stockez de grandes quantités de données relativement non structurées (objets binaires tels que des images, par exemple), il est évident qu'aucune base de données ni solution XML ne convient, mais compte tenu de votre question, je suppose qu'il s'agit davantage du premier que du dernier.

23
Adam Robinson

XML est facile à utiliser, via la sérialisation. Utilisez Stockage isolé

Voir aussi Comment décider où stocker l'état par utilisateur? Enregistrement? Données d'application? Stockage isolé?

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

Toutes les réponses ci-dessus sont de bonnes réponses et résolvent généralement le problème.

Si vous avez besoin d'un moyen simple et gratuit pour mettre à l'échelle des millions de données, essayez le projet d'interface gérée ESENT sur CodePlex .

ESENT est un moteur de stockage de base de données intégrable (ISAM) intégré à Windows. Il fournit un stockage de données fiable, traité, simultané et hautes performances avec un verrouillage au niveau de la ligne, une journalisation à écriture anticipée et une isolation de capture instantanée. Il s'agit d'un wrapper géré pour l'API ESENT Win32.

Il a un objet PersistentDictionary qui est assez facile à utiliser. Considérez-le comme un objet Dictionary (), mais il est automatiquement chargé à partir de et enregistré sur le disque sans code supplémentaire.

Par exemple:

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

Pour répondre à la question de George:

Types de clés pris en charge

Seuls ces types sont pris en charge en tant que clés du dictionnaire:

Octet booléen Int16 UInt16 Int32 UInt32 Int64 UInt64 Float Double Guid DateTime TimeSpan String 

Types de valeur pris en charge

Les valeurs du dictionnaire peuvent être n’importe lequel des types de clé, versions nullables du fichier types de clé, Uri, IPAddress ou a structure sérialisable. Une structure est considéré comme sérialisable uniquement s'il répond à tous ces critères:

• La structure est marquée comme sérialisable • Chaque membre du struct est soit: 1. Un type de données primitif (par exemple, Int32) 2. Une chaîne, un Uri ou une adresse IP 3. Une structure sérialisable.

Ou, pour le dire autrement, un La structure sérialisable ne peut pas contenir toute référence à un objet de classe. Ce est fait pour préserver la cohérence de l'API . Ajouter un objet à un PersistentDictionary crée une copie de l'objet si la sérialisation . La modification de l'objet d'origine ne sera pas modifier la copie, ce qui conduirait à comportement déroutant. Pour les éviter problèmes le PersistentDictionary sera n'accepte que les types de valeur en tant que valeurs.

Peut être sérialisé [Sérialisable] struct Bon { DateTime publique? Reçu; chaîne publique Nom; Prix ​​décimal public; public Uri Url; } 

Ne peut pas être sérialisé struct [Serializable] Bad { octet public [] Données; // les tableaux ne sont pas supportés erreur d'exception publique; // objet de référence}

11
GalacticJello

Je recommande la classe de lecteur/graveur XML pour les fichiers car elle est facilement sérialisée. 

Sérialisation en C #

La sérialisation (appelée pickling in Python) permet de convertir facilement un fichier object à une représentation binaire qui peut alors être par exemple écrit sur le disque ou envoyé sur un fil.

C'est utile par exemple pour enregistrer facilement les paramètres dans un fichier.

Vous pouvez sérialiser vos propres classes si vous les marquez avec [Serializable] attribut. Ceci sérialise tous les membres d'une classe, sauf ceux marqués comme [NonSerialized].

Ce qui suit est un code pour vous montrer comment faire ceci:

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

Ensuite, dans un ConfigForm, appelez votre classe ConfigManager et sérialisez-la!

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

Une fois qu'il a été sérialisé, vous pouvez appeler les paramètres de votre fichier de configuration à l'aide de cm.WindowTitle, etc.

8
user195488

Une quatrième option à ceux que vous mentionnez sont fichiers binaires . Bien que cela semble banal et difficile, c'est vraiment facile avec l'API de sérialisation dans .NET.

Que vous choisissiez des fichiers binaires ou XML, vous pouvez utiliser la même API de sérialisation, bien que vous utilisiez des sérialiseurs différents.

Pour sérialiser une classe binaire, elle doit être marquée avec l'attribut [Serializable] ou mettre en œuvre ISerializable.

Vous pouvez faire quelque chose de similaire avecXML, bien que l'interface s'appelle IXmlSerializable et que les attributs soient [XmlRoot] et d'autres attributs de l'espace de noms System.Xml.Serialization.

Si vous souhaitez utiliser une base de données relationnelle, SQL Server Compact Edition est gratuit, très léger et basé sur un seul fichier.

7
Mark Seemann

Si votre collection devient trop volumineuse, j'ai constaté que la sérialisation XML était assez lente. Une autre option pour sérialiser votre dictionnaire serait de "rouler vous-même" en utilisant BinaryReader et BinaryWriter. 

Voici quelques exemples de code pour vous aider à démarrer. Vous pouvez faire en sorte que ces méthodes d’extension génériques gèrent tout type de dictionnaire, et cela fonctionne assez bien, mais il est trop détaillé pour pouvoir être publié ici.

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

Je viens de terminer le codage du stockage de données pour mon projet actuel. Voici mes 5 cents.

J'ai commencé avec la sérialisation binaire. C'était lent (environ 30 secondes pour une charge de 100 000 objets) et cela créait également un assez gros fichier sur le disque. Cependant, il m'a fallu quelques lignes de code pour le mettre en œuvre et tous mes besoins en stockage ont été couverts. Pour obtenir de meilleures performances, je suis passé à la sérialisation personnalisée. Cadre FastSerialization trouvé par Tim Haynes sur Code Project. En effet, il est quelques fois plus rapide (12 secondes pour le chargement, 8 secondes pour la sauvegarde, 100 000 enregistrements) et prend moins d’espace disque. Le framework est construit sur la technique décrite par GalacticJello dans un post précédent. 

Ensuite, je suis passé à SQLite et j'ai pu obtenir des performances parfois 2 fois plus rapides: 6 secondes pour le chargement et 4 secondes pour la sauvegarde, 100 000 enregistrements. Il comprend l’analyse des tables ADO.NET en types d’application. Il m'a également donné beaucoup plus petit fichier sur le disque. Cet article explique comment tirer le meilleur parti de ADO.NET: http://sqlite.phxsoftware.com/forums/t/134.aspx . Générer des instructions INSERT est une très mauvaise idée. Vous pouvez deviner comment j'ai appris à ce sujet. :) En effet, la mise en œuvre de SQLite m'a pris pas mal de temps, plus une mesure minutieuse du temps pris par pratiquement toutes les lignes du code.

5
ACH

Si vos données sont complexes, volumineuses ou si vous devez les interroger localement, les bases de données objet peuvent constituer une option valide. Je suggérerais de regarder Db4o ou Karvonite .

4
Goran

La première chose que je regarderais est une base de données. Cependant, la sérialisation est une option. Si vous optez pour la sérialisation binaire, alors je éviterais BinaryFormatter - il a tendance à se fâcher entre les versions si vous modifiez les champs, etc. Xml via XmlSerialzier serait correct, et peut être compatible côte à côte (avec les mêmes définitions de classe) avec protobuf-net si vous souhaitez essayer la sérialisation binaire basée sur un contrat (vous offrant ainsi un sérialiseur de fichiers à plat sans effort).

4
Marc Gravell

Une grande partie des réponses de ce fil de discussion tentent de trop modifier la solution. Si je ne me trompe pas, vous voulez simplement stocker les paramètres utilisateur.

Utilisez un fichier .ini ou App.Config pour cela.

Si je me trompe et que vous stockez des données autres que des paramètres, utilisez un fichier texte au format CSV. Celles-ci sont rapides et faciles sans la surcharge de XML. Les gens aiment faire caca, car ils ne sont pas aussi élégants, ne s’adaptent pas bien et ne paraissent pas aussi beaux sur un CV, mais c’est peut-être la meilleure solution pour vous, selon vos besoins.

3
James

J'ai réalisé plusieurs applications "autonomes" avec un magasin de données local. Je pense que la meilleure chose à utiliser serait SQL Server Compact Edition (anciennement SQLAnywhere).

C'est léger et gratuit. En outre, vous pouvez vous limiter à l'écriture d'une couche d'accès aux données réutilisable dans d'autres projets. Si l'application doit être redimensionnée à une taille supérieure à un serveur SQL complet, il vous suffit de modifier la chaîne de connexion.

2
HitLikeAHammer

Si vous choisissez la route de sérialisation binaire, prenez en compte la vitesse à laquelle un membre particulier de la donnée doit être accédé. S'il ne s'agit que d'une petite collection, le chargement du fichier entier aura du sens, mais s'il est volumineux, vous pouvez également envisager un fichier d'index. 

Le suivi des propriétés/des champs de compte situés à une adresse spécifique dans le fichier peut vous aider à accélérer le temps d'accès, en particulier si vous optimisez ce fichier d'index en fonction de l'utilisation de la clé. (éventuellement même lorsque vous écrivez sur le disque.)

0
Todd Richardson

Restez simple - comme vous l'avez dit, un fichier plat suffit. Utilisez un fichier plat.

Cela suppose que vous avez correctement analysé vos besoins. Je voudrais sauter l'étape de la sérialisation en tant que XML, overkill pour un dictionnaire simple. Même chose pour une base de données.

0
Larry Watanabe

En fonction de la compelexity de votre objet Account, je recommanderais soit un fichier XML, soit un fichier plat.

S'il ne reste que quelques valeurs à stocker pour chaque compte, vous pouvez les stocker dans un fichier de propriétés, comme ceci:

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

... et ainsi de suite. La lecture à partir d'un fichier de propriétés devrait être facile, car elle correspond directement à un dictionnaire de chaînes.

Pour ce qui est de savoir où stocker ce fichier, le meilleur choix serait de le stocker dans le dossier AppData, dans un sous-dossier de votre programme. C'est un emplacement où les utilisateurs actuels auront toujours accès à l'écriture, et le système d'exploitation le protègera des autres utilisateurs.

0
Pablo Venturino

Ma première inclination est une base de données d'accès. Les fichiers .mdb sont stockés localement et peuvent être chiffrés si cela est jugé nécessaire. Bien que XML ou JSON puisse également fonctionner pour de nombreux scénarios. Fichiers plats que je n'utiliserais que pour des informations en lecture seule et sans recherche. J'ai tendance à préférer le format csv pour définir la largeur.

0
Matthew Vines

Cela dépend de la quantité de données que vous souhaitez stocker. En réalité, il n'y a pas de différence entre les fichiers plats et XML. XML serait probablement préférable car il fournit une structure au document. En pratique, 

La dernière option, utilisée par de nombreuses applications, est le registre Windows. Personnellement, je ne le recommande pas (bouffée du registre, corruption, autres problèmes potentiels), mais c'est une option.

0
blacksol

Dans mon expérience, dans la plupart des cas, le format JSON dans un fichier est suffisant (vous devez généralement stocker un tableau ou un objet, ou tout simplement un nombre unique ou une chaîne). J'ai rarement besoin de SQLite (qui a besoin de plus de temps pour le configurer et l'utiliser, la plupart du temps, c'est excessif).

0
JedatKinports

Sans savoir à quoi ressemblent vos données, c’est-à-dire leur complexité, leur taille, etc., XML est facile à gérer et facilement accessible. Je ne voudrais PAS utiliser une base de données Access, et les fichiers plats sont plus difficiles à gérer sur le long terme, en particulier si vous traitez avec plus d'un champ/élément de données dans votre fichier.

Je traite quotidiennement de grandes quantités de flux de données de fichiers plats volumineux, et même s’il est un exemple extrême, les données de fichiers plats sont beaucoup plus difficiles à gérer que les flux de données XML que je traite.

Un exemple simple de chargement de données XML dans un ensemble de données à l'aide de C #:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

Vous pouvez également utiliser LINQ to XML pour interroger les données XML ...

HTH ...

0
Tom Miller