| 

.NET C# Java Javascript Exception

4
Ich habe ein kleines konzeptionelles Problem mit der Concurrency in einer Anwendung. Zwar nicht .NET-spezifisch, aber ggf. gibt es da mit ADO.NET oder O/R Mappern vielleicht out-of-the-box Lösungen.

Ich konstruiere mal ein allgemeines Beispiel:
- Tabelle "ProductCategories"
- Tabelle "Products"

Ein Nutzer bearbeitet oder erstellt gerade ein "Product" und selektiert dabei "ProductCategories" . Die Anwendung speichert die "ProductCategories" Zuordnung. Falls parallel (kurz vorher) von einem anderen Nutzer die ProductCategory gelöscht wird, wird es zu einer ForeignKey-Exception kommen.

Jetzt ist die Frage, wie man dies handhaben kann, sodass der Nutzer eine im Idealfall aussagekräftige Meldung erhält und weiß was los ist, aber zumindest dass die Anwendung nicht in einer rohen Exception landet, wobei ein generisches Abfangen von SQL-Exceptions die letzte Lösung sein sollte, wenn nichts an anderes hilft.

In einer klassischen Webanwendung wäre das Problem normalerweise wohl kleiner. Per Post wird eine CategoryID gesendet, die nicht mehr existiert, damit muss die Anwendung sowieso umgehen (aus Sicherheitsgründen) und wird diese einfach ignorieren.

In einer Desktop-Anwendung wäre das Problem schon größer, denn da gibt es eine Verarbeitung wie bei Webanwendungen nicht und man vertraut den Daten ja normal.

Was wären denn sinnvolle Lösungen?
- Vor dem Einfügen prüfen ob das Zielobjekt existiert (langsam, aufwendig wenn überall implementiert?)
- Den ErrorCode der SQL-Exception auf FK-Violations prüfen (hier allerdings Gefahr von falsch interpretierten Fehlern)

Danke für Hinweise.
13.03.2013
kleffel 654 1 9
3 Antworten
2
Ich finde, Du solltest das Problem nicht zuerst von der technischen Seite aus anpacken, sondern von der fachlichen. Je nachdem, um was es bei Deinen echten Daten geht, ist die eine oder die andere Lösung angezeigt: Transaktion abbrechen und Nutzer informieren, Datensatz mit Referenz auf eine auf eine nur gelöscht oder ungültig markierte Kategorie speichern, Datensatz ohne Referenz speichern, ... - das ist alles erst mal keine technische Fragestellung, sondern sollte sich daran orientieren, was im konkreten Fall wirklich inhaltlich sinnvoll ist.
14.03.2013
Matthias Hlawatsch 13,2k 4 9
Gute Antwort :)
In meinem Fall ist das Auftreten unwahrscheinlich. Es würde ausreichen, wenn der Nutzer darüber informiert wird. Allerdings lag eben gerade das Problem darin zu unterscheiden -> FK Exception, weil wirklich Concurrency-Situation vorliegt (das ist kein Programmfehler, sondern ein erwartbares Verhalten, was z.B. nicht in den Errorlog gehört) oder FK Exception aus anderen Grund -> was ein Programmfehler ist und in den Errorlog gehört. Für meinen Fall war es nicht schlimm, diese Unterscheidung nicht treffen zu können und alles als generischen DB Fehler zu behandeln.
kleffel 14.03.2013
Aber wirklich schade das es kein Pattern gibt, um die Concurrency Situation mit wenigst möglich Aufwand generisch zu ermitteln
kleffel 14.03.2013
+1(0000...) für die Ansage, erst den Problemraum zu erkunden. Der Lösungsraum begrenzt sich dann von selbst.
ffordermaier 14.03.2013
3
Eine einfache und saubere Lösung wäre es das ganze im SQL in einer Transaktion zu lösen:

create proc spAddProducts @PcId int, @Produkte dbo.ProductTable as
begin try
begin tran
if not exists(select * from ProductCategories where id=@PcId)
raiserror('Productcategorie ''%s'' existiert nicht', 16, 1, @PcId)

insert into Products (...)
select @PcId, *
from @Produkte

commit tran
end tran
end try
begin catch
if @@TRANCOUNT>0 rollback tran
exec dbo.spRethrowError
end catch
GO


create PROCEDURE [dbo].[spRethrowError]
AS -- Return if there is no error information to retrieve.
IF ERROR_NUMBER() IS NULL
RETURN ;

DECLARE @ErrorMessage NVARCHAR(4000),
@ErrorNumber INT,
@ErrorSeverity INT,
@ErrorState INT,
@ErrorLine INT,
@ErrorProcedure NVARCHAR(200) ;

-- Assign variables to error-handling functions that
-- capture information for RAISERROR.
SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE(), @ErrorLine = ERROR_LINE(),
@ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-') ;

-- Building the message string that will contain original
-- error information.
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' +
'Message: ' + ERROR_MESSAGE() ;

-- Raise an error: msg_str parameter of RAISERROR will contain
-- the original error information.
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber, -- parameter: original error number.
@ErrorSeverity, -- parameter: original error severity.
@ErrorState, -- parameter: original error state.
@ErrorProcedure, -- parameter: original error procedure name.
@ErrorLine-- parameter: original error line number.
) ;
13.03.2013
Floyd 14,6k 3 9
Floyd 14,6k 3 9
Danke, das ist ziemlich viel Holz bei noch mehr Beziehungen (nutzt du dafür einen Generator?). Du ersetzt den generischen FK-Fehler (ermittelbar durch ErrorCode) durch einen Benutzer-definierten. Damit lässt sich aber nicht wirklich ermitteln, ob die FK-Violation durch einen Programmfehler oder auf Concurrency (kein Fehler, Situation) zurückzuführen ist?
kleffel 13.03.2013
1
Eine einfache aber dafür sehr effektive Variante besteht darin die Daten NICHT zu löschen, sondern einfach nur die (in Deinem Beispiel zu löschende) Kategorie über ein Status-Flag zu kennzeichnen:
0 = OK
-1 = gelöscht

So macht das z.B. DotNetNuke, die ja eine Papierkorbfunktion haben, wo alle Objekte, die einen Status -1 haben dann endgültig gelöscht werden können.

Eine andere oft eingesetzte Möglichkeit ist das Setzen von Gültigkeitszeiträumen.
Statt die Kategorie zu löschen, wird ein "Gültig-Bis"-Datum gesetzt.
Deine UI kann das dann auswerten und solche Kategorien dann z.B. disablen oder durchgestrichen formatieren.

LG, Micha
13.03.2013
mblaess 1,2k 1 9
mblaess 1,2k 1 9
solche Soft Deletes habe ich in der Vergangenheit auch schon benutzt, aber sie verlagern das Problem eher. "Gültig-bis" ist denke ich eine wirklich gute Geschichte für so etwas wie Preise. Das Beispiel mit Product und ProductCategory war eher als Beispiel gemeint. In meinem konkreten Fall wäre "Gültig bis" da overkill
kleffel 13.03.2013
Es geht auch gerade um einen absoluten Edge Case, d.h. nach dem Laden der Maske, wurden Daten gelöscht, die auf der Maske zu sehen sind.
kleffel 13.03.2013

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