La mejor forma de almacenar datos localmente en .NET (C #)

Estoy escribiendo una aplicación que toma los datos del usuario y los almacena localmente para usarlos más adelante. La aplicación se iniciará y se interrumpirá con bastante frecuencia, y me gustaría hacer que guarde / cargue los datos en el inicio / finalización de la aplicación.

Sería bastante sencillo si utilizo archivos planos, ya que los datos realmente no necesitan estar seguros (solo se almacenarán en esta PC). Las opciones que creo son así:

  • Archivos planos
  • XML
  • SQL DB

Los archivos planos requieren un poco más de esfuerzo para mantenerlos (no hay clases integradas como en XML), sin embargo, no he usado XML antes, y SQL parece excesivo para esta tarea relativamente fácil.

¿Hay alguna otra vía que valga la pena explorar? Si no, ¿cuál de estas es la mejor solución?


Editar: para agregar un poco más de datos al problema, básicamente, lo único que me gustaría guardar es un diccionario que se parece a esto

Dictionary<string, List> 

donde Account es otro tipo personalizado.

¿Debería serializar el dict como raíz xml, y luego el tipo de cuenta como atributos?


Actualización 2:

Por lo tanto, es posible serializar un diccionario. Lo que lo hace complicado es que el valor de este dict es un genérico en sí mismo, que es una lista de estructuras de datos complejas de tipo Cuenta. Cada cuenta es bastante simple, es solo un conjunto de propiedades.

Entiendo que el objective aquí es intentar y terminar con esto:

   data1 data2     data1 data2   data1 data2   

Como pueden ver, la heirquía es

  • Nombre de usuario (cadena de dict)>
  • Cuenta (cada cuenta en la Lista)>
  • Datos de cuenta (es decir, propiedades de clase).

Obtener este diseño de un Dictionary<Username, List> es el truco y la esencia de esta pregunta.

Aquí hay muchas respuestas de ‘cómo hacerlo’ en la serialización, que es mi culpa ya que no lo dejé claro desde el principio, pero ahora estoy buscando una solución definitiva.

Almacenaba el archivo como JSON . Ya que está almacenando un diccionario que es solo una lista de pares nombre / valor, entonces esto es más o menos para lo que JSON fue diseñado.
Hay bastantes bibliotecas de .NET json gratuitas y decentes. Aquí hay una, pero puedes encontrar una lista completa en el primer enlace.

Realmente depende de lo que estás almacenando. Si está hablando de datos estructurados, XML o un RDBMS SQL muy ligero como SQLite o SQL Server Compact Edition funcionarán bien para usted. La solución SQL se vuelve especialmente convincente si los datos se mueven más allá de un tamaño trivial.

Si está almacenando piezas grandes de datos relativamente no estructurados (objetos binarios como imágenes, por ejemplo), obviamente, ni una base de datos ni una solución XML son apropiados, pero dada su pregunta, supongo que es más de los primeros que de los segundos.

XML es fácil de usar, a través de la serialización. Use almacenamiento aislado .

Consulte también ¿Cómo decidir dónde almacenar el estado por usuario? ¿Registro? ¿Datos de aplicación? Almacenamiento aislado?

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

Todas las anteriores son buenas respuestas, y generalmente resuelven el problema.

Si necesita una forma fácil y gratuita de escalar a millones de datos, pruebe el proyecto ESENT Managed Interface en CodePlex .

ESENT es un motor de almacenamiento de base de datos incrustable (ISAM) que es parte de Windows. Proporciona almacenamiento de datos de alto rendimiento confiable, transaccionado, concurrente con locking de nivel de fila, registro de escritura anticipada y aislamiento de instantáneas. Este es un contenedor administrado para la API de ESENT Win32.

Tiene un objeto PersistentDictionary que es bastante fácil de usar. Piénselo como un objeto de Diccionario (), pero se carga automáticamente y se guarda en el disco sin código adicional.

Por ejemplo:

 ///  /// Ask the user for their first name and see if we remember /// their last name. ///  public static void Main() { PersistentDictionary dictionary = new PersistentDictionary("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(); } 

Para responder la pregunta de George:

Tipos de claves compatibles

Solo estos tipos son compatibles como claves de diccionario:

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

Tipos de valores admitidos

Los valores de diccionario pueden ser cualquiera de los tipos de clave, versiones anulables de los tipos de clave, Uri, IPAddress o una estructura serializable. Una estructura solo se considera serializable si cumple con todos estos criterios:

• La estructura está marcada como serializable. • Cada miembro de la estructura es: 1. Un tipo de datos primitivo (por ejemplo, Int32) 2. Una cadena, Uri o IPAddress 3. Una estructura serializable.

O, para decirlo de otra manera, una estructura serializable no puede contener ninguna referencia a un objeto de clase. Esto se hace para preservar la consistencia API. Agregar un objeto a PersistentDictionary crea una copia del objeto a través de la serialización. La modificación del objeto original no modificará la copia, lo que generaría un comportamiento confuso. Para evitar esos problemas, PersistentDictionary solo aceptará tipos de valores como valores.

Se puede serializar [Serializable] struct Good {public DateTime? Recibido; nombre de cadena pública; Precio Decimal público; público Uri Url; }

No se puede serializar [Serializable] struct Malo {byte público [] Datos; // las matrices no son compatibles pública Exception Error; // objeto de referencia}

Recomiendo la clase XML reader / writer para archivos porque se serializa fácilmente.

Serialización en C #

La serialización (conocida como decapado en python) es una forma fácil de convertir un objeto a una representación binaria que luego puede escribirse en un disco o enviarse a través de un cable.

Es útil, por ejemplo, para guardar fácilmente las configuraciones en un archivo.

Puede serializar sus propias clases si las marca con el atributo [Serializable] . Esto serializa todos los miembros de una clase, excepto los marcados como [NonSerialized] .

El siguiente es un código para mostrarle cómo hacer esto:

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

¡Entonces, en una ConfigForm tal vez, llame a su clase ConfigManager y la serialice!

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

Después de haber sido serializado, puede llamar a los parámetros de su archivo de configuración usando cm.WindowTitle, etc.

Una cuarta opción para los que mencionas son los archivos binarios . Aunque suena arcano y difícil, es muy fácil con la API de serialización en .NET.

Si elige archivos binarios o XML, puede usar la misma API de serialización, aunque utilizaría diferentes serializadores.

Para serializar binariamente una clase, debe marcarse con el atributo [Serializable] o implementar ISerializable.

Puede hacer algo similar con XML , aunque allí la interfaz se llama IXmlSerializable, y los atributos son [XmlRoot] y otros atributos en el espacio de nombres System.Xml.Serialization.

Si desea utilizar una base de datos relacional, SQL Server Compact Edition es gratuito, muy liviano y está basado en un único archivo.

Acabo de terminar de codificar el almacenamiento de datos para mi proyecto actual. Aquí están mis 5 centavos.

Empecé con la serialización binaria. Fue lento (aproximadamente 30 segundos para una carga de 100.000 objetos) y también creó un archivo bastante grande en el disco. Sin embargo, me tomó algunas líneas de código para implementar y cubrí todas mis necesidades de almacenamiento. Para obtener un mejor rendimiento, pasé a la serialización personalizada. Marco de FastSerialization encontrado por Tim Haynes en Code Project. De hecho, es un poco más rápido (tiene 12 segundos para carga, 8 segundos para guardar, 100 000 registros) y ocupa menos espacio en disco. El marco se basa en la técnica descrita por GalacticJello en una publicación anterior.

Luego me mudé a SQLite y pude obtener 2 veces el rendimiento 3 veces más rápido: 6 segundos para carga y 4 segundos para guardar, 100K registros. Incluye el análisis de tablas ADO.NET a tipos de aplicaciones. También me dio un archivo mucho más pequeño en el disco. Este artículo explica cómo obtener el mejor rendimiento de ADO.NET: http://sqlite.phxsoftware.com/forums/t/134.aspx . Generar instrucciones INSERT es una muy mala idea. Puedes adivinar cómo llegué a saber sobre eso. 🙂 De hecho, la implementación de SQLite me llevó bastante tiempo, además de una cuidadosa medición de la toma de tiempo en casi todas las líneas del código.

Si su colección es demasiado grande, he descubierto que la serialización Xml es bastante lenta. Otra opción para serializar su diccionario sería “lanzar su propio” usando un BinaryReader y BinaryWriter.

Aquí hay un código de muestra para comenzar. Puede hacer estos métodos de extensión generics para manejar cualquier tipo de diccionario, y funciona bastante bien, pero es demasiado detallado para publicar aquí.

 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> 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 accountList = new List(); for (int i = 0; i < accountCount; i++) { Account account = new Account(); account.Deserialize(br); accountList.Add(account); } accounts.Add(key, accountList); } } } static Dictionary> accounts = new Dictionary>(); static void Main(string[] args) { string accountName = "Bob"; List newAccounts = new List(); newAccounts.Add(AddAccount("A", 1)); newAccounts.Add(AddAccount("B", 2)); newAccounts.Add(AddAccount("C", 3)); accounts.Add(accountName, newAccounts); accountName = "Tom"; newAccounts = new List(); 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; } } 

Si sus datos son complejos, de gran cantidad o necesita consultarlos localmente, entonces las bases de datos de objetos podrían ser una opción válida. Sugeriría mirar Db4o o Karvonite .

Lo primero que vería es una base de datos. Sin embargo, la serialización es una opción. Si BinaryFormatter por la serialización binaria, entonces evitaría BinaryFormatter : tiene tendencia a enojarse entre las versiones si cambias los campos, etc. Xml a través de XmlSerialzier estaría bien, y puede ser compatible lado a lado (es decir, con la misma clase) definiciones) con protobuf-net si desea probar la serialización binaria por contrato (proporcionándole un serializador de archivos sin ningún esfuerzo).

Muchas de las respuestas en este hilo intentan sobre-diseñar la solución. Si estoy en lo correcto, solo desea almacenar la configuración del usuario.

Utilice un archivo .ini o un archivo App.Config para esto.

Si me equivoco y está almacenando datos que son más que simples configuraciones, use un archivo de texto plano en formato csv. Estos son rápidos y fáciles sin la sobrecarga de XML. A la gente le gusta cagar estas porque no son tan elegantes, no se adaptan bien y no se ven tan bien en un currículum, pero podría ser la mejor solución para ti dependiendo de lo que necesites.

He hecho varias aplicaciones “independientes” que tienen una tienda de datos local. Creo que lo mejor sería SQL Server Compact Edition (anteriormente conocido como SQLAnywhere).

Es liviano y gratis. Además, puede limitarse a escribir una capa de acceso a los datos que sea reutilizable en otros proyectos y, si la aplicación necesita escalar a algo más grande como el servidor SQL completo, solo necesita cambiar la cadena de conexión.

Mi primera inclinación es una base de datos de acceso. Los archivos .mdb se almacenan localmente y se pueden encriptar si se considera necesario. Aunque XML o JSON también funcionarían para muchos escenarios. Archivos planos que solo usaría para información de solo lectura, sin búsqueda (solo lectura hacia adelante). Tiendo a preferir el formato csv para establecer el ancho.

Depende de la cantidad de datos que desea almacenar. En realidad, no hay diferencia entre los archivos planos y XML. XML probablemente sería preferible ya que proporciona una estructura para el documento. En la práctica,

La última opción, y muchas aplicaciones que usan ahora, es el Registro de Windows. No lo recomiendo personalmente (Bloqueo del registro, corrupción, otros posibles problemas), pero es una opción.

Sin saber a qué se parecen sus datos, es decir, la complejidad, el tamaño, etc. XML es fácil de mantener y de fácil acceso. NO usaría una base de datos de Access, y los archivos planos son más difíciles de mantener a largo plazo, particularmente si se trata de más de un campo / elemento de datos en su archivo.

Trato grandes cantidades de datos de archivos planos en grandes cantidades diariamente, y aunque es un ejemplo extremo, los datos de archivos planos son mucho más difíciles de mantener que los datos XML que proceso.

Un ejemplo simple de cargar datos XML en un conjunto de datos usando C #:

 DataSet reportData = new DataSet(); reportData.ReadXml(fi.FullName); 

También puede consultar LINQ a XML como una opción para consultar los datos XML …

HTH …

Si utiliza la ruta de serialización binaria, considere la velocidad a la que se debe acceder a un miembro en particular del datum. Si solo se trata de una colección pequeña, cargar todo el archivo tendrá sentido, pero si es grande, también podría considerar un archivo de índice.

Los campos / propiedades de cuenta de seguimiento que se encuentran en una dirección específica dentro del archivo pueden ayudarlo a acelerar el tiempo de acceso, especialmente si optimiza ese archivo de índice según el uso de la clave. (posiblemente incluso cuando escribe en el disco).

Dependiendo de la competencia de su objeto de Cuenta, recomendaría XML o un archivo plano.

Si solo hay un par de valores para almacenar para cada cuenta, puede almacenarlos en un archivo de propiedades, como este:

 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 

… Etcétera. Leer desde un archivo de propiedades debería ser fácil, ya que se asigna directamente a un diccionario de cadenas.

En cuanto a dónde almacenar este archivo, la mejor opción sería almacenar en la carpeta AppData, dentro de una subcarpeta para su progtwig. Esta es una ubicación en la que los usuarios actuales siempre tendrán acceso para escribir, y se mantiene a salvo de otros usuarios por parte del propio sistema operativo.

Mantenlo simple: como dijiste, un archivo plano es suficiente. Use un archivo plano.

Esto supone que ha analizado sus requisitos correctamente. Me saltaría la serialización como paso XML, excesivo para un diccionario simple. Lo mismo para una base de datos.

En mi experiencia, en la mayoría de los casos JSON en un archivo es suficiente (la mayoría de las veces es necesario almacenar una matriz o un objeto o simplemente un solo número o cadena). Raramente necesito SQLite (que necesita más tiempo para configurarlo y usarlo, la mayoría de las veces es excesivo).