I want to be able to save any C# object in a single column of a SQL database table. I am not clear how to convert the object into a varbinary or get it back from a varbinary. My SystemContextObjects table has an OptionValue column that is Varbinary(max).
var dc1 = new DataContextDataContext();
var optionUpdate = dc1.SystemContextObjects.SingleOrDefault(o => o.OptionId == OptionId && o.OptionKey == OptionKey);
if (optionUpdate != null)
{
optionUpdate.OptionValue = Value; <===== NEED HELP HERE...
optionUpdate.DateCreated = DateTime.Now;
optionUpdate.PageName = PageName;
var ChangeSet = dc1.GetChangeSet();
if (ChangeSet.Updates.Count > 0)
{
dc1.SubmitChanges();
return;
}
}
You can use a binary serializer for this, e.g. using the BinaryFormatter - but your classes must be serializable and be marked as such, here a simple example:
You have a simple Person class and mark it as serializable:
[Serializable]
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
}
You can then serialize it using a memory stream to extract a byte array representing the object:
Person p = new Person() { Name = "Fred Fish", Address = "2 Some Place" };
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, p);
ms.Position = 0;
byte[] personData = ms.ToArray(); // here's your data!
}
To re-create a Person object from a byte array you use de-serialization which works similar:
byte[] personData = ...
using (MemoryStream ms = new MemoryStream(personData))
{
BinaryFormatter formatter = new BinaryFormatter();
Person p = (Person)formatter.Deserialize(ms);
}
I wound up using JSON to accomplish this. I serialize/deserialize the class to/from a string and store that. Works fine.
Related
When a user uses my program, it will generate many thousands of objects over the course of a couple of hours. These cannot accumulate in RAM, so I want to write them to a single file as they are generated. Then, in a different program, the objects must all be deserialized.
When I try to serialize different objects of the same class to the same XML file and then try to deserialize later, I get:
System.InvalidOperationException: 'There is an error in XML document (1, 206).'
Inner Exception
XmlException: There are multiple root elements. Line 1, position 206.
Here is an example of a .NET 6.0 console app that recapitulates this problem:
using System.Xml.Serialization;
using System.IO;
using System.Xml;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
// Serialize a person
using (FileStream stream = new FileStream("people.xml", FileMode.Create))
{
Person jacob = new Person { Name = "Jacob", Age = 33, Alive = true };
XmlSerializer serializer = new XmlSerializer(typeof(Person));
serializer.Serialize(stream, jacob);
}
// Serialize another person to the same file using the "clean XML" method
// https://stackoverflow.com/questions/1772004/how-can-i-make-the-xmlserializer-only-serialize-plain-xml
using (StreamWriter stream = new StreamWriter("people.xml", true))
{
Person rebecca = new Person { Name = "Rebecca", Age = 45, Alive = true };
stream.Write(SerializeToString(rebecca));
}
// Deserialize the people
List<Person> people = new List<Person>();
using (FileStream stream = new FileStream("people.xml", FileMode.Open))
{
XmlSerializer deserializer = new XmlSerializer(typeof(Person));
while (stream.Position < stream.Length)
{
people.Add((Person)deserializer.Deserialize(stream));
}
}
// See the people
foreach (Person person in people)
Console.WriteLine($"Hello. I am {person.Name}. I am {person.Age} and it is {person.Alive} that I am alive.");
}
// Serialize To Clean XML
public static string SerializeToString<T>(T value)
{
var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
var serializer = new XmlSerializer(value.GetType());
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, settings))
{
serializer.Serialize(writer, value, emptyNamespaces);
return stream.ToString();
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public bool Alive { get; set; }
public Person()
{
Name = "";
}
}
}
Probably not the perfect answer, but could you de-serialize to a list, append your new person, and then re-serialize?
public static List<Person> ReadPeople(string file)
{
var people = new List<Person>();
if (File.Exists(file))
{
var serializer = new XmlSerializer(typeof(List<Person>));
using (var stream = new FileStream(file, FileMode.Open))
{
people = (List<Person>)serializer.Deserialize(stream);
}
}
return people;
}
public static void SavePerson(string file, Person person)
{
var people = ReadPeople(file);
people.Add(person);
var serializer = new XmlSerializer(typeof(List<Person>));
using (var stream = new FileStream(file, FileMode.Create))
{
serializer.Serialize(stream, people);
}
}
Additionally, on the "FileMode.Create", is the "FileMode.Append" option. The problem with that is it will append lists next to each other, rather than nesting the "people"
NB - I've split this into two functions to give the flexibility of being able to load the file easily
I am looking for a way to fast and simple implementation of this paradigm:
MyByteArray mb = new MyByteArray();
mb.Add<byte>(bytevalue);
mb.Add<float>(floatvalue);
mb.Add<string>(str);
mb.Add<MyClass>(object);
And then get byte[] from mb to send it as a byte packet via RPC call (to be decoded on the other side using the same technique).
I've found MemoryStream, but it looks like too overheaded for this simple operation.
Can you help me? Thank you.
What are you looking for is BinaryWritter. But it still needs a Stream to write on for pure logic reason. And the only Stream that fits in your need is MemoryStream.
Are you afraid of performance overhead ? You can create your MemoryStream from an existing byte array ;
byte [] buffer = new byte[1024];
using (var memoryStream = new MemoryStream(buffer))
{
using (var binaryWriter = new BinaryWriter(memoryStream))
{
binaryWriter.Write(1.2F); // float
binaryWriter.Write(1.9D); // double
binaryWriter.Write(1); // integer
binaryWriter.Write("str"); // string
}
}
// buffer is filled with your data now.
A tricky way to achieve this is to use a combination of builtin class in .net
class Program
{
static void Main(string[] args)
{
Program program = new Program();
var listBytes = new List<byte>();
listBytes.Add( program.CastToBytes("test"));
listBytes.Add(program.CastToBytes(5));
}
Note
for a custom object you have to define an implicit operator on how the properties or all the object should be converted
public byte[] CastToBytes<T>(T value)
{
//this will cover most of primitive types
if (typeof(T).IsValueType)
{
return BitConverter.GetBytes((dynamic)value);
}
if (typeof(T) == typeof(string))
{
return Encoding.UTF8.GetBytes((dynamic) value);
}
//for a custom object you have to define the rules
else
{
var formatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
formatter.Serialize(memoryStream, value);
return memoryStream.GetBuffer();
}
}
}
This looks like the case for Protocol Buffers, you could look at at protobuf-net.
First, let's decorate the classes.
[ProtoContract]
class User
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
}
[ProtoContract]
class Message
{
[ProtoMember(1)]
public byte Type { get; set; }
[ProtoMember(2)]
public float Value { get; set; }
[ProtoMember(3)]
public User Sender { get; set; }
}
Then we create our message.
var msg = new Message
{
Type = 1,
Value = 1.1f,
Sender = new User
{
Id = 8,
Name = "user"
}
};
And now, we can use ProtoBuf's serializer to do all our work.
// memory
using (var mem = new MemoryStream())
{
Serializer.Serialize<Message>(mem, msg);
var bytes = mem.GetBuffer();
}
// file
using (var file = File.Create("message.bin")) Serializer.Serialize<Message>(file, msg);
I have hit a small snag when I user the binaryformatter to serialize objects. The whole point for the serialization is so that the values can be passed into a hash function which requires a byte array.
The process I have is, I read the file, using newtonsoft Jsonconvert the json file into a POCO object, do some checks, update values as required and save back to same file in the Json format.
The checks include verifying that the hash value matches from the file to what is regenerated at the beginning of the process. The steps I take are, read file, convert to POCO, serialize using binary formatter, generate hash value, compare both values, if correct, update data and save both the new hash value and object as Json into the file.
However, I have hit a snag when i serialize the object using the binary formatter. If the object has properties where the values are same, the byte output from the serializer is different from when the data is read in from the file to when it is written out. As the values of the byte arrays are different, so are the hash values. Moreover, if the values for the properties are different, then the same hash values are generated and therefore no issues.
My question is why does having the same value causes the byte value to be different when the object is read and written to file.
[Serializable]
public class UserAuthorisationData
{
public string surname { get; set; }
public string forename { get; set; }
public string initials { get; set; }
public UserAuthorisationData()
{
surname = "";
forename = "";
initials = "";
}
}
E.g
var objectA = new UserAuthorisationData()
objectA.surname = "Fred";
objectA.forename = "John";
objectA.initials = "FJ";
var objectB = new UserAuthorisationData()
objectB.surname = "John";
objectB.forename = "John";
objectB.initials = "JJ";
In the example above, the value of the byte array for objectA is the same, when the hash values are generated both during when data is written out and when the file is read back in.
However, for objectB, the values differ.
Method below to convert object to byte:
protected virtual byte[] ObjectToByteArray(object objectToSerialize)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream fs = new MemoryStream();
try
{
lock (locker)
{
formatter.Serialize(fs, objectToSerialize);
Logger.Debug($"Successfully converted object to bytes for serialization.");
}
File.WriteAllBytes(#"C:\ali.kamal\User1.txt", fs.ToArray());
return fs.ToArray();
}
}
Call the method on the object
ObjectToByteArray(objectA);
ObjectToByteArray(objectB);
Update 1
Thanks Hadi. The hash code is generated using Microsoft's HMACSHA256.computeHash method.
protected override string ComputeHash(byte[] objectAsBytes, byte[] key)
{
byte[] hashValue = null;
try
{
using (HMACSHA256 hmac = new HMACSHA256(key))
{
hashValue = hmac.ComputeHash(objectAsBytes);
}
}catch(Exception ex)
{
EventLogger.LogEvent($"Could not generate SHA256 hash value, exception:{ex.Message}", EventEntryType.Error);
}
return Convert.ToBase64String(hashValue);
}
e.g.
string hashvalue = ComputeHash(ObjectToByteArray(objectA), ObjectToByteArray("abcd"))
I would suggest you to compare the bytes in different way rather than the Hash code method: Array1.SequenceEqual(Array2), Its not clear how you are comparing the Hash Code of both arrays in your question, but I will assume that you are using GetHashCode method, using Array1.GetHashCode() == Array2.GetHashCode() is not suitable for your case unless you override the HasCode function. Below is the sample using a Console application for using the SequenceEqual method:
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var a = new UserData();
var b = new UserData();
var aS = ObjectToByte(a);
var bS = ObjectToByte(b);
Console.WriteLine("A : {0}", a);
Console.WriteLine("B : {0}", b);
// result for empty objects are equal
Console.WriteLine("A == B ? => {0} \n\n", aS.SequenceEqual(bS));
a = new UserData()
{
ForeName = "A",
Initials = "CC",
SurName = "B",
};
b = new UserData()
{
ForeName = "AX",
Initials = "CC",
SurName = "B",
};
aS = ObjectToByte(a);
bS = ObjectToByte(b);
Console.WriteLine("A : {0}", a);
Console.WriteLine("B : {0}", b);
// result for same data type with same object values are equal
Console.WriteLine("A == B ? => {0} \n\n", aS.SequenceEqual(bS));
a = new UserData()
{
ForeName = "AX",
Initials = "CC",
SurName = "B",
};
b = a;
aS = ObjectToByte(a);
bS = ObjectToByte(b);
Console.WriteLine("A : {0}", a);
Console.WriteLine("B : {0}", b);
// result for different objects are not equal
Console.WriteLine("A == B ? => {0} \n\n", aS.SequenceEqual(bS));
}
static byte[] ObjectToByte(object item)
{
var formatter = new BinaryFormatter();
using (var memory = new MemoryStream())
{
formatter.Serialize(memory, item);
return memory.ToArray();
}
}
}
[Serializable]
public class UserData
{
public string SurName { get; set; }
public string ForeName { get; set; }
public string Initials { get; set; }
public override string ToString()
{
return string.Format("{{SurName: {0} , ForeName:{1}, Initials:{2}}}", SurName ?? "Empty", ForeName ?? "Empty", Initials ?? "Empty");
}
}
}
Here a working Demo
Hope this will help you
I've the following class;
public class MyClass
{
[XmlIgnore]
public string Name { get; set; }
[XmlElement("Name")]
public XmlCDataSection sName
{
get { return new XmlDocument().CreateCDataSection(Name); }
set { Name = value.Value; }
}
}
I've the following function to take a List<> and copy it's contents;
private static T CloneList<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
But in my code when I try;
List<MyClass> oMyClassList = new List<MyClass>();
MyClass oMyClass = new MyClass();
oMyClass.Name = "Hello World's";
oMyClassList.Add(oMyClass);
List<MyClass> oMyClonedClassList = new List<MyClass>(CloneList(oMyClassList));
At the point of executing the following
List<MyClass> oMyClonedClassList = new List<MyClass>(CloneList(oMyClassList));
I get the error XmlNodeConverter only supports deserializing XmlDocuments. The problem occurs because I've added XmlCDataSection into the class.
How can I get around this problem ?
I managed to overcome this problem by changing my CloneList code to the following
public static T DeepClone<T>(T obj)
{
T objResult;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = (T)bf.Deserialize(ms);
}
return objResult;
}
As provided by [Ajith][1] here How do I clone a generic list in C#?
[1]: https://stackoverflow.com/users/853645/ajith "ajith".
Also, to each of the classes that need to be cloned, I had to add [Serializable] at the top of each class as I was getting the exception 'Not marked as serializable'
I am using this.MemberwiseClone() to create shallowcopy but it is not working. Please look at the code below.
public class Customer
{
public int Id;
public string Name;
public Customer CreateShallowCopy()
{
return (Customer)this.MemberwiseClone();
}
}
class Program
{
static void Main(string[] args)
{
Customer objCustomer = new Customer() { Id = 1, Name = "James"};
Customer objCustomer2 = objCustomer;
Customer objCustomerShallowCopy = objCustomer.CreateShallowCopy();
objCustomer.Name = "Jim";
objCustomer.Id = 2;
}
}
When I run the program, It shows objCustomerShallowCopy.Name as "James" rather than "Jim".
Any Ideas??
When you shallow copy the Customer object the objCustomerShallowCopy.Name will be James and will stay like that until you change that object. Now in your case the string "James" will gave 3 references to it (objCustomer, objCustomer2 and objCustomerShallowCopy).
When you are changing objCustomer.Name to Jim you are actually creating a new string object for the objCustomer object and releasing 1 ref to the "James" string object.
We have also had problems getting that to work. We have solved it by serializing and then deserializing the object.
public static T DeepCopy<T>(T item)
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, item);
stream.Seek(0, SeekOrigin.Begin);
T result = (T)formatter.Deserialize(stream);
stream.Close();
return result;
}
Using the above code there will be no reference between the two objects.