Objecten & objectstructuren opslaan als bestand
In dit artikeltje wil ik je laten zien hoe je gemakkelijk door middel van 2 methoden objectgegevens binair in een file op kunt slaan en weer op kunt halen. Er zijn heel wat artikelen aan serialization gewijdt. Deze gaan veel dieper als dit artikeltje. In dit geval gaat het erom hoe serialiseren en deserialiseren we op een simpele, snelle en correcte manier.
Het opslaan van een object of een objectstructuur als bestand is al mogelijk sinds de eerste versie van het .NET framework. Met een duur woord heet dit serialization (opslaan) en deserialization (ophalen/laden). Het opslaan als bestand is 1 vorm maar je kunt bijvoorbeeld de gegevens ook in een MemoryStream of bijv. de Application State van een ASP.NET applicatie opslaan. Verder is de vorm waarmee de gegevens worden opgeslagen te bepalen. Twee van de mogelijkheden zijn binair of in XML formaat. Dit laatste heeft wat nadelen aangezien niet alle gegevens van een object kunnen worden opgeslagen.
Voorwaarden
Een voorwaarde om een object op te kunnen slaan is dat de betreffende class voorzien is van het attribuut [Serializable]. Dus…
[Serializable]
public class AClass
{
...
}
Op het moment dat een instantie (object) van deze class wordt geserializeerd. Dan zullen ook alle gerelateerde velden (properties) worden geserializeerd. Stel…
[Serializable]
public class Employee
{
public Address HomeAddress;
...
}
public class Address
{
public string Street;
public string HouseNr;
public string Zip;
}
We hebben dus een werknemer (employee) met daaraan gekoppeld een thuis adres (HomeAddress). Op het moment dat we Employee serializeren zal ook het HomeAddress worden geserializeerd. In dit voorbeeld zal dit echter een exception opleveren aangezien de Address class niet het serializable attribuut bevat.
[Serializable]
public class Address
{
...
Bovenstaande code is dus wel OK. Het is overigens ook mogelijk velden expliciet niet mee te laten serializeren. Dit kan worden geregeld door het NonSerialized attribuut te zetten op het specifieke veld.
[Serializable]
public class Employee
{
[NonSerialized]
public Address HomeAddress;
...
}
In bovenstaande geval hoeft de Address class dus ook het attribuut serializable niet te bevatten.
Verder is het van belang in de gaten te houden dat als een object of objectstructuur is geserializeerd en een class in de objectstructuur wordt daarna aangepast, de geserializeerde gegevens niet meer kunnen worden opgehaald. De binaire gegevens kunnen dan niet meer gemapped worden op het aangepaste type.
Wanneer?
Wanneer is serializatie handig om te gebruiken? Stel je hebt een WinForms applicatie geschreven waarbij je ingevoerde gegevens in een soort projectbestand op wilt slaan. Hiervoor kun je prima binaire serializatie gebruiken.
Ook kan het handig zijn bij webapplicaties. Zo is het bijvoorbeeld mogelijk via SQL Management Objects (SMO), alle mogelijke gegevens van een SQL database (tabellen, velden, primary keys, foreign keys, indexen etc…) op te halen dit proces vergt echter flink wat tijd. Je zou in dat geval een objectstructuur kunnen creeeren bij het ophalen van deze gegevens die overeenkomt met de databaseindeling. Deze structuur vervolgens serializeren naar een bestand en deze vervolgens bij elke request inlezen om gegevens over de database te verkrijgen. Dit kan natuurlijk alleen als de database qua structuur niet wijzigt.
Verder heeft binair opslaan (en dan niet perse als bestand) in sommige gevallen voordelen boven het vasthouden van een referentie naar een object of objectstructuur. Je kunt hiermee objecten of objectstructuren dupliceren.
De code
Ter zake. Hieronder vindt je de code waarmee een object of objectstructuur kan worden opgeslagen op schijf en weer opgehaald. Het mooie is dat bij het ophalen niet noodzakelijk is te weten welk objecttype er terug wordt gegeven.
LET OP: Via de printversie (links boven en onder) kun je de code kopieren.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
using System;<br /> using System.IO;<br /> using System.Runtime.Serialization;<br /> using System.Runtime.Serialization.Formatters.Binary;<br /> <br /> namespace snoei.net.utilities.Serialization<br /> {<br /> /// <summary><br /> /// Helps saving and loading objects to and from a file.<br /> /// </summary><br /> public abstract class Serializer<br /> {<br /> private Serializer(){}<br /> <br /> /// <summary><br /> /// Saves an object (and it's referenced objects) to a file.<br /> /// </summary><br /> /// <param name="Object">The object to store.</param><br /> /// <param name="Filename">The filename to store the object in.</param><br /> public static void SerializeObject(object Object, string Filename)<br /> {<br /> if (Object != null)<br /> {<br /> Stream ObjectStream = new FileStream(Filename, System.IO.FileMode.Create);<br /> BinaryFormatter ObjectBinaryFormatter = new BinaryFormatter();<br /> ObjectBinaryFormatter.Serialize(ObjectStream, Object);<br /> ObjectStream.Close();<br /> }<br /> }<br /> <br /> /// <summary><br /> /// Loads an object (and it's referenced objects) from a file.<br /> /// </summary><br /> /// <param name="Filename">The filename to load the object from.</param><br /> /// <returns>The object that was in the file.</returns><br /> public static object DeSerializeObject(string Filename)<br /> {<br /> if (File.Exists(Filename))<br /> {<br /> object Object;<br /> Stream ObjectStream = new FileStream(Filename, System.IO.FileMode.Open);<br /> IFormatter ObjectBinaryFormatter = new BinaryFormatter();<br /> Object = ObjectBinaryFormatter.Deserialize(ObjectStream);<br /> ObjectStream.Close();<br /> return Object; <br /> }<br /> else<br /> {<br /> throw new Exception("File not found");<br /> } <br /> }<br /> }<br /> }<br /> |
De code kan dus alsvolgt worden gebruikt:
//Opslaan
Employee emp = new Employee()
emp.Name = "Ton Snoei";
...
SerializeObject(emp,@"c:test.bin");
//ophalen
Employee x = (Employee) DeSerializeObject(@"c:test.bin");