Лучший способ хранения данных локально in.NET (C#)

Я пишу приложение, которое принимает пользовательские данные и сохраняет их локально для дальнейшего использования. Приложение будет запускаться и останавливаться довольно часто, и я хотел бы сохранить/загрузить данные о запуске/завершении приложения.

было бы довольно просто, если бы я использовал плоские файлы, так как данные действительно не нужно защищать (он будет храниться только на этом ПК). Нужным я считаю так:

  • плоские файлы
  • XML
  • SQL DB

плоские файлы требуют немного больше усилий для поддержания (нет встроенных классов, таких как XML), однако я не использовал XML раньше, и SQL кажется излишним для этой относительно простой задачи.

есть ли другие пути, которые стоит изучить? Если нет, то какое из них является лучшим решением?


Edit: чтобы добавить немного больше данных к проблеме, в основном единственное, что я хотел бы сохранить, это словарь, который выглядит так это

Dictionary<string, List<Account>> 

где учетная запись является другим пользовательским типом.

Я бы сериализовал dict как xmlroot, а затем тип учетной записи как атрибуты?


обновление 2:

таким образом, можно сериализовать словарь. Что делает его сложным, так это то, что значение для этого dict является общим, которое представляет собой список сложных структур данных типа 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 составляет

  • имя пользователя (строка dict) >
  • учетная запись (каждая учетная запись в списке) >
  • данные учетной записи (т. е. свойств класса).

получение этого макета от Dictionary<Username, List<Account>> - это сложная процедура, и суть этого вопроса.

здесь есть много ответов "как" на сериализацию, которая является моей ошибка, так как я не сделал это яснее в начале, но теперь я ищу определенное решение.

19 ответов


Я бы сохранил файл как JSON. Поскольку вы храните словарь, который является просто списком пар имя / значение, то это в значительной степени то, для чего был разработан json.
Там довольно много приличных, бесплатных библиотек .NET json-вот один но вы можете найти полный список по первой ссылке.


Это действительно зависит от того, что вы храните. Если вы говорите о структурированных данных, то XML или очень легкий SQL СУБД, как SQLite или SQL Server Compact Edition будет работать хорошо для вас. Решение SQL становится особенно убедительным, если данные выходят за рамки тривиального размера.

Если вы храните большие куски относительно неструктурированных данных (например, двоичные объекты, такие как изображения), то, очевидно, ни база данных, ни решение XML не подходят, но учитывая ваш вопрос я предполагаю, что это больше Первое, чем второе.


XML прост в использовании, через сериализацию. Использовать изолированное хранилище.

см. также как решить, где хранить состояние каждого пользователя? Реестр? Папка AppData? Изолированное Хранилище?

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

все вышеперечисленное-хорошие ответы и, как правило, решает проблему.

Если вам нужен простой и бесплатный способ масштабирования до миллионов фрагментов данных, попробуйте проект управляемого интерфейса ESENT на страницы CodePlex.

ESENT-это встраиваемый механизм хранения баз данных (ISAM), который является частью Windows. Он обеспечивает надежное, транзакционное, параллельное, высокопроизводительное хранение данных с блокировкой на уровне строк, записью вперед и изоляцией моментальных снимков. Это управляемая оболочка для API Esent Win32.

Он имеет объект 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();
    }

чтобы ответить на вопрос Джорджа:

Поддерживаемые Типы Ключей

только эти типы поддерживаются ключи словаря:

Boolean Значение Типа INT16 Байт Типа Uint16 Int32 И Тип Uint32 Типа Int64 Uint64 В Поплавок Двойной Идентификатор Guid DateTime TimeSpan String

Поддерживаемые Типы Значений

значения словаря могут быть любым из основные типы, варианты, допускающие значения null из типы ключей, Uri, IPAddress или a сериализуемая структура. Структура только считается сериализуемым, если он отвечает всем этим критериям:

* структура помечена как сериализуемый • каждый член struct-это либо: 1. Примитивный тип данных (например, Int32) 2. Строка, Uri или IPAddress 3. Сериализуемая структура.

или, другими словами, a сериализуемая структура не может содержать любые ссылки на объект класса. Этот делается для сохранения согласованности API. Добавление объекта в PersistentDictionary создает копию объект хоть сериализация. Изменение исходного объекта не будет измените копию, которая приведет к странное поведение. Избегать этого проблемы PersistentDictionary будет принимать в качестве значений только типы значений.

Может Быть Сериализован [сериализуемые] структура хорошая { общественная datetime? Полученный; имя строку ; публичная десятичная цена; общедоступный Url Uri;}

невозможно сериализовать [сериализуемые] структура плохо { public byte [] Data; / / массивы не поддерживаются ошибка публичного исключения; / / ссылка объект }


Я рекомендую класс чтения/записи XML для файлов, потому что он легко сериализуется.

сериализация в C#

сериализация (известная как маринование в python) - это простой способ конвертировать объект двоичного представления, который можно, например, записать на диск или отправили телеграмму.

это полезно, например, для быстрого сохранения настроек в файл.

вы можете сериализовать свои классы если вы отмечаете их [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, etc.


четвертый вариант для тех, кого вы упомянули двоичные файлы. Хотя это звучит загадочно и сложно, это действительно легко с API сериализации .Сеть.

Если вы выбираете двоичные или XML-файлы, вы можете использовать тот же API сериализации, хотя вы бы использовали разные сериализаторы.

для двоичной сериализации класса он должен быть отмечен атрибутом [Serializable] или реализовать ISerializable.

вы можете сделать что-то подобное с XML, хотя там интерфейс называется IXmlSerializable, а атрибутами являются [XmlRoot] и другие атрибуты в системе.XML.Пространство имен сериализации.

Если вы хотите использовать реляционную базу данных, SQL Server Compact Edition бесплатно и очень легкий и на основе одного файла.


только что закончил кодирование хранения данных для моего текущего проекта. Вот мои 5 копеек.

Я начал с двоичной сериализации. Он был медленным (около 30 секунд для загрузки 100 000 объектов), и он также создавал довольно большой файл на диске. Тем не менее, мне потребовалось несколько строк кода для реализации, и я получил все свои потребности в хранении. Чтобы получить лучшую производительность, я перешел на пользовательскую сериализацию. Найден фреймворк 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 заняла у меня довольно много времени плюс тщательное измерение времени, занимаемого практически каждой строкой кода.


Если ваша коллекция становится слишком большой, я обнаружил, что сериализация Xml становится довольно медленной. Другой вариант сериализации словаря - "свернуть свой собственный" с помощью BinaryReader и BinaryWriter.

вот пример кода, чтобы вы начали. Вы можете сделать эти общие методы расширения для обработки любого типа словаря, и он работает довольно хорошо, но слишком многословен для публикации здесь.

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

Если ваши данные сложны, высоки в количестве или вам нужно запросить его локально, то базы данных объектов могут быть допустимым вариантом. Я бы предложил посмотреть на в db4o или Karvonite.


первое, что я бы посмотрел, это база данных. Однако сериализация является опцией. Если вы идете на двоичную сериализацию, то я бы избежать BinaryFormatter - Он имеет тенденцию сердиться между версиями, если вы меняете поля и т. д. Xml через XmlSerialzier было бы хорошо, и может быть совместимо (т. е. с теми же определениями классов) с protobuf-net, если вы хотите попробовать контрактную двоичную сериализацию (давая вам сериализатор плоских файлов без каких-либо усилий).


многие ответы в этом потоке пытаются переопределить решение. Если я прав,вы просто хотите сохранить пользовательские настройки.

использовать .ini файл или приложение.Конфигурационный файл для этого.

Если я ошибаюсь, и вы храните данные, которые больше, чем просто настройки, используйте плоский текстовый файл в формате CSV. Это быстро и легко, без накладных в XML. Люди любят какать их, так как они не такие элегантные, не масштабируются красиво и не выглядят так хорошо в резюме, но это может быть лучшим решением для вас в зависимости от того, что вам нужно.


Я сделал несколько "автономных" приложений, которые имеют локальное хранилище данных. Я думаю, что лучше всего использовать SQL Server Compact Edition (ранее известный как SQLAnywhere).

Это легкий и бесплатный. Кроме того, вы можете придерживаться записи уровня доступа к данным, который используется в других проектах, а также, если приложение когда-либо нужно масштабировать до чего-то большего, как полномасштабный SQL server, вам нужно только изменить строку подключения.


моя первая склонность-база данных access. Этот.MDB файлы хранятся локально и могут быть зашифрованы, если это будет сочтено необходимым. Хотя XML или JSON также будут работать для многих сценариев. Плоские файлы, которые я бы использовал только для чтения, без поиска (только для прямого чтения) информации. Я предпочитаю формат csv для установки ширины.


Это зависит от количества данных, которые вы хотите сохранить. На самом деле нет никакой разницы между плоскими файлами и XML. XML, вероятно, будет предпочтительнее, поскольку он обеспечивает структуру документа. На практике,

последний вариант, и многие приложения используют сейчас реестр Windows. Я лично не рекомендую его (раздувание реестра, коррупция, другие потенциальные проблемы), но это вариант.


Не зная, как выглядят ваши данные, т. е. сложность, размер и т. д...XML прост в обслуживании и легко доступен. Я бы не использовал базу данных Access, а плоские файлы сложнее поддерживать в течение длительного времени, особенно если вы имеете дело с более чем одним полем данных/элементом в вашем файле.

Я имею дело с большими потоками данных плоских файлов в хорошем количестве ежедневно, и даже если экстремальный пример, данные плоских файлов намного сложнее поддерживать, чем Каналы данных XML I обрабатываются.

простой пример загрузки XML-данных в набор данных с помощью C#:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

вы также можете проверить LINQ to XML в качестве опции для запроса XML-данных...

HTH...


Если вы идете по маршруту двоичной сериализации, рассмотрите скорость, с которой необходимо получить доступ к определенному члену datum. Если это только небольшая коллекция, загрузка всего файла будет иметь смысл, но если он будет большим, вы также можете рассмотреть индексный файл.

отслеживание свойств/полей учетной записи, расположенных по определенному адресу в файле, может помочь вам ускорить время доступа, особенно если вы оптимизируете этот индексный файл на основе использования ключа. (возможно, даже когда вы пишете на диск.)


в зависимости от compelexity вашего объекта счета, я бы рекомендовал либо XML или плоский файл.

Если есть только несколько значений для хранения для каждой учетной записи, вы можете сохранить их в файле свойств, например:

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 внутри подпапки для вашей программы. Это место, где текущие пользователи всегда будут иметь доступ к записи, и оно защищено от других пользователей самой ОС.


держите его простым - как вы сказали, достаточно плоского файла. Используйте плоский файл.

Это предполагает, что вы правильно проанализировали свои требования. Я бы пропустил сериализацию как шаг XML, перебор для простого словаря. То же самое для базы данных.


по моему опыту в большинстве случаев достаточно JSON в файле (в основном вам нужно сохранить массив или объект или только одно число или строку). Мне редко нужен SQLite (который требует больше времени для его настройки и использования, в большинстве случаев это перебор).