| 

.NET C# Java Javascript Exception

2
Hallo

Vor dem speichern eines Objektes in die DB müsste ermittelt werden ob es Änderungen am Objekt gab. Zweck ist eine Historysierung der Änderungen.

Dazu könnte man ja das Objekt vor dem speichern nochmals laden und Property für Property vergleichen. Aber das nochmals laden gefällt uns nicht ganz da die Logik auch auf seiten des Clients nützlich sein könnte.

Ein spezieller PropertyTyp ist auch nicht so das wahre.

public TrackedProperty<Stammdaten> Anrede
{...

Ist irgend wie blöd und unhandlich. Zudem birgt das auch Probleme und viel Aufwand im Zusammenhang mit WCF.

Könnte das noch anders gelöst werden? Ideen?

Gruss GENiALi
News:
14.07.2010
GENiALi 2,5k 1 2 8
GENiALi 2,5k 1 2 8
Nachfrage: Wie sieht es bei multi-client Einsatz aus? Soll vor Speichern gegen die DB geprüft werden, ob dort noch die ursprünglich geladenen Daten stehen, sprich ob schon ein anderer Client 'drübergespeichert' hat? In dem Fall müsstest Du eh eine Db Abfrage (mehr) einbauen.
DaSpors 14.07.2010
5 Antworten
2
Hallo Genaili,

dazu fallen mir spontan folgende Möglichkeiten ein:

  • DataSet verwenden (ich denke das kann das von Haus aus, wie es mit WCF ausschaut weiß ich nicht)
  • INotifyPropertyChanged implementieren und eine zusätzliche Eigenschaft IsDirty anbieten (ev. über eine neue (Aggregat-) Klasse)
  • eine Methode GetCheckSum implementieren -> durch den Verlgeich der aktuellen Prüfsumme mit der Prüfsumme zu Beginn kann auf Änderungen geschlossen werden, diese muss natürlich auch in einer Variablen/Eigenschaft gespeichert werden
    Die Implementierung von GetChecksum kann ähnlich zu den Regeln von GetHashCode erfolgen. GetHashCode unterliegt jedoch zustätzlichen Einschränkungen für die Verwendung mit Hash-Tabelle und sollte daher für diese Aufgabe nicht missbraucht werden.


Edit: Als weiterer Punkt wäre Unit of Work anzuführen.

Punkt 2 und 3 sind ziemlich ident.

Noch zur Erklärung was ich unter einer Aggregat-Klasse verstehe.
public class FooExt
{
public Foo Foo {get; set;}
public bool IsDirty {get; set;}
// public int CheckSumAtBegin {get; set;}
}


Bei Objekten mit überschaubaren Eigenschaften würde ich die Variante 2 nehmen. Bei aufwändigen Objekten (zB eine Liste mit vilen Objekten oder eine Hierarchie) wäre dies wohl zu aufwändig. Hier würde ich die Prüfsummen-Variante wählen - die Prüfsumme könnte hier zB als MD5-Hash ermittelt werden.


mfG Gü
14.07.2010
gfoidl 9,4k 3 5
gfoidl 9,4k 3 5
OK. Das sind Gedanken die uns auch schon durch den Kopf sind. Würde aber für uns heissen: VIEL umbauen.
GENiALi 14.07.2010
Ist es möglich eine Zwischenschicht einzubauen?
Dann zB einen Cache erstellen die Originalobjekte enthält und gegen die geprüft werden kann...
gfoidl 14.07.2010
Das währe auch meine Lösung. Alle Welt arbeitet mit dem Dirty-Flag, Warum nicht ihr? ;-)
klaus_b 14.07.2010
Hab noch einen Punkt hinzugefügt.
gfoidl 16.07.2010
Am meisten Ideen. Jetzt müssen wir nur noch entscheiden.
GENiALi 19.07.2010
1
Eventuell habe ich die Bedingung 'Logik auch auf seiten des Clients nützlich' nicht ganz verstanden, aber wenn sich die 'Historysierung' über einen Trigger erledigen läßt, geht ganz einfach

update Stammdaten set Anrede='Frau' where ...

Einige Datenbanken - wie z.B. mysql - schreiben die neuen Daten nur, wenn sie sich auch geändert haben, und feuern auch nur dann den Trigger.

Wenn zusätzlich noch ein Timestamp gesetzt werden muss, geht es dann z.B. so

update Stammdaten set Anrede='Frau', updated=UNIX_TIMESTAMP()
where (...) and !(Anrede<=>'Frau')
14.07.2010
BeachBlocker 617 3
OK. Bei der Idee kommt mir in den Sinn das ich noch eine Bedinung vergessen habe.

Wer hat die Änderung vorgenommen.
GENiALi 14.07.2010
Geht doch immer noch über einen Trigger:

update Stammdaten set Anrede='Frau', updated=UNIX_TIMESTAMP(),
last_changed_by='GENiALi'
where (...) and !(Anrede<=>'Frau')
BeachBlocker 14.07.2010
OK. Da mir die Trigger nicht ganz so geläufig sind, wie bringe ich den UserName vom C# Code in den Trigger?
UserName im Progi ist nicht gleich UserName auf der DB.
GENiALi 14.07.2010
OK. Habe soeben vom PL gesagt bekommen: Trigger? Nein
Sorry
Brauche wohl eine Lösung für C#.
GENiALi 14.07.2010
1
Ich würde es in MSSQL auch mit einem Tigger lösen:

CREATE TRIGGER tblMyTable_HistoryTrigger
ON mydb.dbo.tblMyTable
BEFORE INSERT, UPDATE
AS
insert into mydb.dbo.tblMyTable_History
select getdate(),*
from deleted D
join inserted I on I.a_ID = D.a_ID
where BINARY_CHECKSUM(I.a_ID,I.a_col1,I.a_col2,...)<>BINARY_CHECKSUM(D.a_ID,D.a_col1,D.a_col2,...)
GO

Die History-Tabelle hat dabei die selben Spalten wie die Originaltabelle nur das sie um eine HistoryID und eine HistoryDate Spalte erweitert wurden.

Andere Idee .. wenn ihr Objekte verwendet dann macht doch ein Checksum-Feld.
Und zwar beim Erzeugen des Objektes erzeugst du mittels "GetHashCode()" eine "CheckSum" für dieses Object. Dieses speicherst du als ReadOnly-Eigenschaft an dieses bevor du es mittels WCF an den Client überträgst. Nun kann der Client oder der Server erneut "GetHashCode()" aufrufen und die beiden Checksummen vergleichen. Wichtig dabei ist nur das du die Funktion "GetHashCode()" überlädst damit Sie alle Felder, außer dem ReadOnly-Feld das die CheckSumme zum vergleicheen enthällt, in die Berechnung einbezieht.

public class MyObject
{
//Eigendlich hier Properties verwenden .. aber ich will mir bissel Schreibarbeit sparen
public int someVar1;
public string someVar2;
public double someVar3;
public Point someVar4;

private int _initialHashCode;
public initialHashCode(){ get { return this.initialHashCode } }

//Sollte aufgerufen werden wenn du alle Daten in dein Object geschrieben hast
public void CalcHash()
{
if(this._initialHashCode== 0)
this._initialHashCode = this.GetHashCode();
}
public override int GetHashCode()
{
var result = 0;
result = result ^ this.someVar1.GetHashCode()
result = result ^ this.someVar2.GetHashCode()
result = result ^ this.someVar3.GetHashCode()
result = result ^ this.someVar4.GetHashCode()
return result;
}
}


Um eine Änderung zu erkennen kannst du jetzt so vorgehen:

...
myObject.sameVar1 = 10;
if(myObject.GetHashCode() == myObject.initialHashCode())
//Omg .. es hat sich was geändert!!


Edit:

Mir fällt da ein Problem ein, jedoch auch gleich eine Lösung.
Ich glaube mich daran zu erinnern das GetHashCode auf unterschiedlichen Rechner unterschiedliche Ergebnisse liefern kann (müßte man nochmal nach Prüfen).
Lösung: Einen x beliebigen Hash-Algorimthus für die Hashberechnung der Felder verwenden.
14.07.2010
Floyd 14,6k 3 9
Floyd 14,6k 3 9
Was haltet ihr von meiner Lösung?
Floyd 16.07.2010
ad GetHashCode - kurz gesagt: nur für Dictionary<TK,TV> udgl. verwenden, sonst können hier Fehler passieren.
Statt GetHashCode eine Methode GetCheckSum und gut ist es (habe ich in meiner AW auch geschrieben ;-)
gfoidl 16.07.2010
"Statt GetHashCode eine Methode GetCheckSum" du musst meine Antwort auch bis zu ende lesen :P
Floyd 16.07.2010
Ja hab ich schon ganz gelesen ;-)
Das Problem/Feature mit GetHashCode ist nicht nur wegen eines anderen Rechners, sondern wenn die GetHashCode-Methode für die Zustandsermittlung verwendet wird muss dies ja in Einklang mit Equals sein. In einem Dic<TK, TV> liegt nun das urpsrüngliche Objekt rum und kann nicht mehr verwendet werden da ein anderer Wert für GetHashCode für das Objekt existiert -> "Inkonsistenz".

An deiner Antwort wollte ich nicht rütteln - die ist ja korrekt ;-)
gfoidl 16.07.2010
Hmm ... Equals müßte trotzem gehen ?! Da Equals in der Standardimplemtierung die Ergebnisse von GetHashCode() beider zu vergleichender Objekte vergleicht müßte das selbe Ergebnis raus kommen.
Darüber hinaus könnte man, wie du es bereits in deinem post geschrieben hast, das ganze auch "GetCheckSum" nennen.

Wollte nun mal ne Diskussion anstoßen weil mir sonst zu langweilig wird.
Floyd 16.07.2010
0
begin transaction

insert into Stammdaten_History
select 'GENiALi' as Username, Now() as Updated, Anrede, Vorname, Nachname, ...
from Stammdaten
where (...) and (!(Anrede<=>'Frau') or ...)

nur wenn rows == 1

update Stammdaten set Anrede='Frau', ... where ...

commit transaction
14.07.2010
BeachBlocker 617 3
In dem Fall wäre ein Trigger sinnvoller da man das Historyschreiben a) nicht an allen stellen nachpflegen muss, b) man es nicht vergessen kann, c) es eine Menge Schreibarbeit sparrt
Floyd 14.07.2010
0
DOPPEL-POST
14.07.2010
Floyd 14,6k 3 9
Floyd 14,6k 3 9

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH