Hier noch mal der gesamte Code von BusinessObject, nachdem ich die Änderungen vorgenommen habe, sonst muss man sich das mühsam zusammenklauben:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
//*****************************************************************************
abstract class BusinessObject
{
//--------------------------------------------------------------------------
// In welcher Transaktion das Objekt enthalten ist
private Guid _guidTransaction = Guid.Empty;
// Bei welchem Objekt die Transaktion begonnen wurde
private BusinessObject _boRoot = null;
// Alle Objekte in der Transaktion; nur beim WurzelObjekt gesetzt
private List<BusinessObject> _listScope = null;
// Der gespeicherte Zustand des Objekts
private Dictionary<FieldInfo, Object> _dictFields = null;
//==========================================================================
public void StartTransaction()
{
if (_listScope != null)
{
throw new Exception("Keine geschachtelten Transaktionen erlaubt.");
}
_listScope = new List<BusinessObject>();
try
{
_StartTransaction(0, Guid.NewGuid(), this);
}
catch (Exception)
{
foreach (BusinessObject boCurr in _listScope)
{
boCurr._guidTransaction = Guid.Empty;
boCurr._boRoot = null;
boCurr._dictFields = null;
}
_listScope = null;
throw;
}
}
//==========================================================================
private void _StartTransaction(int iLevel,
Guid guidTransaction,
BusinessObject boRoot)
{
if (_guidTransaction != Guid.Empty)
{
if (_guidTransaction == guidTransaction)
{
return; // zyklischen Struktur, Objekt schon besucht
}
throw new Exception("Objekt ist schon in einer anderen "
+ "Transaktion enthalten.");
}
boRoot._listScope.Add(this);
_guidTransaction = guidTransaction; // als besucht kennzeichnen
_boRoot = boRoot;
_dictFields = new Dictionary<FieldInfo, Object>();
FieldInfo[] afi;
List<FieldInfo> listfiAll = new List<FieldInfo>();
BusinessObject boChild;
Type t;
// Die Schleife ist notwending, weil man sonst nicht an die privaten
// Felder der Oberklassen kommt.
for (t = GetType(); t != typeof(BusinessObject); t = t.BaseType)
{
afi = t.GetFields(BindingFlags.GetField
| BindingFlags.Instance
| BindingFlags.NonPublic
| BindingFlags.DeclaredOnly
| BindingFlags.Public); // nur zur Sicherheit
foreach (FieldInfo fi in afi)
{
#if SW_VERBOSE
Console.WriteLine (new String (' ', 3 * iLevel)
+ fi.DeclaringType + "." + fi.Name);
#endif
if (fi.FieldType.GetInterface("IEnumerable") != null)//mit Aulistungen soll speziell verfahren werden
{
if (fi.FieldType.IsGenericType)//wenn Auflistung generisch, versuchen, sie per Kopier-Konstruktor zu kopieren.
{
try
{
_dictFields[fi] = Activator.CreateInstance(fi.FieldType, fi.GetValue(this));
}
catch (Exception)
{
if (fi.FieldType.GetInterface("ICloneable") != null)//wenn generische Auflisung Cloneable, flach kopieren
{
ICloneable L = (ICloneable)fi.GetValue(this);
_dictFields[fi] = L.Clone();
}
else//ansonsten normal kopieren
{
_dictFields[fi] = fi.GetValue(this);
}
}
}
else//wenn Auflistung nicht generisch, nachsehen, ob sie Cloneable ist
{
if (fi.FieldType.GetInterface("ICloneable") != null)//wenn nicht generische Auflisung Cloneable, flach kopieren
{
ICloneable L = (ICloneable)fi.GetValue(this);
_dictFields[fi] = L.Clone();
}
else//ansonsten normal kopieren
{
_dictFields[fi] = fi.GetValue(this);
}
}
System.Collections.IEnumerable List=(System.Collections.IEnumerable)fi.GetValue(this);
foreach (object O in List)//Alle BusinessObjects in der Auflistung in die Transaction aufnehmen
{
if (O is BusinessObject)
{
BusinessObject BO = (BusinessObject)O;
BO._StartTransaction(iLevel+1,guidTransaction,boRoot);
}
}
}
else
{
_dictFields[fi] = fi.GetValue(this);
}
}
listfiAll.AddRange(afi);
}
// Rekursiver Abstieg
foreach (FieldInfo fi in listfiAll)
{
if (fi.FieldType.IsSubclassOf(typeof(BusinessObject)))
{
#if SW_VERBOSE
Console.WriteLine ("=> " + new String (' ', 3 * iLevel)
+ fi.DeclaringType + "." + fi.Name);
#endif
boChild = (BusinessObject)fi.GetValue(this);
if (boChild != null)
{
boChild._StartTransaction(iLevel + 1, guidTransaction, boRoot);
}
}
}
}
//==========================================================================
public void CommitTransaction()
{
if (_guidTransaction == Guid.Empty)
{
throw new Exception("Objekt ist in keiner Transaktion enthalten.");
}
if (_listScope == null)
{
throw new Exception("Die Transaktion wurde nicht bei diesem "
+ "Objekt begonnen");
}
foreach (BusinessObject boCurr in _listScope)
{
boCurr._guidTransaction = Guid.Empty;
boCurr._boRoot = null;
boCurr._dictFields = null;
}
_listScope = null;
}
//==========================================================================
public void RollbackTransaction()
{
if (_guidTransaction == Guid.Empty)
{
throw new Exception("Objekt ist in keiner Transaktion enthalten.");
}
if (_listScope == null)
{
throw new Exception("Die Transaktion wurde nicht bei diesem "
+ "Objekt begonnen");
}
foreach (BusinessObject boCurr in _listScope)
{
#if SW_VERBOSE
Console.WriteLine (boCurr.GetType ());
#endif
foreach (FieldInfo fi in boCurr._dictFields.Keys)
{
#if SW_VERBOSE
Console.WriteLine ("..." + fi.DeclaringType + "." + fi.Name);
#endif
fi.SetValue(boCurr, boCurr._dictFields[fi]);
}
boCurr._guidTransaction = Guid.Empty;
boCurr._boRoot = null;
boCurr._dictFields = null;
}
_listScope = null;
}
//==========================================================================
~BusinessObject()
{
if (_listScope != null)
{
RollbackTransaction();
}
else if (_boRoot != null)
{
_boRoot.RollbackTransaction();
}
}
}
OK, mein Dialogfeld ist jetzt fertig:
Mit der übergebenen Auflistung belege ich die Eigenschaft eines Erben der BusinessObject-Klasse, so dass die Aktionen Hinzufügen, Entfernen und Verschieben eines Elementes rückgängig gemacht werden können.
Aber beim Rückgängigmachen von Änderungen an den Eigenschaften von Elementen, konnte mir BusinessObject nicht helfen, obwohl ich drei Tage alle möglichen Varianten ausprobiert habe.
Ich habe mich dann entschlossen, das Problem ungelöst zu lassen(hätte ich schon nach einem Tag machen sollen) : Die Klasse und einige ihrer Member sind abstract, so dass der Benutzer der Klasse sich selbst darum kümmern muss, dass der Status eines Elementes gespeichert wird und wie das RollBack vonstatten geht. Das hat außerdem den Vorteil, dass nicht pauschal alle Eigenschaften der Elemente "konserviert" werden müssen, sondern nur die, die auch wirklich bearbeitet werden.
Ich danke für die Hilfe!
Allerdings wird foreach (BusinessObject BO in List) { ... knallen, sobald er das erste Objekt findet, dass kein BusinessObject ist.
OK.
Werde das neue BusinessObject jetzt mal an ein paar Sachen testen und sehen obs auch wirklich geht. Dass es mit dem Dialogfeld weiter Probleme geben wird, ist jetzt schon klar, denn die Auflistung, für die ich das Rollback gegebenenfalls anwenden will, ist vom Typ ListBox.ObjectCollection(wieso gibt es solche Extrawürste?). Der implementiert kein ICloneable und ist auch nicht generisch. Ich werd mal versuchen, ob ich das durch ein bischen Erben hinkriege...
Hier nochmal der komplette if-Block:
if (fi.FieldType.GetInterface("IEnumerable") != null)
{
if (fi.FieldType.IsGenericType)
{
try
{
_dictFields[fi] = Activator.CreateInstance(fi.FieldType, fi.GetValue(this));
}
catch (Exception)
{
_dictFields[fi] = fi.GetValue(this);
}
}
else
{
if (fi.FieldType.GetInterface("ICloneable") != null)
{
ICloneable L = (ICloneable)fi.GetValue(this);//Von diesem "(ICloneable)" vermute ich, dass es was mit Typkonversion zu tun hat... könnte aber auch sein, dass ich hier nen riesen Bock geschossen hab...
_dictFields[fi] = L.Clone();
}
else
{
_dictFields[fi] = fi.GetValue(this);
}
}
System.Collections.IEnumerable List=(System.Collections.IEnumerable)fi.GetValue(this);
foreach (BusinessObject BO in List)
{
BO.StartTransaction();
}
}
else
{
_dictFields[fi] = fi.GetValue(this);
}
Mit dem code oben gibts ein Problem: Was mach ich mit nicht generischen Auflistungen?
Businessobject müsste ja erst mal zwischen generischen und nicht generischen unterscheiden, der if-Block sieht dann so aus:
if (fi.FieldType.GetInterface("IEnumerable") != null)
{
if (fi.FieldType.IsGenericType)
{
_dictFields[fi]=Activator.CreateInstance(fi.FieldType,fi.GetValue(this));
}
else
{
//Aber was mach ich hier???
}
}
else
{
_dictFields[fi] = fi.GetValue(this);
}
Wie aber erstelle ich eine flache Kopie einer nicht generischen Auflistung?
Ich glaube das müsstes jetzt sein, aber bei der if-Anweisung bin ich mir noch nicht ganz sicher:
if (fi.FieldType.GetInterface("IEnumerable") != null)
{
_dictFields[fi]=Activator.CreateInstance(fi.FieldType,fi.GetValue(this));
}
else
{
_dictFields[fi] = fi.GetValue(this);
}
Hab das ganze auch schon getestet (funktioniert!!!), aber um es in mein Dialogfeld einzubauen hab ich im Moment keine Zeit, werds heut nachmittag mal probieren.
Ich hoffe, ich mache das, was du meinst:
// Die Schleife ist notwending, weil man sonst nicht an die privaten
// Felder der Oberklassen kommt.
for (t = GetType(); t != typeof(BusinessObject); t = t.BaseType)
{
afi = t.GetFields(BindingFlags.GetField
| BindingFlags.Instance
| BindingFlags.NonPublic
| BindingFlags.DeclaredOnly
| BindingFlags.Public); // nur zur Sicherheit
foreach (FieldInfo fi in afi)
{
#if SW_VERBOSE
Console.WriteLine (new String (' ', 3 * iLevel)
+ fi.DeclaringType + "." + fi.Name);
#endif
if (fi.FieldType.IsEnum)//Dieser if-Block ist neu
{
_dictFields[fi]=new List<char>(fi.GetValue(this));//Zu dieser Zeile hab ich zwei fragen (siehe unten)
}
else
{ _dictFields[fi] = fi.GetValue(this);//das hier stand vorher statt dem if-Block
}
}
listfiAll.AddRange(afi);
}
Zwei Fragen:
Ich versuche mittlerweile seit geraumer Zeit, das ganze mit BusinessObjects zu machen, sieht im Moment auch noch nicht mal schlecht aus, aber es gibt ein Problem:
Man soll der Auflistung (über das Dialogfeld) auch Elemente hinzufügen können (auch entfernen und innerhalb der Auflistung verschieben). Das müsste ich dann auch wieder rückgängig machen können, deshalb meine Frage:
Kann ich von BusinessObject erben und es so um eine Eigenschaft vom Typ List<T> erweitern? (Klar kann ich, aber funktioniert das Rollback dann noch?). Dann könnte ich beim Aufruf von ShowDialog nämlich diese Eigenschaft mit der übergebenen Auflistung belegen. Dann StartTransaction und ein bischen an der Auflistung rumgefummelt, wenns dem user dann doch nicht gefällt sage ich RollBackTransaction und die Auflistung ist wieder in ihrem Urzustand.
Ich habs auch schon probiert aber irgendwie hats nicht hingehauen und jetzt würd ich gern wissen, ob ich da einen Fehler gemacht hab oder ob Businessobject das sowieso nicht kann
Ein allgemeines Clone ist schon deshalb schlecht, weil dann auch Singletons geklont werden könnten... was das Singleton ad absurdum führen würde.
KÖNNTE - Niemand muss auch nur irgendwas klonen, aber man hätte die MÖGLICHKEIT