Hallo Zusammen
Ich verwende SQL-Server 2008 R2 mit LINQ und greife über .NET 4.0 darauf zu.
Folgende Frage: Ich habe z.B. eine Tabelle die ein bool Feld "Aktiv" hat. Dieses Aktivfeld sollte aber nur gesetzt werden können (auf true), wenn in einen anderen Tabelle alle Records ihr "Aktiv" Feld auf false haben.
Eigentlich eine einfache Sache: Kurz alle Items über foreach durchgehen und wenn alle false, kann ich das eigene Item true setzen. Aber wenn ich Multiuser Zugriff habe, könnte ja einer in dieser Zeit ein Item ändern... Was dann zwar für die DB Ok ist, aber für meine Applikation nicht....
Gibt es nicht eine Möglichkeit das über die Tabellen zu definieren? Oder wie löst man solche sachen?
Danke
Moin,
dann lass doch nicht die Clients direkt mit der Datenbank sprechen sondern über einen Server, der alle angemeldeten Benutzer verwaltet und ggf. Zugriffe sperrt, falls der von Dir geschilderte Fall droht, einzutreten.
Grüße
Christian
Hmm das ist eine Lösung, aber etwas aufwendig.... Gibt es nicht etwas praktischeres?
Danke
Hallo binaryblob,
Kurz alle Items über foreach durchgehen und wenn alle false
Verwende doch überall LINQ
item.Aktiv = anderItems.All(i=>!i.Aktiv);
Dabei wird direkt die Query ausgeführt und ist sicherlich schneller als mit einer foreach. Noch dazu dass du dazu alle Daten auf dem PC geladen haben musst.
Gruß
Michael
Du könntest vor dem checken mit
ObjectContext.Refresh
dir die aktuellen Daten laden. Dass sich dann vom Laden über dem Checkn zum SaveChanges noch etwas ändert ist in dieser kurzen Dauer dann auch eher unwahrscheinlich
Unwahrscheinlich schon, aber nicht unmöglich... Vielleicht nehme ich es aber etwas zu genau 😉 Danke
Vielleicht nicht uninteressant:
How to: Manage Data Concurrency in the Object Context
Saving Changes and Managing Concurrency
So ganz verstehe ich dein Problem nicht.
Lies doch die Daten aktuell und setze das Flag wenn nötig.
Wenn dann eine Sekunde später ein User ein Flag verändert dann ist das halt Pech. Das kann keine Applikation der Welt vorhersehen.
Wenn Du ganz sicher sein willst setze ein SQL-KOmmando ab das das Flag setzt, dann kann kein anderer User dazwischenkommen.
Vielleicht erzählst Du mal was Du damit machen willst.
Grüße Bernd
Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3
Das Problem ist, das man das Flag des einten Records nur auf true setzen können sollte, wenn alle Flags in einer anderen Tabelle auf false sind.
Die haben eine Beziehung untereinander. Sozusagen wenn alle Kinder deaktiviert sind, soll erst der Vater deaktivierbar sein.
Und wenn jetzt ein Client zwischen den Auslesen und deaktivieren des Vaters eben noch ein Kind aktiv setzt, ist dann sozusagen die DB aus Sicht der Anwendung inkonsistent...
Darum wenn man das beim SQL-Server definieren könnte, wäre es zu 100% sicher! Klar?
Gruss bin
Nein, noch nicht klar.
Was soll denn passieren wenn alle Kinder deaktiviert sind, dann der Vater deaktiviert wird, und dann eine Minute später ein anderer User ein Kind wieder aktiviert ?
Wird der Vater durch den Anwender deaktiviert ?
Grüße Bernd
Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3
Wenn wieder ein Kind aktiviert werden soll: So wird das durch das Clientprogramm verhindert (nur möglich wenn Vater auch aktiv). Aber auch hier wäre eine DB Lösung viel eleganter....
Aber das Hauptproblem ist, wenn jetzt ein Client alle Kinder auf inaktivität überprüft, OK meldet -> dann kommt ein anderer Client setzt ein Kind Aktiv -> dann der alte Client setzt den Vater inaktiv (da er meint alle Kinder seien inaktiv).
Das ist ein Problem im ms Bereich, für das es doch praktische Lösungen geben sollte?
Danke
Ich finde es ehrlich gesagt schon komisch, diese Art der Businesslogik durch die DB zu implementieren, aber hey was solls... 😉
Kann man das vielleicht über einen Check-Constraint hinbekommen...
So nach dem Motto (in Pseudo SQL^^):
CHECK parent.activeState = kind1.activeState OR kind2.activeState OR .... OR kindN.activeState
MfG
wax
Ja andere Lösung gibts ja nicht, ausser einen Server... Und das ganze über die DB zu machen, wäre ja wohl das sauberste finde ich....
Hallo,
hier bietet der SQL Server dir mehrere Möglichkeiten
a)
Verwende Trigger um den Zustand der Childs zu überprüfen. Lege z.B. auf der Parenttabelle einen UPDATE Trigger an, der prüft ob die Spalte 'Aktiv' sich ändert und alle Kinder den dazu entsprechenden Wert besitzen. Wenn nicht, mach im Trigger ein Rollback und schmeiß eine Exception (RAISERROR). Dies kann dann in der Client Anwendung behandelt werden.
b)
Du legst ein CHECK Constraint auf die Spalte und hinterlegst eine STORED FUNCTION, die die Überprüfung vornimmt. Wenn der Check fehlschlägt, wird seitens SQL Server eine Exception geschmissen, die du ebenfalls im Client behandeln kannst.
c)
Du erstellst eine STORED PROCEDURE, die die Logik übernimmt. Da eine SP nicht von Haus aus LOCKs erzeugt, musst du dich selber dann darum kümmern. Das kann man mittels dem Hint ROWLOCK dann machen. Auch hier würde ich dann in einem Fehlerfall mittels RAISERROR den Client benachrichtigen, das da was schiefgelaufen ist.
"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)
Jaaaa, das hört sich gut an! Und was ist in meinem Fall zu empfehlen? Danke
Alle drei Möglichkeiten haben ihre Vor- und Nachteile.
Ein UPDATE Trigger wird nicht pro Row ausgelößt, sondern pro Menge. D.h., das wenn du ein UPDATE xxx SET WHERE LastName = 'Bla' ausführst, enthält die Tabelle "inserted" innerhalb des Triggers alle betroffenden Zeilen. Du müsstest also für jede Zeile die Überprüfung ausführen. Etwas mehr Tipparbeit.
Ein CHECK Constraint wird immer ausgeführt. Ob sich nun die Spalte 'Aktiv' ändern oder Peng. Das geht halt zur Lasten der Performance. Wobei man mit geschickten Setzen der Indizes hier einiges erschlagen kann.
Bei der SP kannst du dir ganz leicht eine Race Condition einfangen, sprich ein Deadlock auf der DB. Deswegen muss die SP, wenn diese denn ein LOCK machen soll, gut durchdacht sein.
Tja, such dir das aus was am besten zu dir passt. Ich persönlich würde den Trigger Weg wählen, da dieser am wenigsten Nachteile bringt.
"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)
Hat evtl. jemand eine Trigger Beispiel, mit einer ähnlichen Situation wie ich Vater, Kind Aktiv/Inaktiv?
Danke
CREATE TRIGGER alert_me
ON dbo.Location
AFTER UPDATE
AS
IF NOT UPDATE(Inactive)
RETURN
IF NOT (SELECT Inactive from inserted) = 1
RETURN
IF (select count(*) from dbo.Entity, inserted where dbo.Entity.LocationId = inserted.LocationId AND dbo.Entity.Inactive = 0) > 0
BEGIN
ROLLBACK
RAISERROR ('Inactive is 1', 16, 10)
END
ELSE
RETURN
GO
So ist das gut in etwa, oder habe ich was übersehen?
Danke
Hi,
das letzte ELSE RETURN kannst du dir sparen. Den Trigger würde ich nicht auf AFTER UPDATE stellen, sondern nur auf UPDATE. Aber ansonsten sieht es soweit in Ordnung aus.
"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)
Hallo Khalid
Danke für die Rückmeldung!
Noch eine Frage du hast gesagt das der Trigger nur "pro Menge" ausgeführt wird.
Das habe ich noch nicht ganz begriffen.
Was heisst das in meinem Fall jetzt genau?
Über das GUI kann immer (momentan) nur 1 Datensatz ausgewählt werden und der wird dann bearbeitet und gespeichert, aber was passiert wenn 2 Clients zur gleichen Zeit etwas ändern, klar der letzte Gewinnt, aber muss ich da jetzt den Trigger noch irgendwie anpassen oder wird der jetzt immer ausgeführt??
Danke!
Hallo binaryblob,
wenn Du in deinem Programm immer nur einen Datensatz ändern kannst, sollte es keine Probleme geben.
Jedoch ist es möglich z.B. direkt per SQL-Statement, mehrere Datensätze auf einmal zu ändern.
In diesem Falle wird nicht für X geänderte Datensätze X-mal dein Trigger ausgeführt, sondern exakt einmal mit X-Datensätzen in Inserted & Deleted.
Dann springt dein Code bei "RETURN" raus, obwohl ein nachfolgender Datensatz ein Rollback machen müsste...
Ok, und wie kann ich das einfach anpassen, weil früher oder später evtl. mehrere gleichzeitg geändert werden?
Danke!