Singleton, Serialisierung (Json, Xml) und Databinding ohne Dataset

    • VB.NET
    • .NET (FX) 4.0

      Singleton, Serialisierung (Json, Xml) und Databinding ohne Dataset

      Anbei ein Sample mit formübergreifendem Databinding.

      Singleton
      Formübergreifendes Databinding erfordert ein global zugreifbares Objekt, nur eines wohlgemerkt. Diese Singularität kann man zB mittels eines Singleton sicherstellen - hier die wohl aller-einfachste Ausführung:

      VB.NET-Quellcode

      1. Public Class Repository
      2. Public Shared ReadOnly Instance As New Repository
      3. Private Sub New()
      4. End Sub
      5. '...
      der Private Konstruktor verhindert, dass von aussen ein Repository erstellt werden kann, und die einzige Instanz dieser Klasse ist nun Public Shared ReadOnly - steht da ja.

      Serialisierung
      Es gibt verschiedene (konkurrierende) Produkte - die alle dasselbe machen: Sie schreiben Objekte als ByteFolge in einen Stream, entweder um ihn zu verschicken, oft im Falle FileStream wird ja an eine Datei "geschickt"- anders gesagt: Das Objekt wird abgespeichert.
      Jo, ich hab mal mit ein paar Varianten herum-experimentiert, und dann für die, die ohne externe Dll funzen, eine SerialisationHelper-Klasse geschrieben, da kann man auch mit dem unterschiedliche Verhalten bischen experimentieren.
      zweimal Json, einmal Xml

      VB.NET-Quellcode

      1. Imports System.Text
      2. Imports System.IO
      3. Imports System.Runtime.Serialization.Json
      4. Imports System.Xml.Serialization
      5. Public Class SerialisationHelper
      6. Public Shared Function FromJContractString(Of T)(input As String) As T
      7. Dim ser = New DataContractJsonSerializer(GetType(T))
      8. Using ms As New MemoryStream(Encoding.UTF8.GetBytes(input))
      9. Return DirectCast(ser.ReadObject(ms), T)
      10. End Using
      11. End Function
      12. Public Shared Function ToJContractString(Of T)(input As Object) As String
      13. Using ms As New MemoryStream()
      14. Dim ser As New DataContractJsonSerializer(GetType(T))
      15. ser.WriteObject(ms, input)
      16. ms.Position = 0
      17. Using sr As New StreamReader(ms)
      18. Return sr.ReadToEnd()
      19. End Using
      20. End Using
      21. End Function
      22. Public Shared Function FromJScriptString(Of T)(ByVal input As String) As T
      23. Dim ser As New System.Web.Script.Serialization.JavaScriptSerializer
      24. Return ser.Deserialize(Of T)(input)
      25. End Function
      26. Public Shared Function ToJScriptString(Of T)(ByVal input As Object) As String
      27. Dim ser As New System.Web.Script.Serialization.JavaScriptSerializer
      28. Return ser.Serialize(input)
      29. End Function
      30. Public Shared Function FromXmlString(Of T)(ByVal input As String) As T
      31. Dim ser = New XmlSerializer(GetType(T))
      32. Using ms As New MemoryStream(Encoding.UTF8.GetBytes(input))
      33. Return DirectCast(ser.Deserialize(ms), T)
      34. End Using
      35. End Function
      36. Public Shared Function ToXmlString(Of T)(ByVal input As Object) As String
      37. Using ms As New MemoryStream()
      38. Dim ser As New XmlSerializer(GetType(T))
      39. ser.Serialize(ms, input)
      40. ms.Position = 0
      41. Using sr As New StreamReader(ms)
      42. Return sr.ReadToEnd()
      43. End Using
      44. End Using
      45. End Function
      46. End Class
      Die Unterschiede betreffen gewisse Attribute, die gesetzt werden können oder müssen - will ich hier garnet drauf eingehen.
      Der Vollständigkeit halber erwähne ich noch die Serialisierung mittm BinaryFormatter, dessen Ausgabe ist nicht menschen-lesbar, aber dafür kann er manche Klassen noch serialisieren, die menschenlesbar nicht serialisierbar sind (zB. Fonts).

      Serialisierung-Persistenz
      Obiger Code serialisiert nicht in Bytes, sondern wandelt diese gleich wieder nach String um, damit Json/Xml-Code in der Textbox begutachtet werden kann. Das hat nur Demonstrations-Wert - normalerweise serialisiert man zu Streams, meist FileStreams - Abspeichern halt.
      Deshalb hab ich ein Extension-Modul gemacht, für Stream und FileInfo, die entweder mit einem BinaryFormatter oder mit XmlSerializer persistierbar sind (Json wäre natürlich ebensogut möglich):
      Serialisierungs-Extensions

      VB.NET-Quellcode

      1. Public Module SystemIoX
      2. <Extension()> _
      3. Public Sub SerializeXml(fi As FileInfo, obj As Object)
      4. ' Achtung! FileInfo.OpenWrite ist ungeeignet, denn es erneuert nicht unbedingt die ganze Datei
      5. Using strm = fi.Open(FileMode.Create)
      6. strm.SerializeXml(obj)
      7. End Using
      8. End Sub
      9. <Extension()> _
      10. Public Sub SerializeXml(strm As Stream, obj As Object)
      11. Call (New XmlSerializer(obj.GetType)).Serialize(strm, obj)
      12. End Sub
      13. <Extension()> _
      14. Public Function DeserializeXml(Of T)(fi As FileInfo) As T
      15. Using strm = fi.OpenRead
      16. Return strm.DeserializeXml(Of T)()
      17. End Using
      18. End Function
      19. <Extension()> _
      20. Public Function DeserializeXml(Of T)(strm As Stream) As T
      21. Return DirectCast((New XmlSerializer(GetType(T))).Deserialize(strm), T)
      22. End Function
      23. <Extension()> _
      24. Public Sub Serialize(fi As FileInfo, obj As Object)
      25. ' Achtung! FileInfo.OpenWrite ist ungeeignet, denn es erneuert nicht unbedingt die ganze Datei
      26. Using strm = fi.Open(FileMode.Create)
      27. strm.Serialize(obj)
      28. End Using
      29. End Sub
      30. <Extension()> _
      31. Public Sub Serialize(strm As Stream, obj As Object)
      32. Call (New Runtime.Serialization.Formatters.Binary.BinaryFormatter).Serialize(strm, obj)
      33. End Sub
      34. <Extension()> _
      35. Public Function Deserialize(Of T)(fi As FileInfo) As T
      36. Using strm = fi.OpenRead
      37. Return strm.Deserialize(Of T)()
      38. End Using
      39. End Function
      40. <Extension()> _
      41. Public Function Deserialize(Of T)(strm As Stream) As T
      42. Return DirectCast((New Runtime.Serialization.Formatters.Binary.BinaryFormatter).Deserialize(strm), T)
      43. End Function
      44. End Module
      Aufruf-Beispiel (im Repository):

      VB.NET-Quellcode

      1. Private _DataFile As New FileInfo("..\..\Autos.Xml")
      2. Public Sub Save()
      3. _DataFile.SerializeXml(_Autos)
      4. End Sub
      5. Public Sub Load()
      6. Autos = _DataFile.DeserializeXml(Of BindingList(Of Auto))()
      7. End Sub
      Diese Extensions sind also der wiederverwendbarste Code dieses Tutorials: sie machen Speichern/Laden zum Einzeiler.

      Databinding
      Meist denkt man bei Databinding an typisierte Datasets oder Entity-Framework, aber das ist garnet zwingend.
      Grundsätzlich kann an jede Property gebunden werden (wohlgemerkt: nur an Properties - nicht an Felder).
      INotifyPropertyChanged
      Falls die Properties den INotifyPropertyChanged-Benachrichtigungsmechanismus unterstützen kann Binding auch bidirektional erfolgen, also nicht nur das Control ändert die Property, sondern wenn man codeseitig die Property ändert, wird (durch feuern des PropertyChanged-Events) automatisch auch das Control geupdatet.
      Da dieses Feature in großem Umfang genutzt wird, empfiehlt es sich, sich eine Basisklasse zu machen, die INotifypropertyChanged implementiert, und dann können Datenklassen davon erben und das Feature quasi gemeinsam nutzen.
      Minimal-Version solche einer Klasse wäre etwa so:

      VB.NET-Quellcode

      1. Public Class DataBase : Implements INotifyPropertyChanged
      2. Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
      3. Public Sub RaisePropertyChanged(propName As String)
      4. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
      5. End Sub
      6. End Class
      aber meist gestaltet man das aufwändiger, um eine komfortablere Benutzung zu erreichen.
      Jedenfalls spricht nichts dagegen, INotifypropertyChanged-Klassen zu serialisieren, also kein Grund wegen des einen (Persistenz) auf das andere (Databinding) zu verzichten.

      Designer-Unterstützung
      Und auch auf Designer-Unterstützung braucht nicht verzichtet werden, man muss dem Projekt nur die Klassen, deren Bindings man im FormDesigner gestalten möchte als Datasource bekannt machen.
      Dazu klickst man einfach im Datenfenster auf "Datenquelle hinzufügen", und fügt eine Datenquelle hinzu, und zwar eine Objekt-Datenquelle:
      ... ...
      (Wie man sieht, gehts im Sample um Autos)
      Zu beachten auch der voreingestellte Haken bei "Hide system assemblies". Weil wenn man den uncheckt, dann kann man ebensogut auch alle Framework-Klassen Designer-Databinding verfügbar machen.
      Hier ein Video, wo das auf FileInfo angewandt wird: Keine Strings in die File-Listbox!

      Jo, also hat man seine eigenen serialisierbaren Klassen Designer-Databinding verfügbar gemacht, so steht einem vom Prinzip her alles offen, was in vier Views-Videos an Möglichkeiten gezeigt ist. (Ich empfehle jedem, der mit den Gestaltungs-Möglichkeiten bezüglich Databinding nicht sehr vertraut ist, die beiden Links auch aufzusuchen, und sich die kurzen Videos mal zu Gemüte zu führen).

      Aber halt nur im Prinzip, denn eigene Datenmodell-Klassen unterstützen nur hierarchische Modellierung, keine relationale.
      Daher entfallen der JoiningView und folglich auch der m:n-View, aber immerhin Parent-Child und DetailView machen überhaupt kein Problem:

      (Sorry, das Sample ist ursprünglich zum Serialisierung testen gewesen, die DataBinderei ist nachträglich drangepatcht, daher siehts bischen wirr aus :saint: ).
      Aber erkennbar oben ein gebundenes PropertyGrid, unten links die (Parent-)Liste der Autos (es sind nur 2), Mitte der DetailView des Auto-Nummernschildes, und rechts der ChildView (halt die Räder - ja, ist nicht ganz so sinnvoll ;) )
      Im DetailView auch die Buttons um den Unterschied aufzuzeigen, wenn NotifyPropertyChanged gefeuert wird, und wenn nicht.

      BindingList(Of T)
      Wie eingangs erwähnt ist die Datenhaltung aus dem Form herausgenommen und in ein Singleton-Repository verlagert, und so kann man mehrere solcher Forms anzeigen, und alle sind an dieselben Daten gebunden.
      Dabei ist zu beachten, dass nicht List(Of Auto) genommen wird, sondern BindingList(Of Auto).
      Weil BindingList(Of T) feuert das IListChanged-Event, und benachrichtigt so das Gui von Zufügungen und Löschungen.
      Bei Verwendung der einfacherern List(Of T) käme es beim übergreifenden Databinding zu Inkonsistenzen, wenn im einen Datagridview ein Auto gelöscht wird, aber das andere Datagridview weiß noch nichts davon.

      Zusammenfassung
      Serialisierung kann man als stark abgespeckte Form von Persistenz ansehen, verglichen mit den auf relationale Datenverarbeitung ausgelegten Technologien EntityFramework / typisiertes Dataset.
      Serialisierung kommt dabei ohne umfangreichen generierten Code aus, und braucht auch keine Datenbank, verzichtet aber neben der Relationalität auch auf weitere Funktionalität, welche bei klassischer Datenverarbeitungs von Haus aus bereitsteht: Filtern, Sortieren, Canceln von Edit-Vorgängen, Validieren. (also der Verzicht ist nicht generell, aber man muss jeweils besondere Vorkehrungen treffen, mehr oder weniger aufwändig)

      Hingegen ein entscheidender Vorzug kann sein die Kompatiblität zu anderen Plattformen.

      So oder so: auf Databinding als Architektur-Paradigma braucht nicht verzichtet werden - weder zur Laufzeit noch zur Designzeit :D
      Dateien
      • JsonTests.zip

        (22,94 kB, 275 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()