Meilleure façon de stocker des données localement in.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 va être démarré et arrêté assez souvent, et j'aimerais faire enregistrer/charger les données sur l'application de début/fin.

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
  • SQL DB

les fichiers plats nécessitent un peu plus d'effort à maintenir (pas de classes intégrées comme XML), cependant je n'ai pas utilisé XML avant, et SQL semble comme un surmenage pour cette tâche relativement facile.

y a-t-il d'autres pistes à explorer? Si non, quel est la meilleure solution?


Edit: pour ajouter un peu plus de données au problème, fondamentalement le seul ce que je voudrais stocker est un dictionnaire qui ressemble à ceci

Dictionary<string, List<Account>> 

où le compte est un autre type personnalisé.

est-ce que je sérialise le dict comme xmlroot, puis le type de Compte comme attributs?


mise à Jour 2:

il est Donc possible de sérialiser un dictionnaire. Ce qui rend la chose compliquée, c'est que la valeur de ce dict est un générique lui-même, qui est une liste de données complexes. structure du compte de type. Chaque Compte est assez simple, c'est juste un tas 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 voir le heirachy est

  • nom D'utilisateur (chaîne de caractères de dict) >
  • Compte
  • (chaque compte de la liste) >
  • Compte de données (c'est à dire des propriétés de la classe).

L'obtention de cette disposition à partir d'un Dictionary<Username, List<Account>> est le point délicat, et l'essence de cette question.

il y a beaucoup de réponses "Comment faire" ici sur la sérialisation, ce qui est ma faute car je ne l'ai pas rendu plus clair au début, mais maintenant je cherche une solution définitive.

58
demandé sur George 2009-12-21 22:00:40

19 réponses

Je stockerais le fichier comme JSON . Puisque vous stockez un dictionnaire qui est juste une liste de paire de nom/valeur alors c'est à peu près ce que json a été conçu pour.

Il y a pas mal de bibliothèques. net JSON décentes et gratuites - voici une mais vous pouvez trouver une liste complète sur le premier lien.

20
répondu zebrabox 2009-12-21 20:19:27

Cela dépend vraiment de ce que vous stockez. Si vous parlez de données structurées, alors soit XML soit un RDBMS SQL très léger comme SQLite ou SQL Server Compact Edition fonctionnera bien pour vous. La solution SQL devient particulièrement convaincante si les données dépassent une taille triviale.

si vous stockez de gros morceaux de données relativement non structurées (objets binaires comme des images, par exemple), alors évidemment, ni une base de données ni une solution XML ne sont appropriés, mais vu votre question, je suppose que c'est plus la première que la seconde.

21
répondu Adam Robinson 2009-12-21 19:03:25

XML est facile à utiliser, via la sérialisation. Utiliser stockage isolé .

Voir aussi Comment décider où stocker l'état par utilisateur? Registre? AppData? Le 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();
    }
}
13
répondu Cheeso 2017-05-23 11:54:54

Tout ce qui précède sont de bonnes réponses, et généralement résoudre le problème.

si vous avez besoin d'un moyen facile, libre à l'échelle à des millions de morceaux de données, essayez le projet ESENT Managed Interface sur CodePlex .

ESENT est un moteur de stockage de base de données intégrable (ISAM) qui fait partie de Windows. Il offre un stockage de données fiable, transactionnel, concurrent, haute performance avec un verrouillage de niveau de rangée, en écriture-ahead la journalisation et l'isolement d'instantané. Il s'agit d'un gestionnaire d'enrubannage pour L'API ESENT Win32.

il a un Objectifdictionnaire persistant qui est assez facile à utiliser. Pensez - y comme un objet Dictionary (), mais il est automatiquement chargé à partir 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 comme touches du dictionnaire:

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

Prise En Charge Des Types De Valeur

les valeurs du dictionnaire peuvent être types de clés, versions nulles de la types de clés, Uri, IPAddress ou a structure sérialisable. Un la structure est seulement considéré comme sérialisable s'il satisfait à tous ces critères:

• la structure est marquée comme suit: serialisable • chaque membre du struct est soit: 1. Un type de données primitif (par exemple Int32) 2. Une chaîne de caractères, Uri ou IPAddress 3. Une structure sérialisable.

ou, pour le dire autrement, 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. Additionneur un objet à un PersistentDictionary crée une copie de l'objet de la sérialisation. Modifier l'objet original ne modifier la copie, ce qui conduirait à confusion comportement. Pour éviter ces les problèmes de la persistance de la volonté gouvernementale n'accepte que les types de valeurs comme valeurs.

Can Be Serialized [Serialisable] struct Good { public DateTime? Reçu; public string Name; prix décimal public; Uri public Url;}

ne Peut pas Être Sérialisés [Serializable] struct Mauvais { données des octets publics []; / / les tableaux ne sont pas pris en charge erreur d'Exception publique; / / objet de référence}

10
répondu GalacticJello 2009-12-21 20:14:01

je recommande la classe XML reader/writer pour les fichiers car elle est facilement sérialisée.

Sérialisation en C#

Sérialisation (connu sous le nom de décapage dans python) est un moyen facile de convertir un l'objet d'une représentation binaire qui peut alors être écrit sur le disque ou envoyé d'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. Cette sérialise tous les membres de la classe, à l'exception de ceux marqués comme [NonSerialized] .

le code suivant est 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;
            }
        }
    }
}

vous alors, dans Peut-être une configuration, 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);
    }
}

après avoir été sérialisé, vous pouvez ensuite, appelez les paramètres de votre fichier de configuration en utilisant cm.WindowTitle, etc.

8
répondu kmote 2011-12-22 23:26:45

une quatrième option à ceux que vous mentionnez sont fichiers binaires . Bien que cela semble étrange et difficile, c'est vraiment facile avec L'API de sérialisation .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 binaire une classe, elle doit être marquée avec l'attribut [Serialisable] ou implémenter Iserialisable.

vous pouvez faire quelque chose de similaire avec XML , bien que là l'interface est appelée IXmlSerializable, et les attributs sont [XmlRoot] et d'autres attributs dans le système.XML.Namespace de sérialisation.

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

7
répondu Mark Seemann 2009-12-21 19:04:10

vient de terminer le stockage de données de codage pour mon projet en cours. Voilà mes 5 cents.

j'ai commencé avec la sérialisation binaire. Il était lent (environ 30 sec pour charge de 100.000 objets) et il créait un assez grand fichier sur le disque ainsi. Cependant, il m'a fallu quelques lignes de code à mettre en œuvre et j'ai obtenu tous mes besoins de stockage couverts. Pour obtenir de meilleures performances, Je suis passé à la sérialisation personnalisée. Trouvé le cadre de FastSerialization par Tim Haynes sur le projet de Code. En effet, il est un peu plus rapide (12 sec pour la charge, 8 sec pour l'enregistrement, 100K) et il prend moins d'espace disque. Le cadre est construit sur la technique esquissée par GalacticJello dans un post précédent.

puis j'ai déménagé à SQLite et j'ai pu obtenir 2 Performances parfois 3 fois plus rapides – 6 sec pour la charge et 4 sec pour enregistrer, 100k records. Il comprend l'analyse ADO.NET tableaux des types d'application. Il m'a aussi donné beaucoup plus petit fichier sur le disque. Cet article explique comment obtenir le meilleur performances hors 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 ça. :) En effet, L'implémentation de SQLite m'a pris pas mal de temps en plus d'une mesure soigneuse du temps pris par à peu près toutes les lignes du code.

5
répondu ACH 2009-12-23 20:34:23

si votre collection devient trop importante, j'ai trouvé que la sérialisation Xml est assez lente. Une autre option pour sérialiser votre dictionnaire serait "rouler" à l'aide d'un BinaryReader et BinaryWriter.

voici un exemple de code pour commencer. Vous pouvez faire ces méthodes d'extension génériques pour manipuler n'importe quel type de dictionnaire, et cela fonctionne assez bien, mais est trop verbeux pour poster 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;
    }
}
5
répondu GalacticJello 2015-05-30 10:25:04

si vos données sont complexes, en grande quantité ou si vous avez besoin de les interroger localement, alors les bases de données d'objets peuvent être une option valide. Je suggère de regarder Db4o ou Karvonite .

4
répondu Goran 2009-12-21 19:40:06

La première chose que je regarde est une base de données. Cependant, la sérialisation est une option. Si vous optez pour la sérialisation binaire, alors je éviter BinaryFormatter - il a tendance à se fâcher entre les versions si vous changez les champs, etc. Xml via XmlSerialzier serait très bien, et peut être compatible côte à côte (c.-à-d. avec les mêmes définitions de classe) avec protobuf-net si vous voulez essayer la sérialisation binaire basée sur le contrat (vous donnant un serialiseur de fichier plat sans aucune effort.)

4
répondu Marc Gravell 2009-12-21 20:35:50

beaucoup de réponses dans ce fil tentent de surenchérir la solution. Si j'ai raison, vous voulez juste stocker les paramètres de l'utilisateur.

utiliser un .ini fichier ou d'une Application.Fichier de configuration pour cette.

si je me trompe, et que vous stockez des données qui sont plus que des paramètres, utilisez un fichier texte Plat au format csv. Ceux-ci sont rapides et faciles sans la surbrillance de XML. Les gens aiment faire caca ceux-ci car ils ne sont pas aussi élégant, ne pas échelle bien et ne pas regarder comme bon sur un cv, mais il pourrait être la meilleure solution pour vous en fonction de ce que vous avez besoin.

3
répondu James 2009-12-21 19:37:50

j'ai fait plusieurs applications" autonomes " qui ont un magasin de données local. Je pense que la meilleure chose à utiliser serait SQL Server Compact Edition (anciennement connu sous le nom de SQLAnywhere).

c'est léger et gratuit. En outre, vous pouvez vous en tenir à l'écriture d'une couche d'accès aux données qui est réutilisable dans d'autres projets plus si l'application a besoin jamais de passer à quelque chose de plus grand comme serveur SQL complet, vous avez seulement besoin de changer la chaîne de connexion.

2
répondu HitLikeAHammer 2009-12-21 19:17:15

mon premier penchant est une base de données d'accès. Le. les fichiers mdb sont stockés localement et peuvent être cryptés si cela est jugé nécessaire. Bien que XML ou JSON fonctionnerait aussi pour de nombreux scénarios. Fichiers plats que je n'utiliserais que pour des informations en lecture seule, sans recherche (en lecture seule). J'ai tendance à préférer le format csv pour définir la largeur.

0
répondu Matthew Vines 2009-12-21 19:03:33

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

la dernière option, et beaucoup d'applications utilisent maintenant est le Registre de Windows. Je ne le recommande pas personnellement (Bloat de Registre, la Corruption, d'autres questions potentielles), mais il est une option.

0
répondu blacksol 2009-12-21 19:06:18

sans savoir à quoi ressemblent vos données, c'est-à-dire la complexité, la taille, etc...XML est facile à entretenir et facilement accessible. Je N'utiliserais pas de base de données D'accès, et les fichiers plats sont plus difficiles à maintenir à long terme, surtout si vous avez affaire à plus d'un champ/élément de données dans votre fichier.

Je m'occupe de gros flux de données de fichiers plats en bonne quantité tous les jours, et même si un exemple extrême, les données de fichiers plats est beaucoup plus difficile à maintenir que les flux de données XML que je traite.

exemple simple de chargement de données XML dans un ensemble de données en utilisant C#:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

vous pouvez également consulter LINQ to XML comme option pour interroger les données XML...

HTH...

0
répondu Tom Miller 2009-12-21 19:15:53

si vous allez à la route de sérialisation binaire, considérez la vitesse à laquelle un membre particulier du système de référence doit être accédé. Si ce n'est qu'une petite collection, charger l'ensemble du fichier aura du sens, mais s'il sera de grande taille, vous pourriez aussi envisager un fichier index.

suivi des propriétés/champs du compte qui sont situés à une adresse spécifique dans le fichier peut vous aider à accélérer le temps d'accès, surtout si vous optimisez ce fichier d'index basé sur l'utilisation des clés. (peut-être même quand vous écrivez sur un disque.)

0
répondu Todd Richardson 2009-12-21 19:37:07

en fonction de la compelexité de votre compte objet, je recommande soit XML ou fichier plat.

S'il n'y a 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 d'un fichier de propriétés devrait être facile, car il correspond directement à un dictionnaire de chaîne.

où stocker ce fichier, la meilleur solution serait de stocker dans le dossier AppData, dans un sous-dossier pour votre programme. C'est un endroit où les utilisateurs actuels auront toujours accès à écrire, et il est protégé des autres utilisateurs par le système D'exploitation lui-même.

0
répondu Pablo Venturino 2009-12-21 19:44:39

restez simple - comme vous l'avez dit, un fichier plat est suffisant. L'utilisation d'un fichier plat.

ceci suppose que vous avez analysé vos besoins correctement. Je sauterais le serializing comme étape XML, overkill pour un dictionnaire simple. Même chose pour une base de données.

0
répondu Larry Watanabe 2009-12-21 20:23:48

dans mon expérience dans la plupart des cas JSON dans un fichier est suffisant (la plupart du temps, vous avez besoin de stocker un tableau ou un objet ou juste un seul nombre ou chaîne de caractères). 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 exagéré).

0
répondu JedatKinports 2017-05-12 07:05:15