سؤال

أنا أكتب تطبيقًا يأخذ بيانات المستخدم ويخزنها محليًا للاستخدام لاحقًا. سيتم بدء التطبيق وإيقافه في كثير من الأحيان إلى حد ما ، وأود أن أجعله يحفظ/تحميل البيانات الموجودة على التطبيق/نهاية التطبيق.

سيكون الأمر واضحًا إلى حد ما إذا استخدمت الملفات المسطحة ، حيث لا تحتاج البيانات إلى تأمينها حقًا (سيتم تخزينها فقط على هذا الكمبيوتر). الخيارات التي أعتقد أنها هكذا:

  • ملفات مسطحة
  • XML
  • SQL DB

تتطلب الملفات المسطحة بذل المزيد من الجهد قليلاً للحفاظ على (لا يوجد فئات مبنية مثل XML) ، ومع ذلك لم أستخدم XML من قبل ، ويبدو أن SQL مبالغة لهذه المهمة السهلة نسبيًا.

هل هناك أي طرق أخرى تستحق الاستكشاف؟ إذا لم يكن الأمر كذلك ، أي من هذه هي الحل الأفضل؟


تحرير: لإضافة المزيد من البيانات إلى المشكلة ، في الأساس الشيء الوحيد الذي أود تخزينه هو القاموس الذي يبدو مثل هذا

Dictionary<string, List<Account>> 

حيث الحساب هو نوع آخر مخصص.

هل سأقوم بتسلسل القولات باعتبارها XMLroot ، ثم نوع الحساب كسمات؟


تحديث 2:

لذلك من الممكن تسلسل القاموس. ما يجعل الأمر معقدًا هو أن قيمة هذا القولون هي عامة ، وهي قائمة بهياكل البيانات المعقدة لحساب النوع. كل حساب بسيط إلى حد ما ، إنه مجرد مجموعة من الخصائص.

أفهم أن الهدف هنا هو محاولة وينتهي الأمر بهذا:

<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>> هو الشيء الصعبة ، وجوهر هذا السؤال.

هناك الكثير من الاستجابات "كيفية" هنا على التسلسل ، وهذا هو خطأي لأنني لم أوضحها في وقت مبكر ، لكنني الآن أبحث عن حل محدد.

هل كانت مفيدة؟

المحلول

سأقوم بتخزين الملف باسم جيسون. نظرًا لأنك تقوم بتخزين القاموس الذي هو مجرد قائمة زوج/قيمة ، فهذا هو ما تم تصميم JSON من أجله.
هناك عدد غير قليل من مكتبات .NET JSON لائقة ، هنا - ها هي واحد ولكن يمكنك العثور على قائمة كاملة على الرابط الأول.

نصائح أخرى

يعتمد الأمر حقًا على ما تقوم بتخزينه. إذا كنت تتحدث عن البيانات المهيكلة ، فإن إما XML أو SQL RDBMS خفيفة الوزن للغاية مثل SQLITE أو SQL Server Compact EDITION ستعمل بشكل جيد لك. يصبح محلول SQL مقنعة بشكل خاص إذا تحركت البيانات إلى ما هو أبعد من الحجم التافهة.

إذا كنت تقوم بتخزين أجزاء كبيرة من البيانات غير المهيكلة نسبيًا (كائنات ثنائية مثل الصور ، على سبيل المثال) ، فمن الواضح أنه لا يوجد قاعدة بيانات أو حل XML مناسب ، لكن بالنظر إلى سؤالك ، أعتقد أنه أكثر من السابق أكثر من الأخير.

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

كل ما سبق إجابات جيدة ، وحل المشكلة بشكل عام.

إذا كنت بحاجة إلى طريقة سهلة ومجانية لتوسيع نطاق ملايين البيانات codeplex.

ESENT هو محرك تخزين قاعدة بيانات قابل للتضمين (ISAM) وهو جزء من Windows. إنه يوفر تخزين بيانات موثوقًا ومعالجًا ومتزامنًا وعالي الأداء مع قفل على مستوى الصف ، وتسجيل الكتابة في الكتابة وعزل اللقطة. هذا هو غلاف مُدار لـ Esent Win32 API.

إنه يحتوي على كائن مستمر سهل الاستخدام. فكر في الأمر ككائن قاموس () ، ولكن يتم تحميله تلقائيًا من وحفظه إلى القرص بدون رمز إضافي.

علي سبيل المثال:

/// <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 TimePan String

أنواع القيمة المدعومة

يمكن أن تكون قيم القاموس أيًا من أنواع المفاتيح ، أو الإصدارات الباطئة من أنواع المفاتيح ، أو URI ، أو iPaddress أو بنية قابلة للتسلسل. يعتبر الهيكل قابلاً للتسلسل فقط إذا كان يفي بجميع هذه المعايير:

• يتم تمييز البنية على أنها قابلة للتسلسل • كل عضو في البنية هو: 1. نوع البيانات البدائية (على سبيل المثال int32) 2. سلسلة أو uri أو ipaddress 3. بنية قابلة للتسلسل.

أو ، بعبارة أخرى ، لا يمكن أن يحتوي الهيكل القابل للتسلسل على أي إشارات إلى كائن فئة. يتم ذلك للحفاظ على اتساق API. تؤدي إضافة كائن إلى incipentdictionary إلى إنشاء نسخة من الكائن على الرغم من التسلسل. لن يؤدي تعديل الكائن الأصلي إلى تعديل النسخة ، مما قد يؤدي إلى سلوك مربك. لتجنب تلك المشكلات ، لن يقبل الثابتة سوى أنواع القيمة كقيم.

يمكن التسلسل serializable] بنية جيدة {dateTime العامة؟ تم الاستلام؛ اسم السلسلة العامة ؛ السعر العشري العام ؛ URL URL العام ؛ }

لا يمكن التسلسل serializable] struct bad {public byte [] data ؛ // صفائف لا تدعم خطأ الاستثناء العام ؛ // كائن مرجع}

أوصي بـ XML Reader/Writer Class للملفات لأنه يتم تسلسله بسهولة.

التسلسل في C#

يعد التسلسل (المعروف باسم التخليل في بيثون) وسيلة سهلة لتحويل كائن إلى تمثيل ثنائي يمكن بعد ذلك كتابة على القرص أو إرساله عبر سلك.

إنه مفيد على سبيل المثال لسهولة حفظ الإعدادات في ملف.

يمكنك تسلسل الفصول الدراسية الخاصة بك إذا قمت بالعلامة معها [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 ، إلخ.

الخيار الرابع لأولئك الذين ذكرتهم هم الملفات الثنائية. على الرغم من أن هذا يبدو غامقًا وصعبًا ، إلا أنه من السهل حقًا مع واجهة برمجة تطبيقات التسلسل في .NET.

سواء اخترت ملفات ثنائية أو XML ، يمكنك استخدام واجهة برمجة تطبيقات التسلسل نفسها ، على الرغم من أنك ستستخدم مسلسلات مختلفة.

لتسلسل فئة ثنائية ، يجب وضع علامة عليها مع السمة [التسلسلية] أو تنفيذها.

يمكنك أن تفعل شيئًا مشابهًا مع XML, ، على الرغم من أن هناك الواجهة تسمى ixmlserializable ، والسمات هي [xmlroot] والسمات الأخرى في مساحة اسم system.xml.serialization.

إذا كنت ترغب في استخدام قاعدة بيانات علائقية ، إصدار SQL Server Compact مجاني وخفيف الوزن للغاية ويستند إلى ملف واحد.

إذا أصبحت مجموعتك كبيرة جدًا ، فقد وجدت أن تسلسل XML يصبح بطيئًا للغاية. هناك خيار آخر لتسلسل القاموس الخاص بك هو "لفة خاصة بك" باستخدام قائد ثنائي و 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;
    }
}

انتهيت للتو من تخزين بيانات ترميز مشروعي الحالي. ها هو 5 سنتات.

لقد بدأت بالتسلسل الثنائي. كان بطيئًا (حوالي 30 ثانية لتحميل 100000 كائن) وكان إنشاء ملف كبير على القرص أيضًا. ومع ذلك ، فقد استغرق الأمر مني بضعة أسطر من التعليمات البرمجية للتنفيذ وحصلت على جميع احتياجات التخزين الخاصة بي. للحصول على أداء أفضل ، انتقلت إلى التسلسل المخصص. تم العثور على إطار عمل سريع من قبل تيم هاينز في مشروع الرمز. في الواقع ، إنها أسرع عدة مرات (حصلت على 12 ثانية للتحميل ، 8 ثوانٍ للحفظ ، سجلات 100K) ويستغرق مساحة قرص أقل. تم بناء الإطار على التقنية التي حددها Galacticjello في منشور سابق.

ثم انتقلت إلى SQLite وتمكنت من الحصول على 2 مرات في بعض الأحيان 3 مرات الأداء - 6 ثانية للتحميل و 4 ثوان لإنقاذ ، 100K سجلات. ويشمل تحليل جداول ado.net لأنواع التطبيق. كما أعطاني ملف أصغر بكثير على القرص. تشرح هذه المقالة أفضل طريقة للحصول على أفضل أداء من ADO.NET: http://sqlite.phxsoftware.com/forums/t/134.aspx. توليد عبارات إدراج فكرة سيئة للغاية. يمكنك تخمين كيف تعرفت على ذلك. :) في الواقع ، استغرقني تنفيذ SQLite الكثير من الوقت بالإضافة إلى قياس دقيق للوقت في أخذ كل سطر من الكود.

إذا كانت بياناتك معقدة أو عالية الكمية أو كنت بحاجة إلى الاستعلام عنها محليًا ، فقد تكون قواعد بيانات الكائن خيارًا صالحًا. أود أن أقترح النظر في DB4O أو كارفونيت.

أول شيء سألقي نظرة عليه هو قاعدة بيانات. ومع ذلك ، التسلسل هو خيار. إذا ذهبت للتسلسل الثنائي ، فأنا يتجنب BinaryFormatter - يميل إلى الغضب بين الإصدارات إذا قمت بتغيير الحقول وما إلى ذلك XmlSerialzier سيكون على ما يرام ، ويمكن أن تكون متوافقة جنبًا إلى جنب (أي مع نفس تعريفات الفئة) مع Protobuf-Net إذا كنت ترغب في تجربة التسلسل الثنائي القائم على العقد (يمنحك مسلسل ملف مسطح دون أي جهد).

تحاول الكثير من الإجابات في هذا الموضوع الإفراط في هندسة الحل. إذا كنت على صواب ، فأنت تريد فقط تخزين إعدادات المستخدم.

استخدم ملف .ini أو ملف app.config لهذا.

إذا كنت مخطئًا ، وكنت تقوم بتخزين بيانات أكثر من مجرد إعدادات ، فاستخدم ملفًا نصيًا مسطحًا بتنسيق CSV. هذه سريعة وسهلة دون النفقات العامة لـ XML. يحب الأشخاص أن يولوا هذه لأنها ليست أنيقة ، ولا تتوسع بشكل جيد ولا تبدو جيدة في السيرة الذاتية ، لكنها قد تكون أفضل حل لك اعتمادًا على ما تحتاجه.

لقد قمت بالعديد من تطبيقات "الوقوف بمفردها" التي تحتوي على متجر بيانات محلي. أعتقد أن أفضل شيء لاستخدامه سيكون إصدار SQL Server Compact (المعروف سابقًا باسم Sqlanywhere).

انها خفيفة الوزن وخالية. بالإضافة إلى ذلك ، يمكنك الالتزام بكتابة طبقة الوصول إلى البيانات التي يمكن إعادة استخدامها في مشاريع أخرى بالإضافة إلى ما إذا كان التطبيق يحتاج إلى توسيع نطاق شيء أكبر مثل خادم SQL الكامل ، تحتاج فقط إلى تغيير سلسلة الاتصال.

الميل الأول لي هو قاعدة بيانات الوصول. يتم تخزين ملفات .mdb محليًا ، ويمكن تشفيرها إذا كان ذلك ضروريًا. على الرغم من أن XML أو JSON ستعمل أيضًا في العديد من السيناريوهات. الملفات المسطحة التي لا يمكنني استخدامها فقط لقراءة فقط ، معلومات غير البحث (إعادة التوجيه فقط). أميل إلى تفضيل تنسيق CSV لتعيين العرض.

يعتمد ذلك على كمية البيانات التي تتطلع إلى تخزينها. في الواقع لا يوجد فرق بين الملفات المسطحة و XML. من المحتمل أن يكون XML أفضل لأنه يوفر بنية للوثيقة. في التمرين،

الخيار الأخير ، والكثير من التطبيقات التي تستخدمها الآن هو سجل Windows. أنا لا أوصي به شخصيا (السجل الانتفاخ ، الفساد ، القضايا المحتملة الأخرى) ، لكنه خيار.

دون معرفة شكل بياناتك ، أي التعقيد ، والحجم ، وما إلى ذلك ... XML سهل الحفاظ عليه ويمكن الوصول إليه بسهولة. لن أستخدم قاعدة بيانات الوصول ، ويصعب الحفاظ على الملفات المسطحة على مدار طويل ، خاصة إذا كنت تتعامل مع أكثر من حقل/عنصر بيانات واحد في ملفك.

أتعامل مع بيانات بيانات الملفات المسطحة الكبيرة بكميات جيدة يوميًا ، وعلى الرغم من أن مثالًا متطرفًا ، إلا أن بيانات الملف المسطح أكثر صعوبة في الحفاظ عليها من خلاصات بيانات XML التي أعالجها.

مثال بسيط لتحميل بيانات XML في مجموعة بيانات باستخدام C#:

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

يمكنك أيضًا التحقق من LINQ إلى XML كخيار للاستعلام عن بيانات XML ...

هول ...

إذا ذهبت إلى مسار التسلسل الثنائي ، ففكر في السرعة التي يحتاج بها عضو معين في المسند إلى الوصول إليه. إذا كانت مجموعة صغيرة فقط ، فسيكون تحميل الملف بأكمله منطقيًا ، ولكن إذا كان كبيرًا ، فقد تفكر أيضًا في ملف فهرس.

يمكن أن يساعدك تتبع خصائص/حقول الحساب الموجودة في عنوان محدد داخل الملف في تسريع وقت الوصول ، خاصة إذا قمت بتحسين ملف الفهرس بناءً على استخدام المفتاح. (ربما حتى عندما تكتب إلى القرص.)

اعتمادًا على إجبار كائن حسابك ، أود أن أوصي إما 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 (الذي يحتاج إلى مزيد من الوقت لإعداده واستخدامه ، في معظم الأوقات تكون مبالغة).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top