Laden...
Avatar #avatar-3273.gif
Mr. Bart Simpson myCSharp.de - Member
Anwendungsentwickler Mittelfranken Dabei seit 13.03.2004 502 Beiträge
Benutzerbeschreibung

Forenbeiträge von Mr. Bart Simpson Ingesamt 502 Beiträge

28.11.2022 - 11:54 Uhr

Ich würde gerne einen SSE (Server-Side-Events) Client in C# für Blazor WASM nutzen (suche grad nach einer fertigen Lib, andernfalls implementiere ich selbst...) und lese mich dazu gerade ein.

Eine ähnliche Frage wurde bereits in einem anderen Thread gestellt.
Damals hatte Abt u.a. kommentiert

Client SSE (über Event Source) aus Deinem Code mit C# ist so in der Form nicht 1:1 heute möglich (weils Blazor noch nicht kann, rudimentär eben SignalR);

Ich frage mich nun: Warum ist das so? bzw. warum sollte das so sein? Meinem Verständnis nach kann man doch in Blazor WASM den HttpClient nutzen und damit sollte das doch implementierbar sein. Oder gibt's da ein Problem dass ich grad nicht sehe bezüglich des Streamings der Response?

Hintergrund: Ich bin grad dabei eine Blazor WASM Anwendung zu entwickeln die eine API mit SSE nutzt (Openhab). Ich bekomme das "problemlos" hin, in dem ich JS verwende - aber dafür benötige ich dann JS-Interop in beide Richtungen (daher die Anführungszeichen...). Ich fände es natürlich schon besser, dass dann komplett in C# zu machen - schließlich nutz ich ja (auch) deswegen Blazor 😉

Bart Simpson

21.12.2017 - 11:52 Uhr

Ja - natürlich.
Und als Zwischenschicht verwendest Du ein API Management Tool (gibts in Azure schon, oder Tyk.io oder API Umrella als unabhängige Lösungen).

Und je nach Umgebung trennen wir noch folgendes (wobei mein Fokus auch Industrie 4.0, Factories, Cloud und Co ist; weiß nicht inwiefern ihr da eine ähnliche Anforderung an die Umgebung habt)

Je länger ich über Dein Modell nachdenke, desto plausibler erscheint es mir - auch im Kontext unserer konkreten Anforderungen. 8)
Wobei ich allerdings noch nicht sicher bin, ob es wirklich zwei so strikte Management-Layer geben wird oder sollte. Aber das ist noch "im Fluss" (Hintergrund: Es gibt hier ein paar Aspekte, die ich in einem Forum nicht breittreten darf...)

Aber ich möchte mich auf jedenfall schon mal bei allen Antwortgebern bedanken! Allein die Tatsache mal was mit der Brille von jemand anderem zu sehen hat mir viel geholfen - und sei es z.T. auch "nur" die Tatsache, dass andere Leute auf die selben Lösungsansätze kommen.
Weitere Anregungen sind natürlich immer gern gesehen! 😁

Bart Simpson

P.S. @Abt: Ich bewege mich exakt in dem von Dir genannten Umfeld - wobei ich aber bei vielen (zumindest aktuell) nicht "Industrie 4.0" hinschreiben würde, sondern eher 0.9 beta 2 :evil:

20.12.2017 - 11:22 Uhr

Hi,

schon mal vielen Dank für die Antworten. Die jeweiligen Tendenzen zeigen mir jetzt schon, dass ich nicht komplett falsch liege mit meinen Ansichten 😁
Ich versuch mal auf die für mich wesentlichen Punkte einzugehen:

Das verstehe ich aber nicht so ganz. Vor allem b). Viel zentraler als Dein Kernel Layer kann es doch gar nicht mehr werden??

Was ich meinte war im Prinzip: Es gibt a) das schon erwähnte Modul zur Benutzerverwaltung und evtl. b) zusätzlich in gewisser Weise die selbe Funktionalität irgendwie nochmal im Kernel - aber eben "nur" für die Nutzung von dort aus. Und damit ergäbe sich eine gewisse funktionale Doppelung (was ich ja auch selbst kritisiert habe).
Die Idee mit dem Proxy klingt erst mal ganz net - aber dann muss man auch kucken, dass es nicht den Proxy um des Proxy's Willen gibt. Denn dann könnte u.U. auch direkt von "extern" mit der Funktionaliät im Kernel kommuniziert werden.
Ich weiss nicht so recht, ob dann nicht wieder Dein vorgeschlagener Punkt 2. der bessere wäre.

Aber vielleicht ist das mal ein Anfang für:

und freue mich auch über eifrige Diskussionen

Läuft! 😁

zB. für HealthCheck und Co nimmt man in vielen Fällen die Service Registry

Das ist auch genau unser Plan 😁 Ich hab nur das in gewisser Weise falsche oder zumindest suboptimale Beispiel HealthCheck gewählt, weil's so schön plakativ ist und damit auch jemand der sich noch nicht konkret mit der Thematik beschäftigt hat auch eine Vorstellung vom "Plan" bekommen kann... Verzeih also die bewusste Irreführung. 😉

Der Punkt, dass die zentrale Stelle keine Autorisierung machen kann/soll ist an sich ein valides Argument. Würdest Du den Standpunkt auch weiterhin vertreten, wenn ich folgende "Erweiterung" einbaue?*Das zentrale Module ist zuständig für die Authentifizierung (ähnlich wie Du das schreibst, von daher sag ich mal: -> Check!) *Die Nutzenden (externen, "drüberliegenden") Programme bekommen zusätzlich Zugriff auf eine Api, mit der sie an zentraler Stelle auch Informationen/Daten zur Autorisierung ablegen/verwalten können.

Hintergrund davon ist: Es kann/wird mehrere Programme geben, die sich eben jene (letztendlich) "administrative Konfiguration" (sprich: Wer darf was) teilen.

Was man in so einem System dann auch nicht vergessen darf, ist das Logging.

Den konkreten Punkt haben wir schon explizit auf dem Schirm. Ich bin aber sehr dankbar, wenn noch irgendwelche anderen Punkte/Anmerkungen kommen - einfach um nichts zu übersehen...

Bei uns ist es so gelöst, das die Verwaltung von Rechten (BL) und die Prüfung von Rechten (vereinfacht DAL) getrennt sind.

Das wollen wir grundsätzlich auch tun. Allerdings ist es "von außen betrachtet" dann doch wieder in einem Modul verortet - wenn auch u.U. über eine weitere/andere API zu erreichen.

U.a. weil unser Projekt in .net Core entwickelt werden soll, haben wir uns die dort vorhandenen Mechanismen / Lösungen schon genauer angekuckt und planen auch einiges von den Konzepten und Ideen bei uns einfließen zu lassen. Für unseren speziellen Anwendungsfall können wir aber aller Wahrscheinlichkeit nach nicht oder nicht vollständig darauf zurückgreifen... Da ist aber noch alles "im Fluss"...

Grundlegend Probieren wir in den Layern so „hoch“ wie möglich zu prüfen ob die Rechte/Lizenz vorhanden ist.

Das favorisiere ich im Prinzip auch. Allerdings gilt natürlich auch die Prämisse "never trust user input" und es darf natürlich nicht durch eine solche Verlagerung der Prüfung dazu kommen, dass angedachte Absicherungen/Kontrollen ausgehebelt oder umgangen werden können.
Um bei meinem Bild mit dem Betriebssystem zu bleiben: Wenn Dir die UI der Systemsteuerung nicht erlaubte eine kritische Stelle umzukonfigurieren, dann solltest Du das auch nicht können, wenn Du direkt versuchst, z.B. die Registry zu editieren.

Bart Simpson

20.12.2017 - 09:01 Uhr

Hallo zusammen,

ich konzipiere gerade (mit Kollegen) an einem größeren Softwareprojekt, welches Basis-Infrastrukturdienste für weitere Software zur Verfügung stellen soll (ich sprech mal bildlich: Eine Art Betriebsystem für andere Software).
D.h. es soll einzelne "Module" geben (Stichwort: Microservices), die für sich genommen funktionieren können und im Zusammenspiel miteinander Ihre Stärken ausspielen - diese sollen aber auch und vor allem genutzt werden von Software die "weiter oben" läuft.
"Unter" den Modulen soll es einen Layer geben, der sich -vereinfach gesagt- um die Verwaltung eben jener Module kümmert. So soll dort z.B. ein ggf. nötiger Dienst gestartet werden oder auch eine Art "Health Checking" für die Module laufen (Um beim Bild von oben zu bleiben: Der Kernel des Betriebssystems). Grundidee ist es dabei, den unteren Layer so schlank wie möglich und damit einfach und performant zu halten.

So weit so schön (und einfach)... :evil:
Nun soll es unter anderem ein Modul geben, welches eine zentrale Benutzer- und Rechteverwaltung erlaubt (Authentifizierung und Authorisierung). Damit sollen weitere Programme in die Lage versetzt werden, diese für sie nötige Funktionalität an eine zentrale Stelle zu delegieren. Auf den ersten Blick sofort etwas was man im Layer für die Module ansiedeln möchte, da es sich augenscheinlich ja um ein solches handelt.
Doch dann gibt es leider Anforderungen an den untersten Layer (im Bild: der Kernel), die ebenfalls irgendwie einer Rechteprüfung unterliegen müssen. (Beispielsweise: Wer darf die Konfiguration für z.B. das Startverhalten von Modulen ändern)

Nun stellt sich die Frage: Welche Softwarekomponente führt die dafür nötigen Prüfungen durch? 🤔 *Das selbe Modul wie oben? Damit bringt man aber eine (eigentlich unnötige und auch unerwünschte) Abhängigkeit des untersten Layers von einer darüber liegenden Schicht ins Spiel... *Eine (Kernel-) eigene Implementierung der Rechteprüfung? Damit wird a) u.U. Code gedoppelt und vor allem b) die Idee der zentralen Benutzerverwaltung ad absurdum geführt

Sieht jemand noch weitere Alternativen? Hat jemand gute Vor- / Ratschläge für die Lösung des Problems? Ich wäre für jedewede Information dankbar und freue mich auch über eifrige Diskussionen - denn damit bekommt man immer einen anderen Blickwinkel, der einem auch mal die Augen öffnen kann. Betriebsblindheit ist schließlich eine Berufskrankheit für Softwareentwickler... 😉

Bart Simpson

13.10.2016 - 12:55 Uhr

Es geht aber auch ohne Nachrichtenschleife: Mit 'nem KeyboardHook

Bart Simpson

11.05.2016 - 12:45 Uhr

Hi,

für das schon angesprochene ASP.net MVC gibt's recht schöne Tutorials - z.B. eines rund um das Thema Anmeldung etc. - zu finden unter http://www.asp.net/mvc/...

Bart Simpson

22.10.2015 - 08:35 Uhr

Das Pessimistische Locking lässt sich ohne Service nur sehr schwierig richtig implementieren.

...naja. Sooo schwer ist das nun auch nicht, zumindest wenn man "den Ball flachhält" 8)
Wir tun das hier ungefähr wie folgt: Der Zugriff auf einen Datensatz erfolgt nicht über die Tabelle selbst, sondern über eine Stored Procedure, die beim Zugriff zwei (zusätzliche) Felder in der Tabelle bearbeitet bzw. auswertet. Das eine beinhaltet ggf. den Client, der den Datensatz gerade bearbeitet (also "das LOCK besitzt"), das andere den Zeitpunkt, bis zu dem die Sperre (noch) gültig ist.
Wenn ein Client "als erster" zugreift, werden die beiden Felder aktualisiert und er bekommt in einer weiteren (berechneten) Spalte im Ergebnis mitgeteilt, dass der Datensatz schreibbar ist.
Kommt nun ein zweiter Client, so wird dies an den beiden Spalten erkannt, und der Client kriegt über die berechnete Spalte mitgeteilt, dass der Datensatz Read-Only ist.

Zu beachten sind dann noch die folgenden Punkte:
a) Wenn ein Client das Lock für einen längeren Zeitraum braucht, muss ggf. das Timeout aktualisiert werden
b) Das normale Aufheben der Sperre muss sauber implementiert werden (z.B. beim Speichern), damit die anderen Clients nicht unnötig lange auf das Timeout warten müssen.
c) Die Dauer des Timeouts sollte sinnvoll gewählt werden
d) Die Logik der Stored Procedure muss natürlich auch mit ggf. abgelaufenen Timeouts umgehen können.
e) Das ganze ist natürlich auf einer gewissen Kooperation aufgebaut - d.h. wenn ein Client irgendwie anders zugreift oder das Read-Only Flag nicht beachtet, greift das natürlich nicht.

Gibt sicher noch ein paar Haken und Ösen bei dem Vorgehen, bei uns klappt das aber recht gut - wobei ich anmerken muss, dass unsere Clients keine Benutzer im eigentlichen Sinn sind, sondern Hintergrundprozesse, die ggf. auch mal warten können 😁

Bart Simpson

21.10.2015 - 14:55 Uhr
command.Parameters.AddWithValue("@MyDateParameter", DateTime.Now.Day + "." + DateTime.Now.Month);  
  

...also dazu fällt mir jetzt eigentlich nur noch eines ein: AUTSCH. :evil:

Ich hab's nicht ausprobiert und es mag durchaus sein, dass Excel diese Notation akzeptiert - aber warum verwendest Du hier so ein -mit Verlaub- String-Gefrickel, wenn es mit den Datumsfunktionen doch genau das gibt, was Du brauchst um es vernünftig zu lösen?
Spätestens wenn Du mal mit Sprachübergreifenden Daten zu tun hast (z.B. deutsches vs. englisches Datumsformat) sind die Probleme bei Deiner Lösung vorprogrammiert...

Bart Simpson

21.10.2015 - 14:47 Uhr

Hallo,

also mal abgesehen davon, dass ich die Anforderung hier

Leider hilft das auch nichts. Da der Tag "heute" in meinem Fall falsch wäre. Ich benötige nur den Monat und den Tag, da es eine Art: Wer hat heute Geburtstag werden soll.

nicht wirklich aus Deinen bisherigen Beschreibungen herausgelesen habe, hilft Dir das sehr wohl - allerdings sollte man sich halt ein wenig mit den Grundlagen von SQL beschäftgen...

SELECT Text,Datum FROM [Tabelle1$] WHERE month(Datum)=month(@MyDateParameter) and day(Datum)=day(@MyDateParameter);

Bart Simpson

21.10.2015 - 14:27 Uhr

Also wenn Du in Excel wirklich als Datum formatiert hast, dann solltest Du das auch als Datum ansprechen - und zwar richtig (sprich: so wie es sich gehört...):

OleDbConnection conn = new OleDbConnection();
            conn.ConnectionString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Mappe1.xlsx" + @";Extended Properties=""Excel 8.0;HDR=YES;IMEX=1;ImportMixedTypes=Text;TypeGuessRows=0""";


            string sql = "SELECT Text,Datum FROM [Tabelle1$] WHERE Datum=@MyDateParameter;";            
            OleDbCommand command = new OleDbCommand(sql, conn);
            command.Parameters.AddWithValue("@MyDateParameter", DateTime.Today);
...

Bart Simpson


Edit: Noch so als kleiner Hinweis: Der Excel-OLE-DB-Provider ist sehr zickig, was die "Typenreinheit" innerhalb der einzelnen Spalten angeht...

19.10.2015 - 09:29 Uhr

Wenn Du unter Windows bist, kannst Du das aber auch komplett an Windows delegieren und die Passwörter verschlüsselt im aktuellen Benutzerprofil ablegen (schau Dir mal die gespeicherten Netzwerkkennwörter an...). Das geht mit ein wenig PInvoke relativ einfach (u.a. mit der CredRead-Funktion).
Einen Einstieg für C# findest Du z.B. auf GitHub.

Bart Simpson

05.10.2015 - 11:58 Uhr

Also aktuelle rechne ich mit ca. 10 Mio Datensätzen.

Damit komm ich auf einen "zusätzlichen" Speicherbedarf von ca. 1,5 GB (plus das, was die BLOBs tatsächlich benötigen - aber das brauchst Du ja in beiden Varianten)... Stellt das wirklich ein Problem dar? Das ist in meiner täglichen Arbeit eher zu vernachlässigen - aber das mag natürlich in Deinem Anwendungsfall anders sein...

Weißt du wie sich der SQL-Server bezüglich der NULL(able) Datensätze verhält?
Reserviert er hierfür überhaupt irgendwas an Speicher?

Das sind Details die u.a. von der Version abhängen - aber hier ist die Doku oder die MSDN Dein Freund...

Bart Simpson

05.10.2015 - 11:41 Uhr

Zum einen muss ich bb1898's Anmerkung zustimmen, dass Du Dir evtl. nochmal generelle Gedanken bezüglich des "DB-Designs" bzw. der "DB-Egnine" machen solltest.

Um beim Thema zu bleiben und auf Deine Tabelle Bezug zu nehmen: Wenn das alles ist, würde ich persönlich nicht mal drüber nachdenken, das in zwei Teile zu zerlegen. Die paar (im wesentlichen) INT-Werte sind marginal im Hinblick auf den Speicherverbrauch. Die BLOBs werden vom SQL-Server sowieso "ausgelagert", also dürfte das keinen großen Overhead erzeugen...
Wenn Du also nicht gerade jenseits der X-Millionen Datensätze bist, dann lautet meine Empfehlung definitiv: Eine Tabelle für alles.

05.10.2015 - 11:15 Uhr

Zusätzlich denke ich, dass ich durch den nicht mehr benötigten Join vielleicht etwas Geschwindigkeit gewinnen könnte. Das ist mehr als wahrscheinlich...

Was sprich generell dafür/dagegen?

Grundsätzlich sind erst mal beide Ansätze legitim. Wenn's wirklich nur darum geht, dass Spalten NULL sind, würde ich persönlich in den allermeisten Fällen dazu tendieren, das in eine Tabelle zu packen. Das kann aber durchaus auch anders sein, wenn es wirklich extrem viele Spalten werden oder wenn die genannten JOINS recht selten nötig sind, oder, oder...

Wie hoch ist der zusätzliche Speicherbedarf für einen Eintrag in der Tabelle, wenn die optionalen Felder gar nicht belegt werden (NULL sind).Danke und Gruß

Das hängt von den Spalten-Datentypen ab. Wenn Du irgendwelche BLOB Daten (z.B. IMAGE oder auch navarchar(max) verwendest oder auch "sehr kleine" Datentypen (bit, int,...) dann ist das denke ich (im Allgemeinen) sehr leicht zu verschmerzen, was das an Speicherverbrauch kostet. Jedenfalls wenn man die ansonsten u.U. ständignötigen JOINS mit in betracht zieht. Denn Performance ist meistens deutlich wichtiger als ein bisschen mehr belegter Plattenplatz. Wenn Du allerdings sehr viele Spalten mit "festen Speicherverbrauch" (das vom SQL Server verwendete physische Layout der Tabelle ist hier ausschlaggebend) hast (meistens trifft sowas z.B. bei Spalten mit nvarchar(x), mit x irgendwo im Hunderterbereich zu), dann könnte das schon deutlich ins Gewicht fallen. Ich persönlich denke aber, dass das nur in sehr speziellen Randbedingungen eine Rolle spielen dürfte...

Du könntest ja mal ein (ggf. vereinfachtes) CREATE TABLE Statement für die beiden Varianten posten, dann kann Dir hier evtl. auch eine etwas fundiertere Aussage geliefert werden. Denn Aussagen wie "...ziemlich viele..." sind immer relativ :evil:
Und: Eine zumindest ungefähre Einschätzung für die Anzahl der Datensätze ist auch hilfreich!

Bart Simpson

23.09.2015 - 16:00 Uhr

Das geht u.U. mit den Methoden, die auch PSList nutzt - schau Dir mal das hier an...

Bart Simpson

18.09.2015 - 10:17 Uhr

Wenn Du was fertiges suchst, schau Dir mal SDelete an. Das würde theoretisch (im Sinne von "war mal so") auch die höchsten Anforerungen erfüllen. Allerdings wurde ja schon erwähnt, dass das bei SSDs (und teilweise auch bei modernen HDDs mit z.B. Hybrid-Technik) nicht mehr klappt, weil dort die Firmware in die Verteilung der Daten auf Ihre "physischen Positionen" auf dem Speichermedium eingreift. Es kann also nicht mehr gezielt überschrieben werden... Deshalb bleibt unter diesem Gesichtspunkt nur folgende Vorgehensweise: a) Löschen der Datei und b) Füllen / Überschreiben des gesamten leeren Speicherplatzes...

a) ist aber wie erwähnt nicht wirklich sicher und b) ist meistens unpraktikabel (überschreib mal eben schnell 2TB und Du weißt was ich meine...). Dazu kommt, dass für "ganz hohe" Anforderungen selbst b) u.U. nicht mehr ausreicht, da moderne Platten z.B. Bereiche nach x-maligem Schreiben aus dem "Pool" nehmen und dafür vorher unbenutzte Reserve-Bereiche ins Spiel bringen. D.h., dass der urpsürngliche Bereich gar nicht mehr (regulär) angetastet werden kann - und somit auch nicht überschrieben...

Das führt zu Richtlinien wie bei uns hier: Festplatten, USB-Sticks, etc. dürfen gar nicht nach außen gehen bzw. wiederverwendet werden. Bei Defekt / Ausmusterung muss der Datenträger physisch so zerstört werden, dass nichts mehr zu retten ist - auch nicht für die Jungs von z.B. Kroll-Ontrack...

Bart Simpson

18.09.2015 - 08:49 Uhr

Mal abgesehen davon, dass

_menu[_menu.count].process();

mit Sicherheit eine IndexOutOfRangeException werfen wird, hat Abt schon das richtige Stichwort gegeben, nämlich Func<T> (bzw. eine der Varianten mit ggf. mehr T's) - allerdings denke ich in Deinem Fall wäre es eher die Action-Klasse, da Du void zurückgibst...

Bart Simpson

17.09.2015 - 14:06 Uhr

Wie BerndFfm schon geschrieben hat, klappt das mit dem direkten Lesen von byte[] meines wissens nach in allen SQL-Server Versionen. Selbiges gilt auch für das Schreiben der Daten - dort kann direkt ein byte[] in einen Abfrage-Parameter gepackt werden.

Das bringt mich zur eigentlichen Frage: Bist Du sicher, dass die Daten überhaupt richtig in die DB geschrieben werden? Oder liegt da vielleicht schon das grundlegende Problem?

Bart Simpson

14.09.2015 - 12:44 Uhr

...da setz ich mal noch einen drauf 😁
Wir sind hier Dienstleister für BPO und haben teilweise Projekte seit mehr als 10 Jahren am laufen (was in der IT quasi Äonen darstellt).
Und ich möchte dabei nur eines sagen: Man glaubt nicht, wie oft ein Kunde (...v.a. die "großen", Global Player...) in solchen Zeiträumen seine Meinung bezüglich eines Themas um 180° und wieder zurück drehen kann... X( Von daher kann man in der Entwicklung gar nicht sauber / flexibel / langfristig /... genug denken.

Bart Simpson

14.09.2015 - 11:53 Uhr

a) wenn man ... Änderungen an der Bedeutung des Datensatzes vornehmen möchte...
b) Migration auf andere Datenbanksysteme...

Das sind u.a. Gründe dafür, warum ich immer dafür plädiere, dass DB-Internas (und dazu gehören IMHO die PKs) nichts ausserhalb der DB verloren haben. Ob man das nun über eine GUID (die als Surogat nach aussen dient) geschieht oder über was anderes (will heißen: eine andere Datenstruktur) ist erst mal zweitrangig.
Alles andere führt immer zu einer Vermischung / unsauberen Trennung der Schichten und sollte tunlichst vermieden werden.

Bart Simpson

14.09.2015 - 09:27 Uhr

[Leicht Off-Topic, aber dennoch zum Thema...]

Mich würde mal interessieren, was Ihr denn so von dem bei uns (auf Anraten unseres DB-Gurus) standardmäßig implementierten "Mittelweg" zu dem Thema haltet:

Wir verwenden normalerweise int oder bigint als PKs haben dann aber normalerweise eine weitere GUID-Spalte (mit newid() gefüllt), auf die ein eindeutiger Schlüssel/Index gelegt wird. Dadurch wird alles was in der DB abgefackelt wird (z.B. JOINs), mit den int-Werten ausgeführt, extern (also z.B. in C#) wird aber die GUID zur Adressierung/Identifizierung der Datensätze verwendet (was durch den Index auch recht flott geht).

Was meint Ihr dazu?

Bart Simpson

14.09.2015 - 08:50 Uhr

@LaTino: Ich gebe Dir zwar in weiten Teilen Recht - aber dennoch unterschlägst Du doch einen Punkt, der gerade aus dem von Dir verlinkten Blogeintrag hervorgeht: Das Verwenden (zufälliger) GUIDs als Primary Key führt a) zu einer (prinzipbedingten) Fragmentierung der zugrunde liegenden Pages und b) (für die meisten wohl wichtiger) zu einer signifikant größeren Zeitspanne, die nötig ist, um ein INSERT auszuführen.
[ACHTUNG: Bildlich gesprochen & grob vereinfacht!]
Der Grund hierfür liegt im wesentlichen in der nötigen "Umsortierung" beim Einfügen eines zufälligen Schlüssels, da der PK das physische Layout der Datenstrukturen bestimmt. D.h. wenn beim Einfügen ein neuer, zufälliger Key z.B. zwischen Position 10 und 11 eingefügt werden muss, dann muss die Tabelle ab Pos. 11 "neu sortiert und abgelegt" werden.

D.h., wenn es die Anwendung erfordert, sehr oft INSERTs auf Tabellen mit vielen vorhandenen Zeilen auszuführen, ist eine zufällige GUID als PK sehr wohl als suboptimal zu betrachten...

Bart Simpson

03.09.2015 - 08:20 Uhr

...und zwar ist auf dem frischen Win10 kein PDF-Reader installiert ⚠

Nach der Installation läuft es wieder...

Sorry wenn ich das jetzt mal so formuliere, aber wenn Du einer meiner Azubis hier wärst, dann würdest Du jetzt mal tüchtig was hinter die Löffel kriegen!?!
Die für Deine Fragestellung absolut relevanteste Information (nach der durch Taipi88 auch schon explizit gefragt wurde) hast Du im Verlauf der gesamten Diskussion völlig unterschlagen. Da opfern hier wirklich einige Leute Ihre Zeit und versuchen Dir zu helfen - und dann sowas... 🙄
Vielleicht solltest Du Dir mal den [Hinweis] Wie poste ich richtig? zu Gemüte führen und v.a. auch beherzigen...

Bart Simpson

01.09.2015 - 16:43 Uhr

Dienste mit Fenstern interagieren zu lassen ist erst mal grundsätzlich keine gute Idee. U.a. auch deshalb sind da einige Hürden zu nehmen, z.B. sich mit der richtigen WindowStation zu verbinden, etc. Ab Vista ist das auch nicht mehr möglich (und es gibt meines Wissens nach auch keinen Workaround mehr).
"Kleiner" Einstieg: MSDN

Bist Du sicher, dass Du Dein Problem nicht grundsätzlich anders angehen solltest? Z.B. mit einer GUI-Application die via (wie auch immer gearteter) IPC mit dem Dienst kommuniziert?

Bart Simpson

31.08.2015 - 08:27 Uhr

... dann kommt ein POPUP...

Das sind (unter anderem) genau die auch von MS selbst beschriebenen Probleme, zu denen es kommen kann (und auch die, die wir hatten und haben). Word ist nicht dafür gedacht / entwickelt worden, um im Hintergrund zu arbeiten. Es wird immer davon ausgegangen, dass ggf. ein User davor sitzt, der dann auch irgendwelche auftauchenden Meldungen wegklicken kann...

Bart Simpson

P.S. Wir sind grade dabei, komplett davon wegzugehen. Je nach Anwendungsfall versuchen wir Word-Dokumente direkt zu erzeugen (über das neue Open-XML-Dom) oder aber (bei komplexeren Themen) mit der (kommerziellen) Aspose Library.

26.08.2015 - 08:15 Uhr

Hi,

vorab: Ich hab keine Ahnung von Silverlight (dafür aber vom IIS)....
Im wesentlichen muss ich leider auf [Hinweis] Wie poste ich richtig? verweisen, da...

a) Ich bin mir sehr sicher, dass Du mehr als nur ein einfaches "Method not allowed" als Fehlermeldung bekommst / bekommen hast. Wenn Du also den zugehörigen Kontext für Deine Fragestellung mitliefern würdest (Exceptiondetails, Stacktrace, etc.), kann Dir evtl. auch geholfen werden...

b) Hilfreich wäre auch eine genauere Information, welche Features im IIS Du denn aktiviert hast bzw. vorher aktiv waren - da gibt es durchaus einige. Waren vorher z.B. die Standardmäßig installierten Features drauf? Wen ja, für welches OS?

Bart Simpson

26.08.2015 - 08:01 Uhr

Hi,

wenn Du Word-Automation in Hintergrunddiensten betreibst, solltest Du Dir bewusst sein, dass das a) von Microsoft nicht supported wird und b) zu einigen unschönen Problemen führen kann.
Details findest Du hier: https://support.microsoft.com/en-us/kb/257757

Bart Simpson

P.S. Wir tun's hier auch - und es funktioniert im Großen und Ganzen eigentlich - aber manchmal macht's dann doch zicken 😉

20.08.2015 - 14:46 Uhr

Hi,

also wir setzen hier Tesseract in vielen unserer Prozesse ein (allerdings noch die 2er Version). Was ich aus (reichlich) Erfahrung sagen kann:

a) Tesseract an sich funktioniert eigentlich ganz gut, hat aber desöfteren Probleme mit den Images. Was genau die Ursache ist, konnte ich noch nicht herausfinden, aber bei uns ist es in etwa bei 1 von 5000 Images so, dass sich Tesseract komplett verabschiedet und dabei meistens dann so im Speicher des Prozesses rumfuhrwerkt, dass der komplette Prozess geötet wird / werden muss... Scheint an den Images zu liegen - woran genau, weiß ich aber nicht.

b) Ich kenn den von Dir genannten Wrapper nicht, aber alle die ich bisher gesehen hab, haben mir immer mehr Ärger gemacht als die Sache wert war... Deshalb starten wir hier Tesseract immer als eigenen Prozess, der dann auch schön überwacht werden kann. Das löst dann auch gleich die unter a) genannten Probleme. Den Overhead für das jeweils Neu-Starten eines Tesseract-Prozesses können wir hier problemlos verschmerzen. Wir haben eine Parallelisierung in unserem Code eingebaut (einzelne Threads, von denen jeder seinen eigenen Prozess startet), was "in Summe" einge gute Performance ergibt.

Bart Simpson

P.S. Ich würd mich freuen, wenn Du Erfahrungsberichte zu Version 3 und/oder dem .net Wrapper hättest 😉

14.08.2015 - 12:48 Uhr

...auch eine mögliche Interpretation der Frage 😉

Und dieses Ansinnen ist mit Generics nicht möglich, denn, wie gesagt, auch generische Typen muessen zur Compilezeit festliegen.LaTino

Das ist zwar im Prinzip richtig, aber dennoch kannst Du (natürlich ohne Typsicherheit) mit Type.MakeGenericType und Activator.Createinstance Instanzen dynamisch erstellen - auch von generischen Typen.

Bart Simpson

14.08.2015 - 12:34 Uhr

...vielleicht wäre es auch mal hilfreich zu erklären, was Du eigentlich versuchst mit Deinem Code zu erreichen - ich seh in der Idee an sich grad keinen all zu tiefen Sinn 😉
Ich hab schon einiges mit Reflection gearbeitet und könnte mir durchauch Anwendungsszenarien vorstellen in denen man zu irgendwelchen ähnlichen Lösungsvorschlägen wie Deinem kommt - aber aus Deinen Fragen & Antworten schlussfolgere ich, das der Hund wo völlig anders begraben liegt (Probleme mit der Architektur, Unkenntniss der vorhandenen Möglichkeiten, etc.)

Ich lese zwischen den Zeilen, dass es um irgendeine Art von (De-?) Serialisierung in (nach?) XML geht...

Bart Simpson

14.08.2015 - 12:20 Uhr

Die Einwende von LaTino sind natürlich korrekt - aber wenn ich Dich richtig verstanden habe, sollte es dennoch möglich sein...

 public abstract class NetworkArgs : EventArgs
    {
        public abstract Type DataType { get; }                
    }

    public class NetworkArgs<T> : NetworkArgs
    {
        public T Message;

        public NetworkArgs(T Message)
        {
            this.Message = Message;
        }

        public override Type DataType
        {
            get
            {
                return typeof(T);
            }
        }
    }

Deine Liste wird dann natürlich eine List<NetworkArgs>

Bart Simpson

[Edit:]
P.S. Die Stichworte Type.MakeGenericType und Activator.Createinstance sollten Dich auf den richtigen Weg bezgl. des zweiten genannten Problems bringen...

03.08.2015 - 08:47 Uhr

Hi,

also die Ursache an sich ist ohne detailierte Meldung vom SQL Server nur sehr schwer auszumachen. Ich hab allerdings ein paar Tips, wie wir hier an sowas rangehen:

  • Mit dem SQL Server Management Studio können auch unerfahrene Nutzer ziemlich viel über grade laufende Transaktionen herausfinden. Stichwort: Aktivitätsmonitor. Dort wird angezeigt, welcher Prozess grade was tut bzw. von wem er z.B. blockiert wird.
  • Die ManagementViews, die mit SQL 2005 eingeführt und seitdem in jeder Version ausgebaut wurden sind extrem hilfreich (darauf beruht auch der Aktivitätsmonitor)
    (- Last but not Least: Lies Dir mal ein gutes Tutorial durch, welches alles rund um's Locking erklärt. Da gibt's einiges an Zusammenhängen, die man erst mal gerafft haben muss...)

Bart Simpson:

P.S. Um eine im Thread bereits angesprochenes Thema nochmal aufzugreifen: Ja, der SQL Server erkennt Deadlocks normalerweise automatisch und beendent nach einem Timeout einen der beteiligten Prozesse (welcher das ist, entscheidet er nach eigenem Gusto, typischerweise der, bei dem's am wenigsten beim Rollback zu tun gibt). Allerdings kann's natürlich sein, dass Dein .net Client ein Connection-Timeout gesetzt hat, welches früher zuschlägt. Dann bricht dieser ab und das Timeout des Deadlocks kommt nicht zum Zuge...

22.06.2015 - 10:30 Uhr

Hi,

wie schon den anderen Antworten zu entnehmen, kommt's hier auf die (Constrained) Delegation an. Das Ganze beruht auf der Kerberos-Authentifizierung und kann sehr schnell sehr kompliziert werden...

Evtl. helfen Dir diese beiden Links für den Einstieg weiter:
Using Kerberos with SQL Server
SQL Linked Server Query failed with “Login failed for user …”

Bart Simpson

29.04.2015 - 21:22 Uhr

Wenn alle beteiligten Seiten (sprich: Server & Client) die Klassen kennen (sprich: eine Referenz auf die Assembly besitzen), dann sollte das problemlos gehen - immer vorausgesetzt, dass die einzelnen Properties/Klassen allesamt serialisierbar sind. Und ich vermute mal, dass hier das Problem liegt. Da Du aber nichts über den Fehler sagst, ist das nur geraten...

-> Welchen Fehler genau bekommst Du denn (und hier ist v.a. auch die "Server-Seite" interessant)

P.S. Sehr hilfreich bei der Fehlersuche kann hierbei das Aktivieren des Tracing und anschließende Auswerten der Logfiles mit dem Service Trace Viewer (aus dem SDK, heißt im deutschen Startmenü "Viewer für Dienstabläufe" glaub ich)sein.
Außerdem kann es helfen, die Exceptions via FaultContract auch mit zu übertragen. Das macht es am Client relativ leicht, Fehler auf Serverseite zumindest eingrenzen zu können.

Bart Simpson

20.02.2015 - 12:32 Uhr

Hi,

interessant sind hier auch einige Randbedingungen. Z.B. Dürfen die Daten vom USB-Stick kopiert werden? Geht's um ein größeres Volumen?
Eine Mögliche Lösung wäre nämlich erst mal mit Deinem Programm die Daten in einen Temp-Ordner zu kopieren und dann das zweite Tool über diesen laufen zu lassen - allerdings nur dann, wenn auch alle passt.

Bart Simpson

18.02.2015 - 16:11 Uhr

Im Prinzip ja (wobei ich mir immer noch nicht sicher bin, ob ich Deine Problemstellung wirklich verstehe...) - aber ich rate Dir mal erst die Grundlagen zu verstehen, z.B. mit den 101 LINQ Samples.

18.02.2015 - 13:50 Uhr

...jetzt check ich's erst... Mischt Du da SQL und LINQ Syntax? BETWEEN ist ein SQL Keyword - ich glaub nicht, dass es das in LINQ so gibt... Versuch mal was in der Art:

var ts1 = (from l7 in db
                 where ((l7.BEGINNINGDATE >= 1417132800 && l7.BEGINNINGDATE  <= 1417133700)
                       ||  (l7.ENDDATE >= 1417132800 && l7.ENDDATE <= 1417133700)
                       ||  (l7.BEGINNINGDATE >= 1417132800 l7.BEGINNINGDATE <= 1417133700))
						&& l7.EN_ENDDATE > 1417133700
                            select l7);

Bart Simpson

18.02.2015 - 13:32 Uhr

Hi,

sorry, aber mit dem Fragment kann ich leider mal gar nix anfangen... Auf den ersten Blick seh ich if (timestamp1 == true). Das kann wohl nicht stimmen, oder?

Ich bin immer noch der Meinung, dass es geschickter ist, das ohne Schleif sondern mit GROUP BY zu lösen - aber für's Verständnis wäre die zugrunde liegende Tabellen-Struktur sehr hilfreich. Sonst kann ich leider nicht wirklich was dazu sagen...

18.02.2015 - 12:44 Uhr

Eine DLL im GAC zu registrieren geht meines Wissens nach nicht per ClickOnce (ich lass mich aber gerne korrigieren - wär interessant zu wissen...). Per Code ging es vermutlich - aber wozu? Warum verteilst Du sie nicht einfach mit (im Zweifelsfall explizit als "Einschließen" bzw. "Erforderlich" unter den Einstellungen für die Anwendungsdateien eintragen)?

Bart Simpson

18.02.2015 - 12:31 Uhr

Was ich noch anmerken möchte:

a) Du musst mit der von mir genannten Query aber aufpassen, dass Du nicht (versehentlich) doppelt zählst. Wenn ein Eintrag BeginDate z.B. bei 11:30 und EndDate bei 12:30 hat, dann würde er sowohl für 11 als auch für 12 gefunden werden... -> Für welche Stunde soll er dann zählen? 11? 12? Beide?

b) Willst Du im Endergebnis dann alle Details haben oder nur Summenbildungen (z.B. Stückzahl pro Produkt und Stunde oder so)? Bei letzterem würde ich das grundsätzlich anders angehen...
Möglicher Ansatz:

  • (Da UnixTimeStamp,) Umrechnung von Begin- und Enddate in zwei Werte, nämlich Datum (ohne Uhrzeit) und Stunde (inkl. Berücksichtigung von der unter a) genannten Problematik)
  • Im SELECT dann ein GROUP BY mit Datum, Stunde, [was sonst noch an Kriterien nötig ist, z.B. ProductId, etc.] und SUM() oder COUNT() Berechnungen

Das hat dann den Vorteil, dass Du nur einmal gegen die DB Abfragst und gleich alles aufbereitet serviert bekommst.

Bart Simpson

P.S. Ist es nicht eher üblich die Stunde von 12:00 bis 12:59 zu zählen und nicht von 12:01 bis 13:00?

18.02.2015 - 10:02 Uhr

Hi,

also die grundsätzliche Filterung sollte dann mit

Select * from Table
where BeginDate between [MinDatum] and [MaxDatum]
OR EndDate between [MinDatum] and [MaxDatum]

möglich sein.

Was ich immer noch nicht verstehe, ist "...Dort sollen Anrufe gezählt werden. Und das für jede Stunde des Tages. ...".

Meinst Du damit ein GROUP BY nach Stunde?

Bart Simpson

18.02.2015 - 09:25 Uhr

Nachdem t2t gerade auch geantwortet hat, muss ich dann doch nochmal nachhaken...

Was meinst Du mit dem hier eigentlich genau?

Nun möchte ich von einem bestimmten Tag alle Daten, stündlich herausfiltern.

Ich könnte mir da nämlich einige verschiedene Interpretationsmöglichkeiten vorstellen...

Bart Simpson

P.S. Nur am Rande: Warum wird eigentlich ein Datumswert als UnixTimeStamp codiert? Wären die integrierten Datentypen (Date, DateTime, DateTime2, etc.) nicht geeigneter? V.a. da dann auch gleich integrierte Funktionen zur Verfügung stehen.

18.02.2015 - 09:17 Uhr

Hi,

Ich denke mal, Du hast BETWEEN gründlich missverstanden...

Between ist nichts anderes als ein vereinfachtes AND (mit "eingeschlossenen Rändern"), also z.B.

SELECT * FROM Tabelle
WHERE Id BETWEEN 10 and 20

ist identisch zu

SELECT * FROM Tabelle
WHERE Id >=10 and  Id<=20

Was Du da machst ist sicher so nicht gewollt... ein "X Between X and Wert" liefert erst mal alle Datensätze, da die Spalte X immer größer oder gleich der Spalte X (also sich selbst) ist. Und der Bool'sche Ausdruck den Du nach dem AND bringst macht da mal überhaupt keinen Sinn...

Was Du meinst ist vermutlich

Select * from Table
where BeginDate between [MinDatum_Begin] and [MaxDatum_Begin]
and EndDate between [MinDatum_End] and [MaxDatum_End]

Wobei die Werte in eckigen Klammern typischerweise hart codierte Werte oder Werte aus Variablen sind. *)

Bart Simpson

*) Disclaimer: Natürlich gehen auch komplexere Ausdrücke, aber ich denke nicht, dass das hier gemeint ist...

03.02.2015 - 09:20 Uhr
  • Anstatt auf zwei Datenbanken gleichzeitig zuzugreifen, in der ersten Datenbank Procedures zum Zugriff auf die Daten der zweiten Datenbank erstellen, und dann im DbContext darauf mappen, so wie hier beschrieben:
    >
    . Das scheint mir aber nicht der Anforderung zu entsprechen, daß die Daten in getrennten DBs liegen sollen, auch wenn sie physisch in einer anderen DB liegen.

Warum sollte das denn den Anforderungen widersprechen? Ob der Zugriff nun über einen separaten DbContext erfolgt oder über ein und den selben und dann via SPs (oder Views oder was auch immer) auf die andere DB zugegriffen wird ist doch aus Sicht der Datenspeicherung völlig unerheblich. Und im Fall von (ich vermute mal) SQL Server ist es auch aus Sicherheitsaspekten irrelevant, da die zweite DB ihre eigenen Berechtigungen etc. haben kann. Und diese müssen - egal wie der Zugriff letztlich erfolgt - immer "erfüllt" sein, damit der User auch was tun kann.

Ich für meinen Teil habe die selbe Thematik schon vielfach eingesetzt (zumeist mit Views, da bei uns üblicherweise nur Lesezugriffe auf die zweite DB erfolgen müssen) und bin damit problemlos durch unseren internen und externen (=Kunden-) Datenschutz-Audits gekommen. Wichtig ist aus meiner Sicht nur, dass die Sicherheitsrelevanten Bereiche (z.B. Berechtigungen des einzelnen Users) quasi transparent weitergereicht werden, so dass sich durch den neuen Zugriffsweg keine Sicherheitslücken auftun - was gerade in Bezug auf Personaldaten o.ä. im Falle einer Lücke leicht zum Problem ausarten könnte...

Bart Simpson

P.S. Beim SQL-Server ist es durch diese Technik sogar Problemlos möglich nicht nur auf Daten in anderen DBs zuzugreifen, sondern es können sogar andere (physische) Server im Spiel sein (Stichwort: Linked Server).

07.11.2014 - 08:33 Uhr

Der einfachste Fall ist, dass Regel 2 gar nicht mehr zur Anwendung kommt (bzw. könnte gar nicht zur Anwendung kommen), wenn Regel 1 fehlschlägt. Und abhängig davon, was bei Regel 1 rauskommt, kann Regel 2 z.B. entfallen.

Das liese sich z.B. lösen, in dem Du eine Art "Kontext-Objekt" (vgl. z.B. HttpContext) bei der Validierung mit durchreichst (oder evtl. auch Stackartig jeweils ein neues erzeugst und weitergibst). Da packst Du dann alle nötigen Informationen aus dem "Verlauf" der Validierung hinein und stellst sie somit den nachgeschalteten Regeln zur Verfügung.

Wobei noch die Frage bleibt, wer die Anwendung einer bestimmten Regel eigentlich anstößt. Wird das irgendwie "von außen" getriggert (z.B. in dem eine Liste/Baum/etc. von Regeln durchlaufen wird) oder stößt eine Regel die nächste an (z.B. Parent-Child-Beziehung der Regeln).
Denn es muss ja bestimmt werden, ob eine Regel überhaupt ausgeführt werden soll. Das könnte z.B. die jeweilige Regel selbst bestimmen (und z.B. als Validierungsergebnis so was wie "NichtAngewendet" zurückgeben) oder die "vorhergehende" Regel um dann eben die Regel gar nicht erst anzustoßen oder es könnte auch durch eine wie auch immer geartete, außerhalb liegende, Entität (ich nenn's mal den "Regelausführer") gestan werden...

Bart Simpson

06.11.2014 - 11:44 Uhr

Hi,

was bedeutet denn das hier?

In meinem Fall sind die Regeln zur Validierung nicht unabhängig.

Ich kann mir, ohne weitere Hinweise, darunter nur sehr schwer was vorstellen...
Bedeutet das, dass z.B. Regel2 ein anderes Ergebnis liefern kann, wenn das Regel1 tut? Also Abhängigkeiten der Validierung von "vorhergehenden" Ergebnissen bestehen?

Bart Simpson

06.11.2014 - 10:52 Uhr

Hi,

just my 2 Cents...

Ich hab mich vor einiger Zeit mal mit einem ziemlich ähnlichem Problem herumgeschlagen. Validierung -> Ergebnis visualisieren und persistieren.
Die Lösung bei uns sah im Prinzip so aus:

Es gibt Klassen, die die eigentliche Validierung implementieren (musste hier erweiterbar sein, also n Regeln) und als Ergebnis einen Enum-Wert (Ok, Fehler, Warnung, etc.) zurückgeben. Die Regelklassen geben dynamisch die Strings für die entsprechenden Meldungen zurück (in diesem Fall aus Resourcen).
Das ermöglicht es, dass nur die Information "welcher Regeltyp" und "welches Ergebnis" gespeichert werden muss und zur Visualisierung (bei uns ist das den Compilermeldungsfenster in Visual Studio nachempfunden, inkl. Filter für die versch. Stufen) einfach der Regeltyp dynamisch nach seiner Meldung gefragt wird.
Das ist vielleicht nicht die performanteste Lösung, bringt aber einiges an Vorteilen mit sich:*Einfache Erweiterbarkeit (einfach weiteren Typ für neue Regel implementieren) - sogar über Plugin-System möglich *Lokalisierung ohne Magic Numbers, Enums für Resourcen-Zugrif etc. - jeder Regeltyp "kennt" seine Meldungen und kann sie z.B. aus Resourcen laden *Die Meldung an sich muss nicht gespeichert werden, sondern nur Regel- und der Ergebnis-Typ *Es lässt sich sehr einfach eine Klassenhierarchie für die Regeln aufbauen um Verhalten/Logik zu vererben

Bart Simpson

23.09.2014 - 18:08 Uhr

Wieso deaktivierst du es nicht einfach für diesen speziellen Fall?

..das ginge natürlich - aber ich wollt' ja wissen, was in dem Fall "der" Weg der Wahl ist... 😁

Bart Simpson

23.09.2014 - 16:50 Uhr

Ich grüble grade an einer Meldung, die mir FX Cop bei der Analyse eines Projekts ausspuckt - und ich frag mich echt, was in der Situation sinnvoll ist...

Grob vereinfacht geht's um einen WCF-Datacontract, der (im wesentlichen) zum Transfer von BLOB-Daten dienen soll und im Prinzip so aussieht:

    [DataContract]
    public class WcfSampleContractFile
    {
        [DataMember]
        public byte[] BlobData { get; set; }
    }

FX Cop mäkelt nun (zu Recht?) an, dass keine Arrays als Property verwendet werden sollten (siehe CA1819: Eigenschaften sollten keine Arrays zurückgeben).

Ein Vorschlag dazu wäre, das zu etwas in der Art umzuformulieren:

[DataContract]
    public class WcfSampleContractFile
    {
        private byte[] blobData = new byte[0];

        [DataMember]
        public ICollection<byte> BlobData
        {
            get { return this.blobData; }
            set { this.blobData = value.ToArray(); }
        }
    }

Das bringt nun aber wiederum

CA2227: Collection properties should be read only zum Vorschein - was aber in dem Kontext nicht gerade zielführend wäre, denn für die (De-) Serialisierung muss es ja grade eben schreibbar sein...

Wie denn nun? 🤔

Bart Simpson

05.09.2014 - 10:52 Uhr

Wenn Du am Server zeilenweise Werte berechnen willst musst Du das IMHO auch in der entsprechenden Zeile tun, z.B. mit irgendwas in der Form

SELECT a,b,c, (a+b+c)/3 as durchschnitt
FROM Tabelle

Das geht evtl. noch um einiges geschickter (z.B. mit einer Table-Valued-Function), aber die Grundidee ist dabei die selbe.

Bart Simpson