0

I am trying to save a hashset<string> to a database and then retrieve it, but I can not figure out how to use the binary reader for this.

Here in #region Types

TypeList = new Dictionary<string, Type>

I have :

[typeof(HashSet<string>).FullName]=typeof(HashSet<string>)

to write it I have:

[typeof(HashSet<string>)] = (v,w) =>
            {
                w.Write(v != null);
                if (v == null) return;
                HashSet<string> datas = (HashSet<string>)v;
                w.Write(datas.Count);
                foreach(string data in datas)
                {
                    w.Write(data);
                }
            }

But I can not figure out how to read it from the database back into a hashset

            [typeof(HashSet<string>)] = r=>
            {
                int length = r.ReadInt32();
                HashSet<string> data = new HashSet<string>();
                for (int i = 0; i < length; i++)
                    data.Add(r.ReadString());

                return data;
            }

This fails with an error:

Unable to read beyond the end of the stream.

My knowledge of binary reader is very low, I am not sure how the hashset is read from the database. Obviously the loop wont be fully correct as there is no length() in a hashset, the values are not indexed etc, the advantage (and why I need to use it elsewhere in the code) is that it can never contain two of the same value.

r.count did not work (even though that is how you find the "length" of a hashset and I have no idea how you iterate over the values inside r (or even if that's how you do it) this has driven me up the wall for two days now.

Clarification: I am adding to an open source game, user data is stored inside a database and i was having an issue with my hashset array not saving, seems that the class that deals with the database needs each type defined, this is an area that is very alien to me. having never really dealt with data storage before. I was assured that once I have the definition added to tell it how to deal with hashsets then the hashset will save....however I can not figure out how to do that part, i am trying and have been for a while (and still am). The irony is that this is something i am working on to keep my mind and mental health up while being shielded the latter is suffering for sure

Full class:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Library;

namespace MirDB
{
public sealed class DBValue
{
    internal static readonly Dictionary<string, Type> TypeList;
    private static readonly Dictionary<Type, Func<BinaryReader, object>> TypeRead;
    private static readonly Dictionary<Type, Action<object, BinaryWriter>> TypeWrite;

    static DBValue()
    {
        #region Types
        TypeList = new Dictionary<string, Type>
        {
            [typeof(Boolean).FullName] = typeof(Boolean),
            [typeof(Byte).FullName] = typeof(Byte),
            [typeof(Byte[]).FullName] = typeof(Byte[]),
            [typeof(Char).FullName] = typeof(Char),
            [typeof(Color).FullName] = typeof(Color),
            [typeof(DateTime).FullName] = typeof(DateTime),
            [typeof(Decimal).FullName] = typeof(Decimal),
            [typeof(Double).FullName] = typeof(Double),
            [typeof(Int16).FullName] = typeof(Int16),
            [typeof(Int32).FullName] = typeof(Int32),
            [typeof(Int32[]).FullName] = typeof(Int32[]),
            [typeof(Int64).FullName] = typeof(Int64),
            [typeof(Point).FullName] = typeof(Point),
            [typeof(SByte).FullName] = typeof(SByte),
            [typeof(Single).FullName] = typeof(Single),
            [typeof(Size).FullName] = typeof(Size),
            [typeof(String).FullName] = typeof(String),
            [typeof(TimeSpan).FullName] = typeof(TimeSpan),
            [typeof(UInt16).FullName] = typeof(UInt16),
            [typeof(UInt32).FullName] = typeof(UInt32),
            [typeof(UInt64).FullName] = typeof(UInt64),
            [typeof(Point[]).FullName] = typeof(Point[]),
            [typeof(Stats).FullName] = typeof(Stats),
            [typeof(BitArray).FullName] = typeof(BitArray),
            [typeof(HashSet<string>).FullName]=typeof(HashSet<string>)
        };
        #endregion

        #region Reads

        TypeRead = new Dictionary<Type, Func<BinaryReader, object>>
        {
            [typeof(Boolean)] = r => r.ReadBoolean(),
            [typeof(Byte)] = r => r.ReadByte(),
            [typeof(Byte[])] = r => r.ReadBytes(r.ReadInt32()),
            [typeof(Char)] = r => r.ReadChar(),
            [typeof(Color)] = r => Color.FromArgb(r.ReadInt32()),
            [typeof(DateTime)] = r => DateTime.FromBinary(r.ReadInt64()),
            [typeof(Decimal)] = r => r.ReadDecimal(),
            [typeof(Double)] = r => r.ReadDouble(),
            [typeof(Enum)] = r => r.ReadInt32(),
            [typeof(Int16)] = r => r.ReadInt16(),
            [typeof(Int32)] = r => r.ReadInt32(),
            [typeof(Int32[])] = r =>
            {
                if (!r.ReadBoolean()) return null;

                int length = r.ReadInt32();

                Int32[] values = new Int32[length];
                for (int i = 0; i < length; i++)
                    values[i] = r.ReadInt32();

                return values;

            },
            [typeof(Int64)] = r => r.ReadInt64(),
            [typeof(Point)] = r => new Point(r.ReadInt32(), r.ReadInt32()),
            [typeof(SByte)] = r => r.ReadSByte(),
            [typeof(Single)] = r => r.ReadSingle(),
            [typeof(Size)] = r => new Size(r.ReadInt32(), r.ReadInt32()),
            [typeof(String)] = r => r.ReadString(),
            [typeof(TimeSpan)] = r => TimeSpan.FromTicks(r.ReadInt64()),
            [typeof(UInt16)] = r => r.ReadUInt16(),
            [typeof(UInt32)] = r => r.ReadUInt32(),
            [typeof(UInt64)] = r => r.ReadUInt64(),
            [typeof(Point[])] = r =>
            {
                if (!r.ReadBoolean()) return null;

                int length = r.ReadInt32();

                Point[] points = new Point[length];
                for (int i = 0; i < length; i++)
                    points[i] = new Point(r.ReadInt32(), r.ReadInt32());

                return points;

            },
            [typeof(Stats)] = r => r.ReadBoolean() ? new Stats(r) : null,
            [typeof(BitArray)] = r =>
            {
                if (!r.ReadBoolean()) return null;

                return new BitArray(r.ReadBytes(r.ReadInt32()));
            },
            [typeof(HashSet<string>)] = r=>
            {
                int length =r.ReadInt32();
                string[] temp = new string[length];


                for (int i = 0; i < length; i++)
                {
                    temp[i] = r.ReadString();
                }
                HashSet<string> data = new HashSet<string>();
                foreach(string indata in temp)
                {
                    data.Add(indata);
                }
                return data;               
            }
        };

        #endregion

        #region Writes

        TypeWrite = new Dictionary<Type, Action<object, BinaryWriter>>
        {
            [typeof(Boolean)] = (v, w) => w.Write((bool) v),
            [typeof(Byte)] = (v, w) => w.Write((Byte) v),
            [typeof(Byte[])] = (v, w) =>
            {
                w.Write(((Byte[]) v).Length);
                w.Write((Byte[]) v);
            },
            [typeof(Char)] = (v, w) => w.Write((Char) v),
            [typeof(Color)] = (v, w) => w.Write(((Color) v).ToArgb()),
            [typeof(DateTime)] = (v, w) => w.Write(((DateTime) v).ToBinary()),
            [typeof(Decimal)] = (v, w) => w.Write((Decimal) v),
            [typeof(Double)] = (v, w) => w.Write((Double) v),
            [typeof(Int16)] = (v, w) => w.Write((Int16) v),
            [typeof(Int32)] = (v, w) => w.Write((Int32) v),
            [typeof(Int32[])] = (v, w) =>
            {
                w.Write(v != null);
                if (v == null) return;
                Int32[] values = (Int32[]) v;

                w.Write(values.Length);

                foreach (Int32 value in values)
                    w.Write(value);
            },
            [typeof(Int64)] = (v, w) => w.Write((Int64) v),
            [typeof(Point)] = (v, w) =>
            {
                w.Write(((Point) v).X);
                w.Write(((Point) v).Y);
            },
            [typeof(SByte)] = (v, w) => w.Write((SByte) v),
            [typeof(Single)] = (v, w) => w.Write((Single) v),
            [typeof(Size)] = (v, w) =>
            {
                w.Write(((Size) v).Width);
                w.Write(((Size) v).Height);
            },
            [typeof(String)] = (v, w) => w.Write((String) v ?? string.Empty),
            [typeof(TimeSpan)] = (v, w) => w.Write(((TimeSpan) v).Ticks),
            [typeof(UInt16)] = (v, w) => w.Write((UInt16) v),
            [typeof(UInt32)] = (v, w) => w.Write((UInt32) v),
            [typeof(UInt64)] = (v, w) => w.Write((UInt64) v),
            [typeof(Point[])] = (v, w) =>
            {
                w.Write(v != null);
                if (v == null) return;
                Point[] points = (Point[]) v;

                w.Write(points.Length);

                foreach (Point point in points)
                {
                    w.Write(point.X);
                    w.Write(point.Y);
                }
            },
            [typeof(Stats)] = (v, w) =>
            {
                w.Write(v != null);
                if (v == null) return;

                ((Stats)v).Write(w);
            },
            [typeof(BitArray)] = (v, w) =>
            {
                w.Write(v != null);
                if (v == null) return;

                BitArray array = (BitArray) v;


                byte[] bytes = new byte[(int) Math.Ceiling(array.Length/8d)];
                array.CopyTo(bytes, 0);

                w.Write(bytes.Length);
                w.Write(bytes);
            },
            [typeof(HashSet<string>)] = (v,w) =>
            {
                w.Write(v != null);
                if (v == null) return;
                HashSet<string> datas = (HashSet<string>)v;
                w.Write(datas.Count);
                foreach(string data in datas)
                {
                    w.Write(data);
                }
            }
        };

        #endregion
    }

    public string PropertyName { get; }
    public Type PropertyType { get; }
    public PropertyInfo Property { get; }

    public DBValue(BinaryReader reader, Type type)
    {
        PropertyName = reader.ReadString();
        PropertyType = TypeList[reader.ReadString()];


        PropertyInfo property = type?.GetProperty(PropertyName);

        if (property != null)
            if (property.GetCustomAttribute<IgnoreProperty>() != null) 
return;

        Property = property;
    }
    public DBValue(PropertyInfo property)
    {
        Property = property;

        PropertyName = property.Name;

        if (property.PropertyType.IsEnum)
        {
            PropertyType = property.PropertyType.GetEnumUnderlyingType();
            return;
        }

        if (property.PropertyType.IsSubclassOf(typeof(DBObject)))
        {
            PropertyType = typeof(int);
            return;
        }

        PropertyType = property.PropertyType;
    }

    public void Save(BinaryWriter writer)
    {
        writer.Write(PropertyName);
        writer.Write(PropertyType.FullName);
    }

    public object ReadValue(BinaryReader reader)
    {
        return TypeRead[PropertyType](reader);
    }
    public void WriteValue(object value, BinaryWriter writer)
    {
        TypeWrite[PropertyType](value, writer);
    }


    public bool IsMatch(DBValue value)
    {
        return string.Compare(PropertyName, value.PropertyName, StringComparison.Ordinal) == 0 && PropertyType == value.PropertyType;
    }
}

}

  • Why would you turn a perfectly good string into bytes? String are the 2nd worst type to process. Bytes are the worst. – Christopher May 24 '20 at 12:33
  • well i have a list of strings that are added to during the runtime, that can not repeat so a hashset seemed the logical solution...infact in the code its perfect....right up to the point i need to store and retireve the data from it between boots – David Eastwick May 24 '20 at 12:42
  • I am not wondering about the strings. I figured they are a nessesary evil. I wonder why you would turn the 2nd worst type (string) into the worst type (byte), when DB's have support for strings. It sounds like you got a unresolved 1:N relationship. – Christopher May 24 '20 at 13:07
  • Thing is I do not know anything about database storage, just that I know I have to define how the binary reader deals with hashsets to allow me to save the hashset.....the write code is me winging it, if i have that bit wrong or if there is a better way to define the hashset to the binary reader please say. – David Eastwick May 24 '20 at 13:20
  • What is this syntax `[typeof(HashSet)] = (v,w) =>` and `[typeof(HashSet)] = r=>`? What are all the types involved, at least with `v`, `w` and `r`? Also, this code should generally just work: you write the count of the source hashset, and then all its contents, and to read it back, you read the count N and then read N strings from the source. That should just work. If it doesn't, create a [mre]. Probably you're not reading what you think you are. – CodeCaster May 24 '20 at 13:26
  • Put the full class into the post r is the binary reader w is the binary writer V is the "object" I confess I would not know where to begin on creating a minimal reproducible example. I am very much at the hobby level when it comes to coding – David Eastwick May 24 '20 at 13:35

2 Answers2

1

Something with those whole operation just seems wrong:

You got a Collection of strings in memory.

But instead of storing it into a table with a ForeignKey and VARCHAR Colum, you turn them into a byte stream to store the bytes?

Data should be kept into a DB processable format for as long as possible. Data Integrity should be left to the DB, for as long as possible. That you will not be able to do meaningfull processing or Data Integrity on Images Files, most Office Documents or similar BLOBs is unavoidable, but you actually have processable strings.

Given the limited information, I would create a table with:

  • ID. Just the primary key for this table
  • Fk_WhateverThisRelatesTo the key of whatever is going to hold the HashSet
  • VARCHAR StringValue - the value for each string

Then put a UNIQUE constraint over the combination of Fk_WhateverThisRelatesTo and StringValue. The Fk and StringValues can duplicate, but no combination of both can.

Christopher
  • 9,634
  • 2
  • 17
  • 31
  • 3
    You're assuming a relational database, but they're talking about modifying an existing open source game that uses a BinaryReader/BinaryWriter to save its state. The game makers would appear to have decided on a binary save file format, not an RDBMS, and the OP will have to use that. – CodeCaster May 24 '20 at 14:18
  • @CodeCaster Ah, that explains it. – Christopher May 24 '20 at 14:25
0

Seems i was not reading the boolean at first....this one line has driven me around the bend for two days..... just couldnt see that it was missing.

[typeof(HashSet<string>)] = r=>
            {
                if (!r.ReadBoolean()) return null;
                int length =r.ReadInt32();
                string[] temp = new string[length];

                for (int i = 0; i < length; i++)
                {
                    temp[i] = r.ReadString();
                }
                HashSet<string> data = new HashSet<string>();
                foreach(string indata in temp)
                {
                    data.Add(indata);
                }
                return data;               
            }

needed this line:

if (!r.ReadBoolean()) return null;