Laden...

LINQ Einschränkungen

Erstellt von binaryblob vor 12 Jahren Letzter Beitrag vor 12 Jahren 2.098 Views
B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren
LINQ Einschränkungen

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

C
112 Beiträge seit 2009
vor 12 Jahren

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

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

Hmm das ist eine Lösung, aber etwas aufwendig.... Gibt es nicht etwas praktischeres?

Danke

1.552 Beiträge seit 2010
vor 12 Jahren

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

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

Ok, danke für den Tipp! Aber Grundproblem besteht so ja immer noch...

1.552 Beiträge seit 2010
vor 12 Jahren

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

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

Unwahrscheinlich schon, aber nicht unmöglich... Vielleicht nehme ich es aber etwas zu genau 😉 Danke

1.552 Beiträge seit 2010
vor 12 Jahren

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

3.825 Beiträge seit 2006
vor 12 Jahren

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

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

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

3.825 Beiträge seit 2006
vor 12 Jahren

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

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

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

731 Beiträge seit 2006
vor 12 Jahren

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

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

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....

3.511 Beiträge seit 2005
vor 12 Jahren

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)

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

Jaaaa, das hört sich gut an! Und was ist in meinem Fall zu empfehlen? Danke

3.511 Beiträge seit 2005
vor 12 Jahren

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)

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

Hat evtl. jemand eine Trigger Beispiel, mit einer ähnlichen Situation wie ich Vater, Kind Aktiv/Inaktiv?

Danke

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren
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

3.511 Beiträge seit 2005
vor 12 Jahren

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)

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

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!

T
708 Beiträge seit 2008
vor 12 Jahren

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...

B
binaryblob Themenstarter:in
36 Beiträge seit 2008
vor 12 Jahren

Ok, und wie kann ich das einfach anpassen, weil früher oder später evtl. mehrere gleichzeitg geändert werden?

Danke!