Сериализация типизированного DataSet
От: Karamat Беларусь  
Дата: 28.02.05 12:42
Оценка:
Подумал и решил запостить сюда, может кому-нибудь пригодится.

У меня возникла проблема передачи датасета через ремотинг.
Здесь
Автор: VladD2
Дата: 21.04.03
есть решение для не типизированного датасета, соответственно передается информация о структуре данных.
У меня структура известна на клиенте и сервере, и лишний трафик не нужен. Поэтому я немного поправил код:

using System;
using System.IO;
using System.Data;
using System.Collections;

namespace RSDN
{
    public class DataSerializer
    {
        static readonly DataRowVersion[] _aryVer = new DataRowVersion[2] 
                { DataRowVersion.Original, DataRowVersion.Current };


        ///////////////////////////////////////////////////////////////////////
        // Сериализация.

        public static byte[] SerializeDataSet(DataSet ds)
        {
            MemoryStream ms = new MemoryStream();
            SerializeDataSet(ms, ds);
            return ms.ToArray();
        }
        public static void SerializeDataSet(Stream stream, DataSet ds)
        {
            BinaryWriter bw = new BinaryWriter(stream);
            DataTableCollection tables = ds.Tables;

            //bw.Write(ds.DataSetName);
            int tablesCount = 0;
            
            foreach(DataTable dt in tables)
            {    // Вообще-то foreach-и лучше на всякий пожарный избегать.
                // Но мне было в лом.
                if(dt.Rows.Count > 0)
                    tablesCount++;
            }

            bw.Write(tablesCount);

            foreach(DataTable dt in tables)
            {    // Вообще-то foreach-и лучше на всякий пожарный избегать.
                // Но мне было в лом.
                if(dt.Rows.Count > 0)
                    SerializeDataTable(bw, dt);
            }

            
        }

        public static void SerializeDataTable(BinaryWriter bw, DataTable dt)
        {
            DataColumnCollection columns = dt.Columns;
            int iColCount = columns.Count;

            // Имя таблицы
            bw.Write(dt.TableName);



            // Записываем битовое поле описывающее колонки поддерживающие
            // DBNull. Если бит поднят, значит, колонка поддерживает DBNull.
            BitArray bitsNull = new BitArray(iColCount);
            byte[] byteNull = new byte[(iColCount + 7) / 8];


            ///////////////////////////////////////////////////////////////
            // add data

            // count rows
            bw.Write(dt.Rows.Count);

            // Записываем строки
            foreach(DataRow dr in dt.Rows)
            {
                byte verFlags = 0;
                int iVerStart;
                int iVerEnd;
                // Разбираемся, какие версии нужно писать.
                // Всего есть два варианта: Original и Current
                DataRowState state = dr.RowState;
                switch(state)
                {
                        // Original + Current и они равны!
                    case DataRowState.Unchanged: 
                        iVerStart = 0;
                        iVerEnd = 0;
                        verFlags = 0;
                        break;
                    case DataRowState.Deleted: // Только Original
                        iVerStart = 0;
                        iVerEnd = 0;
                        verFlags = 1;
                        break;
                    case DataRowState.Added: // Только Current
                        iVerStart = 1;
                        iVerEnd = 1;
                        verFlags = 2;
                        break;
                        // Original + Current и они НЕ равны!
                    case DataRowState.Modified:
                        iVerStart = 0;
                        iVerEnd = 1;
                        verFlags = 3;
                        break;
                    default:
                        throw new ApplicationException(
                            "Недопустимое состояние строки: " + state.ToString());
                }

                // Пишем описание версий. Временно, так как на этом мы
                // теряем байт на строку. Куда лучше писать дополнительные два
                // бита в битовое поле DbNull (хотя это и не красиво).
                bw.Write(verFlags);

                // Записываем версии текущей строки. Всего их может быть две.
                // в принципе можно было бы для случая DataRowState.Modified
                // писать только дельту данных. Но это как-нибудь потом. :)
                for(int iVetIndex = iVerStart; iVetIndex <= iVerEnd; iVetIndex++)
                {
                    DataRowVersion drv = _aryVer[iVetIndex];


                    // Создаем и заполняем битовое поле. Если бит поднят,
                    // значит, соответствующая колонка содержит DBNull.
                    bitsNull.SetAll(false);
                    for(int i = 0; i < iColCount; i++)
                    {
                        if(dr[i, drv] == DBNull.Value)
                            bitsNull.Set(i, true);
                    }

                    bitsNull.CopyTo(byteNull, 0);
                    // Записываем битовое поле в стрим.
                    bw.Write(byteNull);


                    // Перебираем колонки и пишем данные...
                    for(int i = 0; i < iColCount; i++)
                    {
                        // Если колонка содержит DBNull, записывать ее значение 
                        // ненужно.
                        object data = dr[i, drv];
                        if(data == DBNull.Value) // Учитываем версию!
                            continue;

                        // Записываем данные ячейки.
                        switch(Type.GetTypeCode(dt.Columns[i].DataType))
                        {    // Каждому типу соответствует переопределенная функция...
                            case TypeCode.Boolean: bw.Write((Boolean)data); break;
                            case TypeCode.Char: bw.Write((Char)data); break;
                            case TypeCode.SByte: bw.Write((SByte)data); break;
                            case TypeCode.Byte: bw.Write((Byte)data); break;
                            case TypeCode.Int16: bw.Write((Int16)data); break;
                            case TypeCode.UInt16: bw.Write((UInt16)data); break;
                            case TypeCode.Int32: bw.Write((Int32)data); break;
                            case TypeCode.UInt32: bw.Write((UInt32)data); break;
                            case TypeCode.Int64: bw.Write((Int64)data); break;
                            case TypeCode.UInt64: bw.Write((UInt64)data); break;
                            case TypeCode.Single: bw.Write((Single)data); break;
                            case TypeCode.Double: bw.Write((Double)data); break;
                            case TypeCode.Decimal: bw.Write((Decimal)data); break;
                            case TypeCode.DateTime:
                                // Для DateTime приходится выпендриваться особым образом.
                                bw.Write(((DateTime)(data)).ToFileTime());
                                break;
                            case TypeCode.String: bw.Write((String)data); break;
                            default:
                                //разбираемся - не массив ли это байтов
                                if(dt.Columns[i].DataType == typeof(byte[]))
                                {
                                    byte[] array = (byte[])data;
                                    bw.Write(array.Length);
                                    bw.Write(array);
                                }
                                else
                                {
                                    // На всякий случай пробуем записать неопознанный тип
                                    // виде строки.
                                    bw.Write(data.ToString());
                                }
                                break;
                        }
                    }
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        // Десериализация

        private static ArrayList RemoveReadOnly(DataTable dt)
        {
            ArrayList roColumnsNumbers = null;
            int numCols = dt.Columns.Count;
            for (int i = 0; i < numCols; i++)
            {
                if(dt.Columns[i].ReadOnly)
                {
                    if(roColumnsNumbers == null)
                        roColumnsNumbers = new ArrayList();
                    roColumnsNumbers.Add(i);
                    dt.Columns[i].ReadOnly = false;
                }
            }

            return roColumnsNumbers;
        }

        private static void ResetReadOnly(ArrayList roColumnsNumbers, DataTable dt)
        {
            if(roColumnsNumbers != null)
            {
                int numCols = roColumnsNumbers.Count;
                for (int i = 0; i < numCols; i++)
                {
                    dt.Columns[(int)roColumnsNumbers[i]].ReadOnly = true;
                }
            }
        }

        public static void DeserializeTable(BinaryReader br, DataTable dt)
        {
            ArrayList roColumnsNumbers = RemoveReadOnly(dt);
            try
            {
                DataRow dr;
                dt.BeginLoadData();

                int iColCount = dt.Columns.Count;


                byte[] byteNull = new byte[(iColCount + 7) / 8];
            
                int counRows = br.ReadInt32();
                DataRowCollection rows = dt.Rows;
                for(int r = 0; r < counRows; r++)
                {
                    // Читаем описание версий. Временно, так как на этом мы
                    // теряем байт на строку.
                    byte verFlags = br.ReadByte();
                    int iVerStart;
                    int iVerEnd;
                    DataRowState drs;
                    switch(verFlags)
                    {
                            // Original + Current и они равны!
                        case 0: // DataRowState.Unchanged
                            iVerStart = 0;
                            iVerEnd = 0;
                            drs = DataRowState.Unchanged;
                            break;
                        case 1: // DataRowState.Deleted Только Original
                            iVerStart = 0;
                            iVerEnd = 0;
                            drs = DataRowState.Deleted;
                            break;
                        case 2: // DataRowState.Added Только Current
                            iVerStart = 1;
                            iVerEnd = 1;
                            drs = DataRowState.Added;
                            break;
                            // Original + Current и они НЕ равны!
                        case 3: // DataRowState.Modified
                            iVerStart = 0;
                            iVerEnd = 1;
                            drs = DataRowState.Modified;
                            break;
                        default:
                            throw new ApplicationException(
                                "Недопустимое состояние строки. Сбой при загрузке.");
                    }
                
                    // Считываем данные.
                    dr = dt.NewRow();
                    rows.Add(dr);
                    dr.BeginEdit();

                    // Считываем версии текущей строки.
                    for(int iVetIndex = iVerStart; iVetIndex <= iVerEnd; iVetIndex++)
                    {

                        br.Read(byteNull, 0, byteNull.Length);
                        BitArray bitsNull = new BitArray(byteNull);


                        for(int i = 0; i < dt.Columns.Count; i++)
                        {

                            if(bitsNull.Get(i))
                            {
                                dr[i] = DBNull.Value;
                                continue;
                            }


                            switch(Type.GetTypeCode(dt.Columns[i].DataType))
                            {
                                case TypeCode.Boolean: dr[i] = br.ReadBoolean(); break;
                                case TypeCode.Char: dr[i] = br.ReadChar(); break;
                                case TypeCode.SByte: dr[i] = br.ReadSByte(); break;
                                case TypeCode.Byte: dr[i] = br.ReadByte(); break;
                                case TypeCode.Int16: dr[i] = br.ReadInt16(); break;
                                case TypeCode.UInt16: dr[i] = br.ReadUInt16(); break;
                                case TypeCode.Int32: dr[i] = br.ReadInt32(); break;
                                case TypeCode.UInt32: dr[i] = br.ReadUInt32(); break;
                                case TypeCode.Int64: dr[i] = br.ReadInt64(); break;
                                case TypeCode.UInt64: dr[i] = br.ReadUInt64(); break;
                                case TypeCode.Single: dr[i] = br.ReadSingle(); break;
                                case TypeCode.Double: dr[i] = br.ReadDouble(); break;
                                case TypeCode.Decimal: dr[i] = br.ReadDecimal(); break;
                                case TypeCode.DateTime:
                                    dr[i] = DateTime.FromFileTime(br.ReadInt64());
                                    break;
                                case TypeCode.String: dr[i] = br.ReadString(); break;
                                default:
                                    if(dt.Columns[i].DataType == typeof(byte[]))
                                    {
                                        int lenght = br.ReadInt32();
                                        byte[] array = new byte[lenght];
                                        br.Read(array, 0, lenght);
                                        dr[i] = array;
                                    }
                                    else
                                    {
                                        dr[i] = Convert.ChangeType(br.ReadString(), 
                                            Type.GetTypeCode(dt.Columns[i].DataType));
                                    }
                                    break;
                            }
                        }
                        if(iVetIndex == 0)
                        {
                            dr.AcceptChanges();
                            if(iVerEnd > 0)
                                dr.BeginEdit();
                        }
                    }

                    if(drs == DataRowState.Deleted)
                        dr.Delete();

                    dr.EndEdit();
                
                }
                dt.EndLoadData();
            }
            catch
            {
                dt.RejectChanges();
                throw;
            }
            finally
            {
                ResetReadOnly(roColumnsNumbers, dt);
            }
        }

        public static void DeserializeDataSet(Stream stream, DataSet ds)
        {
            BinaryReader br = new BinaryReader(stream);
            
            int counTables = br.ReadInt32();

            for(int t = 0; t < counTables; t++)
            {
                DeserializeTable(br, ds.Tables[br.ReadString()]);
            }
        }

        public static void DeserializeDataSet(byte[] data, DataSet ds)
        {
            MemoryStream ms = new MemoryStream(data);
            DeserializeDataSet(ms, ds);
        }
    }
}


Буду благодарен за любые замечания
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.