Laden...

LINQ-Syntax "überschreiben"

Erstellt von Palladin007 vor 10 Jahren Letzter Beitrag vor 10 Jahren 5.300 Views
Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren
LINQ-Syntax "überschreiben"

Hi,

ist es möglich, die LINQ-Syntax für andere Zwecke zu nutzen, als die, für die sie vorgesehen ist?

Mein Gedanke war, dass man doch einen SQL-Statement-Builder schreiben und dann mit der LINQ-Sytnax nutzen könnte, da LINQ ja deutlich einfacheren SQL-STatements ähnelt.

Als Beispiel:

SQL:

SELECT ArticleId, ArticleNo
FROM Articles
WHERE ArticleNo < '5000'
ORDER BY ArticleNo;

LINQ in c#:

string sqlString =  select "ArticleId", "ArticleNo"
					from "Articles"
					where "ArticleNo = 5000"
					orderby "ArticleNo";

Das würde die Arbeit mit SQL-Statements in c# deutlich vereinfachen, ich weiß aber nicht, ob das überhaupt möglich ist.

Gruß

1.002 Beiträge seit 2007
vor 10 Jahren

Hallo Palladin007,

die Syntax selbst kannst du nicht verändern, also keine eigene domain specific language erschaffen.

Mit LINQ to SQL und dem Entity Framework wird allerdings genau das, was du erreichen möchtest, bereits umgesetzt: das Generieren von SQL-Statements aus LINQ, u.a. aus der query comprehension syntax (so heißt sie offiziell).

m0rius

Mein Blog: blog.mariusschulz.com
Hochwertige Malerarbeiten in Magdeburg und Umgebung: M'Decor, Ihr Maler für Magdeburg

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo Palladin007,

ergänzend zu m0rius:

Das geht nicht so einfach, denn diese Linq-Syntax wird vom Compiler in die entsprechenden Erweiterungs-Methoden übersetzt. Z.B. select -> Enumerable.Select.

Möglich wäre es somit nur, wenn du den Compiler erweiterts/tauscht/etc. Vllt. gibt es hier mit Roselyn eine Möglichkeit einzuhaken.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

C
2.121 Beiträge seit 2010
vor 10 Jahren

Und ich muss noch ne Frage loswerden.
Was wäre an dem gezeigten Beispiel einfacher? Wenn auch nur ein Statement dabei rauskommt, ist da doch nichts gewonnen?

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Komplexere Statements würden dadurch einfacher werden, das war nur ein möglichst einfaches Beispiel zum Zeigen.

Die Idee kam mir auf Arbeit, wo ein Kollege einen SelectBuilder geschrieben habe um das ganze zu vereinfachen. Das ist auch deutlich einfacher geworden, allerdings sieht die Verwendung fast genauso aus, wie wenn ich LINQ über die Methoden nutze.
Wenn ich die zusätzliche LINQ-Syntax einbauen könnte, würde sich der Code aber noch weiter verkürzen und vereinfachen.

1.002 Beiträge seit 2007
vor 10 Jahren

Hallo Palladin007,

wie gesagt: Das Entity Framework (oder LINQ to SQL) ist dein Freund, was die Generierung von SQL-Statements angeht. Dann musst du dich auch nicht darum kümmern, dass diese korrekt gegen SQL-Injection geschützt werden.

m0rius

Mein Blog: blog.mariusschulz.com
Hochwertige Malerarbeiten in Magdeburg und Umgebung: M'Decor, Ihr Maler für Magdeburg

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Das muss ich mir bei Zeiten mal anschauen.

Das Entity Framework geht nur nicht, sagt der Kollege, der den SelectBuilder geschrieben hat, warum hab ich nicht wirklich verstanden, bin ja auch erst in der Ausbildung 😄

Aber dass es das so schon gibt, ist gut zu wissen, da ich das bei einem eigenen Projekt (wenn ich dann mal dazu komme) auch brauche.

2.891 Beiträge seit 2004
vor 10 Jahren

Das geht nicht so einfach, denn diese Linq-Syntax wird vom Compiler in die entsprechenden Erweiterungs-Methoden übersetzt. Z.B. select -> Enumerable.Select.

So ganz ist das nicht korrekt. Der Compiler setzt nicht die entsprechenden Erweiterungsmethoden ein. Stattdessen wird einfach ganz stupide eine Texttransformation durchgeführt. Dem Compiler ist dort herzlich egal, ob die Quelle ein IEnumerable, IQueryable oder sonstwas ist; ebensowenig müssen Where/Select/... Erweiterungsmethoden sein.

Beispiel:


var res = from p in src
          where p.Price > 100m
          group p by p.Category;

wird einfach zu


var res = src
          .Where(p => p.Price > 100m)
          .GroupBy(p => p.Category);

Dem Compiler ist hierbei egal, was src für einen Datentypen hat. Ebenso können Where und/oder GroupBy auch Instanzmethoden sein. Die einzige Anforderung ist, dass sich der transformierte Code kompilieren lässt.
Am besten mal nach "Bart de Smet, LINQ to Everything" googlen.

Dadurch kann man z.B. folgende Sachen machen:


Foo foo = from a in new Foo("Articles")
          where "ArticleNo = 5000"
          orderby "ArticleNo"
          select "ArticleId,ArticleNo";

string sql = foo.ToString(); // ergibt "SELECT ArticleId,ArticleNo FROM Articles WHERE ArticleNo = 5000 ORDER BY ArticleNo"

Das ganze wird folgendermaßen umgesetzt:


public class Foo
{
	private string from;
	private string where;
	private string orderBy;
	private string select;

	public Foo(string tableName)
	{
		this.from = tableName;
	}

	public Foo Where(Func<Foo,string> bar)
	{
		this.where = bar(this);
		return this;
	}

	public Foo OrderBy(Func<Foo,string> bar)
	{
		this.orderBy = bar(this);
		return this;
	}

	public Foo Select(Func<Foo,string> bar)
	{
		this.select = bar(this);
		return this;
	}

	public override string ToString()
	{
		return "SELECT "+this.select+" FROM "+this.from+" WHERE "+this.where+" ORDER BY "+this.orderBy;
	}
}

Wie man sieht, basiert das weder auf IEnumerable/IQueryable/etc. noch müssen Where/Select/etc. Erweiterungsmethoden sein.

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Hey, geil, genau das hab ich gesucht, danke 😄

Hätte nicht gedacht, dass das so einfach ist, aber das ist ja fast schon kinder leicht 😄

2.891 Beiträge seit 2004
vor 10 Jahren

Hätte nicht gedacht, dass das so einfach ist, aber das ist ja fast schon kinder leicht 😄

Naja, das ist ein Minimalbeispiel - hier wird ja nur naiv Text verkettet. Aber gerade bei SQL musst du dir deine Konstrukte gut durchdenken, sobald es um Parameter geht (und das wird es schnell).

Vielleicht ist das Thema "Micro-ORM" (z.B. Dapper, PetaPoco, SqlFu, ...) für dich interessant. Das ist nicht ganz so abstrakt wie z.B. LINQ2SQL oder das EF, kann aber auch helfen, leichter Code zu schreiben.

5.657 Beiträge seit 2006
vor 10 Jahren

Hi Palladin007,

Das Entity Framework geht nur nicht, sagt der Kollege, der den SelectBuilder geschrieben hat, warum hab ich nicht wirklich verstanden, bin ja auch erst in der Ausbildung 😄

Und ich dachte immer, die Ausbildung sei dazu da, etwas zu lernen...
Aber was heißt denn "das Entity Framework geht nur nicht"? Heißt es, "das EntityFrameWork geht nicht" oder heißt es "das EntityFramework entspricht nicht deinen Anforderungen"? Deine ursprüngliche Frage war, ob man mit Linq SQL-Anweisungen zusammenstellen kann, und die Antwort lautet: Genau das macht Linq To SQL bereits. Wenn das deinen Anforderungen nicht entspricht, kannst du dir den Quellcode anschauen und es genauso oder so ähnlich selbst umsetzen, bis es deinen Anforderungen entspricht. Wenn du selbst nicht sagen kannst, warum es deinen Anforderungen nicht entspricht, kann dir sowieso niemand weiterhelfen.

Christian

Weeks of programming can save you hours of planning

6.911 Beiträge seit 2009
vor 10 Jahren

Hallo dN!3L,

danke für die Klarstellung. Bisher wusste ich noch gar nicht, dass es hier bei LINQ rein nach Pattern (Mapping Keyword -> passende Methode) geht. Coole Sache.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

@dN!3L:

Na klar wird das noch deutlich umfangreicher. Was ich mit einfach meine, ist das Prinzip, wie man die LINQ-Schlüsselwörter für seine eigenen Zwecke nutzen kann - ohne IEnumerable.

@MrSparkle:

Warum es nicht geht/den Anforderungen nicht entspricht, kann ich nochmal nach fragen, ich wollte da einfach nicht weiter nach hacken, da er eigentlich sehr beschäftigt war und ich auch was anderes zu tun hatte 😄

Natürlich ist die Ausbildung dazu da, etwas zu lernen, allerdings bin ich erst seit etwas mehr als ein Monat dort. Außerdem stehen die gerade vor dem Ende eines Sprints (da muss dann ein Feature in der Software fertig und vorstellbar sein), da gibt es immer ein bisschen Stress, weil hier und da noch Bugs entdeckt werden, der Termin aber schon im Nacken hängt.

Ich hab deshalb auch die Aufgabe bekommen, einen Fetcher zu schreiben, dass die Kollegen sich mit anderen Tasks beschäftigen können und meinen Fetcher nur noch reviewn müssen.
Ich benutze da halt auch diesen SQLSelectBuilder und bin der Meinung, dass die LINQ-Syntax optisch angenehmer wäre. Natürlich ist das Meinungssache, aber die Möglichkeit, da LINQ zu nutzen, ist meiner Meinung nach ein kleiner Zusatz, der nicht allzu viel Aufwand kostet, aber die Arbeit erleichtert.

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Hi,

ich bastel grad fleißig rum und wühle immer tiefer im LINQ.

Dabei kam mir aber eine Idee. Und zwar ist es im Moment ja irgendwie unpraktisch, wenn ich bei from eine Bereichsvariable definieren soll, die ich gar nicht brauche.

Ich hatte daher gehofft, ob es vielleicht irgendwie möglich wäre, linq wie folgt zu nutzen:

IDbConnection connection = new SqlConnection(connectionString);
string tableName = "Persons";
string[] resultColumns = new string[] { "Name", "Age" };

ISelectBuilder sqlQuerry = from tableName in connection
                           where "Age > 18" 
                           select resultColumns;

IDataReader reader = sqlQuerry.ExcecuteReader();

Sowas in der Richtung, das wäre bei der Nutzung halt meiner Meinung nach sehr intuitiv in der Nutzung.

Dann könnte der Nutzer es sich auch sparen, ein Objekt zu erstellen, dass er dann hinter in nutzt, denn so würde intern das SqlBuilder-Objekt erstellt, womit dann weiter gearbeitet werden kann.
Leider ist der from-Part von LINQ schreinbar nichts, wo der Compiler einfach nur mit einer Methode austauscht, wie bei den anderen Schlüsselwörtern, zumindest habe ich nichts gefunden.

Hat da vielleicht jemand die zündende Idee, wie ich das umgehen könnte, oder muss ich da bei dem bleiben, was dN!3L geschrieben hat:

from a in new Foo("Articles")

oder auf das Beispiel von oben angewandt:

IDbConnection connection = new SqlConnection(connectionString);
string tableName = "Persons";
ISelectBuilder sqlBuilder = new ISelectBuilder(connection, tableName);
string[] resultColumns = new string[] { "Name", "Age" };

ISelectBuilder sqlQuerry = from x in sqlBuilder 
                           where "Age > 18" 
                           select resultColumns;

IDataReader reader = sqlQuerry.ExcecuteReader();
2.891 Beiträge seit 2004
vor 10 Jahren

Naja, ob das schön ist, weiß ich nicht, aber: Man deklariert vor dem from einen Variablennamen. Wenn du in den Select/Where/OrderBy...-Methoden Expressions verwendest, kannst du diesen Namen auch auslesen. Der Wert ist dann aber wirklich (nur) der Name - dort irgendwas als Parameter übergeben geht nicht.

Das würde dann so aussehen:


IDbConnection connection = new SqlConnection(connectionString);
Foo foo = from Persons in connection.AsFoo()
          where "Age > 18"
          select "Name, Age";

string sql = foo.ToString(); // ergibt "SELECT Name, Age FROM Persons WHERE Age > 18"

Code dahinter:


public static class DbCommandExtensions
{
	public static Foo AsFoo(this IDbConnection dbConnection) { return new Foo(); }
}

public class Foo
{
	private string from;
	private string where;
	private string select;

	public Foo Where(Expression<Func<Foo,string>> expression)
	{
		this.where = this.Handle(expression);
		return this;
	}

	public Foo Select(Expression<Func<Foo,string>> expression)
	{
		this.select = this.Handle(expression);
		return this;
	}

	private string Handle(Expression<Func<Foo,string>> expression)
	{
		this.from = ((LambdaExpression)expression).Parameters[0].Name;
		return expression.Compile()(this);
	}

	public override string ToString()
	{
		return "SELECT "+this.select+" FROM "+this.from+" WHERE "+this.where;
	}		
}

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Expressions - wieder was Neues 😄

Habe ich das richtig verstanden, dass die from-Klausel mit dem in dahinter nur eine rein deklarative Bedeutung haben, also nur zum deklarieren der Variable dienen und selber auf Code-Seite (Also die Erweiterungsmethoden) keine Bedeutung haben?
Dann kann ich zumindest mit Suchen aufhören. 😄

Aber auf jeden Fall danke für die Antwort, ich denke, das werde ich einbauen.
Ob das dann genutzt wird, kann ich ja frei wählbar lassen, aber ich finde es so zumindest schon einmal schönes lesbar, als vorher.
Ein sonderlich großer Mehr-Aufwand ist das ja nicht, ich muss es nur vorher alles darauf aus legen.

2.891 Beiträge seit 2004
vor 10 Jahren

Habe ich das richtig verstanden, dass die from-Klausel mit dem in dahinter nur eine rein deklarative Bedeutung haben, also nur zum deklarieren der Variable dienen und selber auf Code-Seite (Also die Erweiterungsmethoden) keine Bedeutung haben?

Jein. Ja, es dient nur der Deklaration der Variablen; aber für die Erweiterungsmethoden wird halt genau dieser Name für den Lambda-Parameter benutzt - von daher ist das schon irgendwie von Bedeutung (wobei ein Variablenname aber in der Regel für die Programmausführung nicht von Belang ist).

Palladin007 Themenstarter:in
2.078 Beiträge seit 2012
vor 10 Jahren

Klingt irgendwie nach Reflection?

Kann ich davon ausgehen, dass das auch so unperformant ist, wie Reflection, denn dann würde ich eine kleine Sicherung einbauen, dass der Name von der Instanziirung bis zum Aufruf der Select-Methode nur einmal abgefragt wird, denn Select darf in dieser Select-Querry ja eh nur einmal aufgerufen werden.

2.891 Beiträge seit 2004
vor 10 Jahren

Kann ich davon ausgehen, dass das auch so unperformant ist, wie Reflection[...]

Es gibt übrigens einen Unterschied zwischen langsam und nicht schnell genug.
Die schlechte Performance von Reflection wird erst für den Benutzer bemerkbar, wenn du eine Aktion wirklich oft durchführst (Vergleichszahlen siehe z.B. in DynamicFieldAccessor statt Reflection - schneller dynamischer Zugriff auf Felder/Properties - ab ca. 500.000 Wiederholungen dauert es länger als eine Sekunde). Und bei deinem Anwendungsfall dürfte die Ausführung des SQL-Befehls um Größenordnungen länger dauern, als das Zusammenbasteln des SQL-Befehls.