Autor Beitrag
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: So 06.10.13 22:38 
Abend,


ich hab mir vor einer Weile vor genommen, nach und nach einen Sql-Builder zu schreiben.
Einen, der auch möglichst viel der unterstützt. Außerdem möchte ich das nach dem Prinzip der LINQ-Erweiterungs-Methoden aufbauen, also, dass jede Methode sich die aktuelle Instanz zurück gibt.

Wer sich aber nun zumindest ein bisschen mit SQL aus kennt, weiß, dass das nicht mal eben getan ist, sondern auch viele Fallen bereit halten kann.

Also sitze ich seit dem daran, das Projekt sinnvoll zu planen, um später weitere Funktionen von SQL möglichst reibungslos hinzu fügen zu können.

Allerdings habe ich da gleich ein ziemlich nerviges Problem: Umfang


Beim Erstellen des Interfaces für einen Select-Builder ist mir ziemlich schnell aufgefallen, dass das Interface alleine ganz schnell viel zu groß wird. Logische Konsequenz: Einzelne Teil-Funktionen getrennt voneinander implementieren.
Also hab ich mir überlegt, für die Teile einer jeden Select-Abfrage die Teilbereiche Select, From, Where, etc. zu implementieren. So kann ich die Funktionen relativ gut auf mehrere Klassen auf teilen und Where kann ich z.B. wieder verwenden.

Da ist dann aber auch schon der Kern meines Problems erreicht: Wenn ich die Funktionen auf teile, können diese nicht mehr die aktuelle Instanz des SelectBuilders zurück geben, da diese den nicht kennen. Und wenn ich eine Klasse schreibe, die die ganzen Sql-Funktionen bereit stellt und im Endeffekt nur an die einzelnen Part-Builder durch leitet, allerdings die eigene Instanz zurück gibt, ist das auch nicht gerade schön. Außerdem habe ich dann immer noch eine ganze Menge Methoden, auch wenn die konkrete Implementierung nicht mehr dort liegt.


Hat jemand eine Idee, wie ich das möglichst elegant lösen kann?


Mir ist dabei wichtig, dass ich das Ganze wie die LINQ-Erweiterungsmethoden nutzen kann, denn so kann der Nutzer dann das Sql-Statement komfortabel nutzen und ich habe die Möglichkeit, zusätzlich Erweiterungsmethoden zu schreiben, welche dann wiederum die Definitionen der LINQ-Methoden besitzen. So kann der Builder dann auch mit der LINQ-Syntax genutzt werden, was meiner Meinung nach nochmal komfortabler ist und das bauen von SQL-Commands sehr einfach und vor allem elegant gestaltet.

Ich hoffe, ihr habt ungefähr verstanden, was ich meine. :D



Gruß
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4798
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 07.10.13 09:15 
Hallo Palladin007,

sorry, leider habe ich nicht wirklich verstanden, was du machen willst.
Und bei
Palladin007 hat folgendes geschrieben:
Einen, der auch möglichst viel der unterstützt.
fehlt wohl ein wichtiges Wort, oder?

Du schreibst ja schon etwas von LINQ, also warum benutzt du nicht gleich LINQ.

Oder geht es dir darum einen IQueryProvider zu implementieren, so daß eine externe Datenquelle mit LINQ angesprochen werden kann? Dann schau mal in LINQ: Building an IQueryable provider series ff.

Vllt. verstehe ich mehr, wenn du etwas Code posten könntest, wie der Zugriff dann aussehen soll.
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 07.10.13 11:31 
Es geht mir nur darum, das Statement als String zu erhalten, also liegst du mit IQueryProvider schon ganz richtig.
Das Command-Objeckt erstellen oder abschicken, kann der Nutzer dann selber machen. Oder ich überlege mir etwas Anderes.


Ein Beispiel, wie ich mir den Aufruf erhoffe:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
var sqlBuilder = SqlBuilderFactory.CreateForSql();
var selectBuilder = sqlBuilder.GetSelectBuilder();

selectBuilder.From("Articles")
             .Join("Attributes").On("ArticleId"// Oder wenn es zwei verschiedene Spalten, zwei Parameter
             .Where("AnyNumber").IsEqual("1234"// Hier bin ich noch am Überlegen, wie ich das genauer mache
             .Select("ArticleNo");

var selectQuery = selectBuilder.ToString();

// ...


Und da der Compiler für die LINQ-Syntax nur entsprechend Erweiterungsmethoden aus tauscht, wollte ich das ganze auch für diese Syntax ermöglichen.
Aber das kann warten, bis der Builder an sich funktioniert.


Zitat:
Einen, der auch möglichst viel der Sql-Funktionen unterstützt.


Das hatte ich vergessen. ^^


Den Link werde ich mir mal anschauen, sobald ich Zeit finde, hatte gerade nur ein paar Minuten, um hier zu antworten. ^^



Gruß
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 07.10.13 11:46 
Zitat:
Es geht mir nur darum, das Statement als String zu erhalten,


Ähm, EF nehmen und am Context ToTraceString() aufrufen? Du hast irgendwie noch nicht erklärt warum du die vorhandenen Frameworks Linq2Sql und Entity Framework nicht benutzen möchtest.
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 07.10.13 12:13 
Weil ich das selber schreiben möchte und weil der Builder für sich und alleine, ohne irgendein Datenverarbeitungs-Framework funktionieren soll.
Ich möchte nur den Kommando-Text haben.


AUf die Idee gebracht hat mich ein Kollege, der das hier implementiert hat, meiner Meinung nach aber viel zu klein und gerade so weit, wie es gebraucht wird. Wenn neue Funktionen benötigt werden, müssen wir erst fragen, warten und solange simpel den String schreiben, was ich ziemlich hässlich finde.


Mein Ziel ist es also, so einen Builder zu schreiben, um einmal selber fitter in SQL zu werden, meinen Spaß bei der Programmier-Arbeit zu haben und das Ganze dann auch auf Arbeit anzubieten.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4708
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 07.10.13 12:23 
Ich glaube dein Kollege macht das eigentlich richtig ;)

Ab den ersten 10% SQL (ok das sind die die aber zu 90% gebraucht werden) wird das ganze Plattformabhängig du must dich also entscheiden welche Plattform du unterstützen willst.
Viele SQL Konstrukte sehe ich als nicht darstellbar in einer Fluent API. SQL ist nun mal deklarativ und nicht imperativ. Nicht umsonst sind viele Dinge in LINQ/EF zumindest umsortiert oder sehr merkwürdig umgesetzt wenn man sich mal das SQL dazu ansieht. Entweder simpler LINQ Ausdruck oder simples SQL. Beides gleichzeitig eher nicht möglich. Wenn du mit SQL anfängst um dazu einen passenden LINQ artigen Syntax zu erfinden wirds hart. Ich vermute mal die LINQ Leute haben das irgendwann gemerkt und haben es anders herum gemacht.

Aber wenn du sowas hinbekommst wie eine rekursive CTE oder ein "over Partition" mit deinem "Teile und Hersche"-Ansatz über eine Fluent API dann bist du mein Held :flehan: :flehan:
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4798
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 07.10.13 12:32 
Hallo,

ohne die dahinterstehende Datenbank zu kennen, wird es aber nur ein rudimentäres SQL werden, denn SQL ist quasi nur ein Pseudo-Standard und jeder DB-Provider hat spezielle SQL-Syntax (beispielsweise für Joins, Escaping, ...).

Wenn dir Linq2SQL zu speziell und EF zu groß ist, dann kannst du auch mal nach Micro-ORMs suchen, z.B. FluentData, Dapper, Massive oder PetaPoco.
Unter .NET Micro-ORMs - Dapper, PetaPoco, and more gibt es einen Vergleich verschiedener ORMs.

Ich verstehe deinen Anreiz, aber diese Aufgabe sind andere eben auch schon angegangen ;-)
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 07.10.13 13:11 
So gesehen gibt es eigentlich schon alles und ich hätte gar nichts, was ich in der Freizeit programmieren könnte. ^^
Mir geht es da halt auch darum, meinen persönlichen Spaß daran zu haben. Außerdem ist es ein gutes Training. ^^
Auch erhoffe ich mir davon, mich im Projekt-Planen selber zu üben, da das noch meine größte Schwäche ist. :D


Die LINQ-Syntax will ich vorerst auch gar nicht einbauen, die kann ich nachträglich immer noch erweitern. Die bringt mir auch erst etwas, wenn der Builder funktioniert. ^^


Was den Standard angeht, möchte ich das sowieso so planen, dass der Nutzer eigentlich nur die Interfaces sieht. Er ruft dann das entsprechende Objekt über eine Factory ab, wo dann die verschiedenen Standards implementiert sind. Beginnen werde ich mit Transact SQL.

Aber auch bei dieser Herangehensweise stellt sich immer noch die Frage: Wie plane ich das Projekt sinnvoll, dass die einzelnen Klassen nicht zu voll werden und das Ganze auch flexibel erweiterbar bleibt?


@Ralf Jansen:

Ja, SQL beinhaltet riesige Mengen und das bisschen, was wirklich genutzt wird, ist vergleichsweise wenig, aber alleine, wenn ich eine Klasse für Select-Abfragen mit entsprechenden Methoden und Überladungen für Select, From, Join, Where und Funktionen, wie Pivot schreibe, wird das groß und unübersichtlich. Dann kommen noch die anderen Dinge hinzu, wie z.B. Insert. Manche Methoden (z.B. And) können nur nach anderen Methoden aufgerufen werden. Bei And ist es aber nicht nur Where, nachdem es aufgerufen wird, sondern auch Join. Oder As. Dann gibt es noch Sub-Selects, Punkt, Punkt, Punkt...


Für den Umfang von SQL ist das vermutlich wenig, aber so zusammen gefasst ist das viel zu viel für eine Klasse.
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Do 10.10.13 12:13 
Mir kam da jetzt gerade eine Idee, wie ich das machen könnte und der Umfang dennoch halbwegs klein bleiben würde:

Und zwar schreibe ich für jede Funktion, die ich einbauen möchte, nur exakt die Funktionen, die technisch nötig sind. Und diese Funktionen haben auch genau die Parameter, die ich haben möchte. Und alle Überladungen, die im Endeffekt nur dazu da sind, dass es leichter zu bedienen ist, die werden dann durch Erweiterungsmethoden hinzu gefügt. Diese Methoden tun dann eigentlich nichts anderes, als die Daten, die benötigt werden, in verschiedneer Form anzufragen und der Builder-Klasse so zu übergeben, wie sie diese braucht.
Der Nutzer kann die Builder Klasse natürlich auch ohne Erweiterungen nutzen, allerdings müsste er dann z.B. Tabellen-Namen über eine entsprechende Klasse angeben, die ihrerseits dazu dafür zuständig ist, den Namen richtig zusammen zu bauen und zu prüfen, dass z.B. keine Sql-Injection möglich wird.

Die Erweiterungsmethoden kann ich dann auch alle in mehrere Klassen aufteilen, sodass für jeden Teilbereich des Select-Builders (Select, From, Join, Where, etc.) immer eine Klasse ein Haufen Überladungsmethoden bereit stellt, die das Nutzen dann komfortabel gestalten.

Auch die Interfaces werden dadruch kleiner, denn wenn jemand einen anderen Sql-Standard hinzu fügen möchte, kann er ja einfach die "umständliche Art" nutzen, die Erweiterungsmethoden funktionieren aber trotzdem, da sie die Eingaben nur mit Hilfe der vom Standard vor gegebenen Klassen Klassen validieren und an die SelectBuilder-Klasse weiter reichen.


Edit:

Ich hab es jetzt mal testweise ein bisschen was so aufgebaut, wie es dann ungefähr aussehen würde und ich denke, das passt soweit.
Dann kann ich mir eine Bibliothek erstellen, die die ganzen Interfaces beinhaltet und auch die Erweiterungsmethoden, die eigentlich nur mit den Interfaces arbeiten und gar nicht mehr brauchen, da sie aus dem Objekt, eine Factory bekommen, welche dann die nötigen Klassen für den jeweiligen Standard bereit stellt.

Das einzige, was ich mir noch überlegen muss, ist, wie ich es hin kriege, dass ein Sql-Standard, der nachträglich in einer extra Bibliothek hinzu gefügt wurde, auch erkannt wird.