Лучший способ хранить данные локально в .NET (C #) - PullRequest
66 голосов
/ 21 декабря 2009

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

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

  • Плоские файлы
  • XML
  • БД SQL

Плоские файлы требуют немного больше усилий для поддержки (нет встроенных классов, подобных 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>

Как вы можете видеть, иерархия

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

Получение этого макета из Dictionary<Username, List<Account>> - хитрый момент, и суть этого вопроса.

Существует множество ответов «как делать» здесь по поводу сериализации, и это моя вина, так как я не делал это раньше, но сейчас я ищу определенное решение.

Ответы [ 19 ]

23 голосов
/ 21 декабря 2009

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

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

22 голосов
/ 21 декабря 2009

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

14 голосов
/ 21 декабря 2009

XML прост в использовании благодаря сериализации. Используйте Изолированное хранилище .

См. Также Как решить, где хранить состояние пользователя? Реестр? Данные приложения? Изолированное хранилище?

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();
    }
}
11 голосов
/ 21 декабря 2009

Все вышеперечисленное является хорошим ответом и в целом решает проблему.

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

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

У него есть объект 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 Byte Int16 UInt16 Int32 UInt32 Int64 UInt64 Float Double Guid DateTime Строка TimeSpan

Поддерживаемые типы значений

Значения словаря могут быть любыми из ключевые типы, Nullable версии ключевые типы, Uri, IP-адрес или Сериализуемая структура. Структура только считается, что сериализуемый соответствует всем этим критериям:

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

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

Может быть сериализовано [Serializable] struct Good { общедоступный DateTime? Получено; публичная строка Name; публичная десятичная цена; публичный Ури Урл; }

Не может быть сериализовано [Serializable] struct Bad { открытый байт [] данных; // массивы не поддерживаются Ошибка общественного исключения; // ссылочный объект}

8 голосов
/ 21 декабря 2009

Я рекомендую класс чтения / записи 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 и т. Д.

7 голосов
/ 21 декабря 2009

Если ваша коллекция становится слишком большой, я обнаружил, что сериализация 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;
    }
}
7 голосов
/ 21 декабря 2009

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

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

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

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

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

6 голосов
/ 23 декабря 2009

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

Я начал с двоичной сериализации. Он был медленным (около 30 секунд при загрузке 100 000 объектов) и создавал довольно большой файл на диске. Однако, мне потребовалось несколько строк кода для реализации, и я покрыл все свои потребности в хранилище. Чтобы получить лучшую производительность, я перешел на пользовательскую сериализацию. Найден фреймворк FastSerialization Тимом Хейнсом в проекте Code. Действительно, это в несколько раз быстрее (12 секунд на загрузку, 8 секунд на сохранение, 100 тыс. Записей) и занимает меньше места на диске. Фреймворк построен по методике, изложенной GalacticJello в предыдущем посте.

Затем я перешел на SQLite и смог увеличить производительность в 2, а то и в 3 раза - 6 с при загрузке и 4 с при сохранении, 100 тыс. Записей. Он включает в себя анализ таблиц ADO.NET по типам приложений. Это также дало мне намного меньший файл на диске. В этой статье объясняется, как добиться максимальной производительности из ADO.NET: http://sqlite.phxsoftware.com/forums/t/134.aspx. Создание операторов INSERT - очень плохая идея. Вы можете догадаться, как я узнал об этом. :) Действительно, реализация SQLite заняла у меня довольно много времени плюс тщательное измерение времени, затрачиваемого практически на каждую строку кода.

4 голосов
/ 21 декабря 2009

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

4 голосов
/ 21 декабря 2009

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

...