Чтение данных из файла фиксированной длины в объекты класса

У меня есть файл фиксированной длины, и я хотел бы прочитать его данные в объектах класса. Эти объекты в дальнейшем будут использоваться для вставки/обновления данных в БД. Хотя это можно сделать с помощью StreamReader, я ищу более сложное решение. FileHelper — еще одно решение, но я не хочу использовать открытый исходный код в своей программе. Есть ли другой вариант?

В приведенной ниже ссылке один пользователь ответил на этот аналогичный вопрос, но он не уточняется:

https://codereview.stackexchange.com/questions/27782/how-to-read-fixed-width-data-fields-in-net

Я пытался реализовать это, но не могу найти атрибут Layout().

Спасибо.

Пример файла фиксированной длины:

aCSTDCECHEUR20140701201409161109 //Header of the file
b0000000000050115844085700800422HB HERBOXAN-COMPACT WHITE 12,5L         0000002297P0000000184L0000000000 0000000000
zCSTDCECH201409161109 148 //Footer of the file

person IFlyHigh    schedule 26.09.2014    source источник
comment
Как выглядит ваш файл фиксированной длины? Можем ли мы взглянуть на содержимое и на то, что вы пробовали, прежде чем предлагать что-либо?   -  person Vivek Jain    schedule 26.09.2014
comment
@theghostofc Прямо сейчас я внедряю StreamReader, а позже планирую использовать простые строковые функции, такие как Spilled или indexof, для получения данных. код будет выглядеть намного чище. Обратите внимание, что я не прошу здесь код, а просто хочу знать, какие у меня есть варианты в этом случае.   -  person IFlyHigh    schedule 26.09.2014
comment
Отредактировал мой вопрос также для образца файла фиксированной длины.   -  person IFlyHigh    schedule 26.09.2014
comment
Можно использовать следующую библиотеку: github.com/borisdj/FixedWidthParserWriter   -  person borisdj    schedule 20.11.2018


Ответы (2)


Я не знаю, как ваши данные были сериализованы (вы не указываете ни протокола, ни описания данных); однако вы сказали, что разработка решения для другого вопроса решит вашу проблему. Я даю вам пояснение к этому: вам будет легко изменить мою реализацию, чтобы данные анализировались в соответствии с вашим форматом (вместо использования двоичного потока, как я сделал в следующем примере).

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

Я могу привести пример реализации здесь (это просто пример, отредактируйте его перед использованием в производстве...):

Файл, содержащий вашу структуру данных:

//MyData.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FixedLengthFileReader
{
    class MyData
    {
        [Layout(0, 10)]
        public string field1;
        [Layout(10, 4)]
        public int field2;
        [Layout(14, 8)]
        public double field3;

        public override String ToString() {
            return String.Format("String: {0}; int: {1}; double: {2}", field1, field2, field3);
        }
    }
}

Атрибут:

// LayoutAttribute.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FixedLengthFileReader
{
    [AttributeUsage(AttributeTargets.Field)]
    class LayoutAttribute : Attribute
    {
        private int _index;
        private int _length;

        public int index
        {
            get { return _index; }
        }

        public int length
        {
            get { return _length; }
        }

        public LayoutAttribute(int index, int length)
        {
            this._index = index;
            this._length = length;
        }
    }
}

Пример реализации считывателя:

//FixedLengthReader.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;

namespace FixedLengthFileReader
{
    class FixedLengthReader
    {
        private Stream stream;
        private byte[] buffer;

        public FixedLengthReader(Stream stream)
        {
            this.stream = stream;
            this.buffer = new byte[4];
        }

        public void read<T>(T data)
        {
            foreach (FieldInfo fi in typeof(T).GetFields())
            {
                foreach (object attr in fi.GetCustomAttributes())
                {
                    if (attr is LayoutAttribute)
                    {
                        LayoutAttribute la = (LayoutAttribute)attr;
                        stream.Seek(la.index, SeekOrigin.Begin);
                        if (buffer.Length < la.length) buffer = new byte[la.length];
                        stream.Read(buffer, 0, la.length);

                        if (fi.FieldType.Equals(typeof(int)))
                        {
                            fi.SetValue(data, BitConverter.ToInt32(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(bool)))
                        {
                            fi.SetValue(data, BitConverter.ToBoolean(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(string)))
                        {
                            // --- If string was written using UTF8 ---
                            byte[] tmp = new byte[la.length];
                            Array.Copy(buffer, tmp, tmp.Length);
                            fi.SetValue(data, System.Text.Encoding.UTF8.GetString(tmp));

                            // --- ALTERNATIVE: Chars were written to file ---
                            //char[] tmp = new char[la.length - 1];
                            //for (int i = 0; i < la.length; i++)
                            //{
                            //    tmp[i] = BitConverter.ToChar(buffer, i * sizeof(char));
                            //}
                            //fi.SetValue(data, new string(tmp));
                        }
                        else if (fi.FieldType.Equals(typeof(double)))
                        {
                            fi.SetValue(data, BitConverter.ToDouble(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(short)))
                        {
                            fi.SetValue(data, BitConverter.ToInt16(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(long)))
                        {
                            fi.SetValue(data, BitConverter.ToInt64(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(float)))
                        {
                            fi.SetValue(data, BitConverter.ToSingle(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(ushort)))
                        {
                            fi.SetValue(data, BitConverter.ToUInt16(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(uint)))
                        {
                            fi.SetValue(data, BitConverter.ToUInt32(buffer, 0));
                        }
                        else if (fi.FieldType.Equals(typeof(ulong)))
                        {
                            fi.SetValue(data, BitConverter.ToUInt64(buffer, 0));
                        }
                    }
                }
            }
        }
    }
}

И напоследок пример реализации программы (очень простой):

// Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace FixedLengthFileReader
{
    class Program
    {
        static void Main(string[] args)
        {
            MyData md = new MyData();
            Console.WriteLine(md);

            Stream s = File.OpenRead("testFile.bin");
            FixedLengthReader flr = new FixedLengthReader(s);
            flr.read(md);
            s.Close();

            Console.WriteLine(md);
        }
    }
}

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

41 42 43 44 45 46 47 48 49 4A 01 00 00 00 00 00 00
00 00 00 E0 3F

Который представляет байты для:

  • Строка ABCDEFGHIJ (10 байт)
  • Целое число 1 (4 байта)
  • Двойной 0,5 (8 байт)

(Я создал файл с помощью XVI32, добавив этот шестнадцатеричный код и сохранив его как testFile.bin)

person Giuseppe    schedule 29.09.2014
comment
Спасибо за образец кода (и электронное письмо). Учитывая, что я только что написал немного более общую версию ответа с помощью Generics, Compiled Expression Trees и Convert.ChangeType, я не публиковал новый ответ, но вы можете прочитать мой пост в блоге об этом и увидеть пример кода в terryaney.wordpress.com/2014/10/01/. - person Terry; 01.10.2014
comment
Похоже, это решение не подходит для варианта использования верхнего/нижнего колонтитула? Хотя можно использовать класс Visual Basic TextFieldParser для вызова SetFieldWidths для разметки файла для чтения, наличие записей верхнего и нижнего колонтитула может все усложнить. Я имею дело с файлом фиксированной длины, в котором есть несколько промежуточных записей заголовка/нижнего колонтитула, разбросанных по всему файлу, что усложняет общий синтаксический анализ. - person PeterX; 09.02.2015
comment
Целью моего ответа было предоставить решение основной проблемы ОП: Я пытался реализовать это, но не могу найти атрибут Layout(). (мне кажется, что это единственная проблема ОП в ее описании ). Моя основная цель состояла в том, чтобы показать пример использования атрибутов, а не предоставить полную реализацию (как я писал: вам будет легко изменить мою реализацию, чтобы данные анализировались в соответствии с вашим форматом ). MyData в моем примере может содержать раздел заголовка, имеющий атрибуты Layout для переменных заголовка и то же самое для переменных нижнего колонтитула. Возможно ли это в вашем случае использования? - person Giuseppe; 10.02.2015

Если структура правильно сформирована, у меня возникнет соблазн создать серию классов ... Reader(Stream), которые имитируют вашу файловую структуру. Используя контейнер IOC, такой как Unity, вы можете передать файловый поток классу чтения «Документ» верхнего уровня и разрешить ему передавать поток «дочерним» читателям для чтения каждого компонента файла. Когда каждая логическая «запись» завершена, вы можете инициировать событие/обратный вызов в стеке записи базы данных, чтобы преобразовать граф объектов в памяти, который представляет файл, в механизм обновления вашей базы данных (что может потребовать дальнейшего преобразования или просто Mongo -как документ написать).

person PhillipH    schedule 26.09.2014