문제

저는 사용자 데이터를 가져와서 나중에 사용하기 위해 로컬에 저장하는 애플리케이션을 작성 중입니다.애플리케이션이 상당히 자주 시작되고 중지되며, 애플리케이션 시작/종료 시 데이터를 저장/로드하도록 만들고 싶습니다.

데이터가 실제로 보안될 필요가 없기 때문에 플랫 파일을 사용한다면 상당히 간단할 것입니다(데이터는 이 PC에만 저장됩니다).따라서 내가 믿는 옵션은 다음과 같습니다.

  • 플랫 파일
  • XML
  • SQLDB

플랫 파일은 유지 관리하는 데 좀 더 많은 노력이 필요하지만(XML과 같은 내장 클래스가 없음) 이전에 XML을 사용한 적이 없으며 SQL은 상대적으로 쉬운 이 작업에는 과도한 것처럼 보입니다.

탐험해 볼 만한 다른 방법이 있나요?그렇지 않다면 다음 중 가장 좋은 솔루션은 무엇입니까?


편집하다:문제에 데이터를 조금 더 추가하기 위해 기본적으로 제가 저장하고 싶은 유일한 것은 다음과 같은 사전입니다.

Dictionary<string, List<Account>> 

여기서 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>

보시다시피 상속은

  • 사용자 이름(dict 문자열) >
  • 계정(목록의 각 계정) >
  • 계정 데이터(예: 클래스 속성)

이 레이아웃을 다음에서 가져옵니다. Dictionary<Username, List<Account>> 까다로운 부분이고 이 질문의 본질입니다.

여기에 직렬화에 대한 '방법' 응답이 많이 있습니다. 이는 초기에 더 명확하게 하지 않았기 때문에 내 잘못이지만 이제는 확실한 해결책을 찾고 있습니다.

도움이 되었습니까?

해결책

파일을 다음과 같이 저장했습니다 JSON. 이름/값 쌍 목록 인 사전을 저장하고 있기 때문에 이것이 JSON이 설계 한 것과 거의 같습니다.
꽤 괜찮은 무료 .NET JSON 라이브러리가 있습니다. 하나 그러나 첫 번째 링크에서 전체 목록을 찾을 수 있습니다.

다른 팁

실제로 저장하는 것에 달려 있습니다. 구조화 된 데이터에 대해 이야기하는 경우 XML 또는 SQLITE 또는 SQL Server Compact Edition과 같은 매우 가벼운 SQL RDBM이 귀하에게 적합합니다. 데이터가 사소한 크기를 넘어 이동하면 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 Managed Interface 프로젝트를 사용해 보십시오. 코드플렉스.

ESENT는 Windows의 일부인 내장형 데이터베이스 스토리지 엔진(ISAM)입니다.행 수준 잠금, 미리 쓰기 로깅 및 스냅샷 격리를 통해 안정적이고 트랜잭션된 동시 고성능 데이터 스토리지를 제공합니다.이는 ESENT Win32 API에 대한 관리형 래퍼입니다.

사용하기 매우 쉬운 PertantDictionary 개체가 있습니다.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();
    }

George의 질문에 대답하려면:

지원되는 키 유형

이러한 유형 만 사전 키로 지원됩니다.

부울 바이트 int16 UINT16 int32 UINT32 int64 UINT64 플로트 더블 그 안내 데이터 타임 타임 스팬 문자열

지원되는 값 유형

사전 값은 주요 유형, 핵심 유형의 무효 버전, URI, iPaddress 또는 직렬화 가능한 구조 중 하나 일 수 있습니다.구조는 이러한 모든 기준을 충족하는 경우에만 직렬화 가능한 것으로 간주됩니다.

• 구조는 직렬화 가능하다. • 구조물의 모든 구성원은 다음 중 하나입니다.1.기본 데이터 유형(예:int32) 2.문자열, URI 또는 ​​iPaddress 3.직렬화 가능한 구조입니다.

또는 다른 방법으로 말하면, 직렬화 가능한 구조에는 클래스 객체에 대한 참조를 포함 할 수 없습니다.이것은 API 일관성을 보존하기 위해 수행됩니다.영구적으로 객체를 추가하면 직렬화를 통해 객체의 사본이 생성됩니다.원래 객체를 수정하면 사본이 수정되지 않으므로 혼란스러운 동작이 발생합니다.이러한 문제를 피하기 위해 영구적 인 것은 값 유형 만 값으로 만 받아 들일 것입니다.

직렬화 가능 SERIALIZABLE] Struct Good {public dateTime?받았다;공개 문자열 이름;공개 십진수 가격;공개 Uri URL;}

직렬화할 수 없음 SERIALIZABLE] 구조 BAD {public byte [] 데이터;// 배열이 지원되는 공개 예외 오류가 아닙니다.// 참조 객체 }

파일에 쉽게 직렬화되기 때문에 XML Reader/Writer 클래스를 권장합니다.

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의 직렬화 API는 정말 쉽습니다.

바이너리 또는 XML 파일을 선택하든 다른 직렬화를 사용하더라도 동일한 직렬화 API를 사용할 수 있습니다.

클래스를 직렬화하려면 [Serializable] 속성으로 표시되거나 Iserializable을 구현해야합니다.

당신은 비슷한 일을 할 수 있습니다 XML, 인터페이스를 ixmlserializable이라고하지만 속성은 [xmlroot] 및 System.xml.serialization 네임 스페이스의 기타 속성입니다.

관계형 데이터베이스를 사용하려면 SQL Server Compact Edition 무료이며 매우 가벼우 며 단일 파일을 기반으로합니다.

컬렉션이 너무 커지면 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;
    }
}

현재 프로젝트를위한 데이터 저장소 코딩을 마쳤습니다. 여기 내 5 센트가 있습니다.

이진 직렬화로 시작했습니다. 느리게 (100,000 개 객체의 하중에 대해 약 30 초)였으며 디스크에도 큰 파일을 생성하고있었습니다. 그러나 구현하는 데 몇 줄의 코드가 필요했고 모든 스토리지 요구 사항을 다루었습니다. 더 나은 성능을 얻으려면 사용자 정의 직렬화로 이동했습니다. Code Project에서 Tim Haynes의 Fastserialization 프레임 워크를 발견했습니다. 실제로 몇 배 더 빠르며 (로드의 경우 12 초, 저장의 경우 8 초, 100k 레코드)가 적고 디스크 공간이 줄어 듭니다. 프레임 워크는 이전 게시물에서 Galacticjello가 설명한 기술을 기반으로합니다.

그런 다음 SQLITE로 옮겼고 가끔 3 배 빠른 성능을 얻을 수있었습니다 - 하중의 경우 6 초, 저장을 위해 4 초, 100k 레코드를 얻을 수있었습니다. ADO.NET 테이블을 애플리케이션 유형으로 구문 분석하는 것이 포함됩니다. 또한 디스크에서 훨씬 작은 파일을 제공했습니다. 이 기사는 Ado.net에서 최고의 성능을 얻는 방법을 설명합니다. http://sqlite.phxsoftware.com/forums/134.aspx. 삽입 문을 생성하는 것은 매우 나쁜 생각입니다. 내가 그것에 대해 어떻게 알게되었는지 추측 할 수 있습니다. :) 실제로, SQLITE 구현은 거의 모든 코드 라인에 의해 시간을 꽤 많은 시간과 신중하게 측정했습니다.

데이터가 복잡한 경우 수량이 많거나 로컬로 쿼리 해야하는 경우 객체 데이터베이스가 유효한 옵션이 될 수 있습니다. 나는 보는 것이 좋습니다 DB4O 또는 카보나이트.

내가 가장 먼저 보는 것은 데이터베이스입니다. 그러나 직렬화는 옵션입니다. 이진 직렬화를 위해 가면 피하다 BinaryFormatter - 필드 등을 변경하면 버전간에 화를내는 경향이 있습니다. XML을 통해 XmlSerialzier 계약 기반 바이너리 직렬화를 시도하려면 프로토 비프 네트와 함께 나란히 호환 될 수 있으며 (예 : 노력없이 플랫 파일 직렬 라이저를 제공).

이 스레드의 많은 답변은 솔루션을 과도하게 엔지니어링하려고합니다. 내가 맞으면 사용자 설정을 저장하고 싶다.

이를 위해 .ini 파일 또는 app.config 파일을 사용하십시오.

내가 틀렸고 단순한 설정 이상의 데이터를 저장하고 있다면 플랫 텍스트 파일을 CSV 형식으로 사용하십시오. XML의 오버 헤드없이 빠르고 쉽습니다. 사람들은 우아하지 않기 때문에 똥 똥을 좋아하고, 멋지게 확장하지 않으며 이력서에서는 좋아 보이지 않지만, 필요한 것에 따라 최상의 솔루션 일 수 있습니다.

로컬 데이터 저장소가있는 몇 가지 "독립형"앱을 수행했습니다. 사용하기 가장 좋은 것은 SQL Server Compact Edition (이전 Sqlanywhere라고 함)입니다.

가볍고 무료입니다. 또한 다른 프로젝트에서 재사용 할 수있는 데이터 액세스 계층을 작성하는 데 고수 할 수 있으며 앱이 완전히 날아간 SQL Server와 같은 더 큰 것으로 확장 해야하는 경우 연결 문자열 만 변경하면됩니다.

첫 번째 성향은 액세스 데이터베이스입니다. .MDB 파일은 로컬로 저장되며 필요한 것으로 간주되면 암호화 할 수 있습니다. XML 또는 JSON도 많은 시나리오에서도 작동합니다. 플랫 파일은 읽기 전용, 비 검색 (Forward Read Only) 정보에만 사용합니다. 너비를 설정하기 위해 CSV 형식을 선호하는 경향이 있습니다.

저장하려는 데이터의 양에 따라 다릅니다. 실제로 플랫 파일과 XML 사이에는 차이가 없습니다. XML은 문서에 구조를 제공하기 때문에 바람직 할 것입니다. 실제로,

마지막 옵션과 많은 응용 프로그램이 지금 사용하는 것은 Windows 레지스트리입니다. 나는 개인적으로 그것을 추천하지는 않지만 (레지스트리 블로트, 부패, 기타 잠재적 문제), 옵션입니다.

데이터가 어떻게 보이는지 알지 못하고 복잡성, 크기 등 ... XML은 유지 관리하기 쉽고 쉽게 액세스 할 수 있습니다. 액세스 데이터베이스를 사용하지 않으며, 특히 파일에 둘 이상의 데이터 필드/요소를 처리하는 경우 플랫 파일은 장거리 운반을 유지하기가 더 어렵습니다.

나는 매일 큰 플랫 파일 데이터 피드를 잘 다루고 있으며, 극단적 인 예를 들어, 평면 파일 데이터는 XML 데이터 피드 I를 처리하는 것보다 유지하기가 훨씬 어렵습니다.

C#을 사용하여 XML 데이터를 데이터 세트에로드하는 간단한 예.

DataSet reportData = new DataSet();

reportData.ReadXml(fi.FullName);

XML 데이터를 쿼리하기위한 옵션으로 LINQ에서 XML을 확인할 수도 있습니다.

HTH ...

이진 직렬화 경로를 사용하는 경우 데이텀의 특정 구성원에 액세스 해야하는 속도를 고려하십시오. 작은 컬렉션 일 뿐이라면 전체 파일을로드하는 것이 합리적이지만 큰 경우 인덱스 파일을 고려할 수도 있습니다.

파일 내 특정 주소에 위치한 계정 속성/필드 추적은 특히 주요 사용법을 기반으로 해당 인덱스 파일을 최적화하는 경우 액세스 시간 속도를 높이는 데 도움이 될 수 있습니다. (디스크에 글을 쓸 때도 가능합니다.)

계정 객체의 강력성에 따라 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

... 기타 등등. String Dictionary에 직접 매핑하므로 속성 파일에서 읽는 것은 쉬워야합니다.

이 파일을 어디에서 저장 해야하는지에 대해서는 최고의 선택은 프로그램의 하위 폴더 안에 AppData 폴더에 저장하는 것입니다. 이것은 현재 사용자가 항상 쓰기에 액세스 할 수있는 위치이며 OS 자체에 의해 다른 사용자로부터 안전하게 보관됩니다.

간단하게 유지하십시오 - 당신이 말했듯이, 플랫 파일만으로도 충분합니다. 플랫 파일을 사용하십시오.

이것은 요구 사항을 올바르게 분석했다고 가정합니다. XML 단계로 직렬화를 건너 뛰고 간단한 사전에 대한 과잉을 건너 뛸 것입니다. 데이터베이스와 동일합니다.

대부분의 경우 파일의 JSON만으로도 충분합니다 (주로 배열이나 객체 또는 단일 번호 또는 문자열을 저장해야합니다). 나는 sqlite가 거의 필요하지 않습니다 (그것을 설정하고 사용하는 데 더 많은 시간이 필요합니다. 대부분은 과잉입니다).

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top