| 

.NET C# Java Javascript Exception

3
Hallo zusammen,

kann man LINQ to Entities nicht irgendwie dazu überreden, Methoden zu akzeptieren, die nur ein weiteres Query enthalten? Beispiel:

void Main()
{
var activeProducts1 = (from p in db.Products
where p.Active && p.Price > 100
select p).ToList();
var activeProducts2 = (from p in GetActiveProducts()
where p.Price > 100
select p).ToList();
var activeProducts3 = (from p in Products
join ap in GetActiveProducts() on p.ProductID equals ap.ProductID
where p.Price > 100 && ap.Price < 200
select p).ToList();
var activeProducts4 = (from p in db.Products
where p.Price > 100
select new {
Product = p,
ActiveProducts = GetActiveProducts()
}).ToList();
}

ObjectQuery<Product> GetActiveProducts()
{
return (from p in db.Products
where p.Active
select p) as ObjectQuery<Product>;
}


Die ersten beiden LINQ Statements resultieren im gleichen SQL Query. Mal abgesehen davon, dass das 3. und 4. Query wenig Sinn macht, scheitert der Aufruf des 4., weil LINQ to Entities den Methodenaufruf nicht übersetzen kann. Aber warum schafft es das beim 2. und sogar beim 3. Aufruf?

Und was bleibt als Alternativlösung? Alle Abfrageteile, die in etlichen anderen Abfragen vorkommen, via Copy/Paste übernehmen? Das kann nicht der Weisheit letzter Schluss sein.
News:
23.02.2011
Daniel Kuppitz 596 7
7 Antworten
3
Der Linq-to-Entites support des Entity Framework ist um Klassen schlechter als der von Linq-to-SQL (codekicker verwendet L2S. Ich habe nach mehrstündigen Migrationsversuchen auf das EF aufgegeben).

Probier mal folgendes:

void Main()
{
var prods = GetActiveProducts();
var activeProducts5 = (from p in prods
where p.Price > 100
select p).ToList();
}

Eventuell kann Linq-to-EF das. Falls nicht bleibt dir noch einen einen Linq-Provider zu schreiben, welcher die Abfrage transformiert, indem er die Variable/den Methodenaufruf inlined, und danach die normale Abfrage an das EF weiterreicht. Das ist sehr kompliziert ;-) Als Beispiel für einen QueryProvider: Für codekicker haben wir einen Linq-Provider, welcher alle Abfragen per CompiledQuery.Compile einmalig kompiliert und danach nur noch aus dem Cache entnimmt. Das hat einen ordentlichen Performancegewinn erbracht.
23.02.2011
Marvin Steppat 3,1k 4 8
2
Zu deiner ersten Frage, dem "Warum" gibt Microsoft folgende Erklärung:
Verweise auf nicht skalare Abschlüsse werden nicht unterstützt
Zu deiner zweiten Frage, der "Alternativlösung", suche im Internet nach den Begriffen extension methods lambda
Ich benötigte ein "string based order by" und realisierte es folgendermaßen:
namespace System.Linq
{
/// <summary>
/// LambdaExtenders
/// </summary>
public static class LambdaExtender
{
/// <summary>
/// string based orderby
/// </summary>
/// <typeparam name = "T"></typeparam>
/// <param name = "items"></param>
/// <param name = "property"></param>
/// <param name = "ascending"></param>
/// <returns></returns>
public static IOrderedEnumerable<T> MyOrderBy<T>(this IEnumerable<T> items, string property, bool ascending)
{
try
{
ParameterExpression myObject = Expression.Parameter(typeof (T), "MyObject");
ParameterExpression myEnumeratedObject = Expression.Parameter(typeof (IEnumerable<T>),
"MyEnumeratedObject");
MemberExpression myProperty = Expression.Property(myObject, property);
LambdaExpression myLamda = Expression.Lambda(myProperty, myObject);
MethodCallExpression myMethod = Expression.Call(typeof (Enumerable),
ascending ? "OrderBy" : "OrderByDescending",
new[] {typeof (T), myLamda.Body.Type},
myEnumeratedObject, myLamda);
Func<IEnumerable<T>, IOrderedEnumerable<T>> mySortedLamda =
Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(myMethod, myEnumeratedObject).Compile();
return mySortedLamda(items);
}
catch (Exception ex)
{
throw new Exception(string.Concat("MyOrderBy exception\n", ex.Message), ex.InnerException);
}
}
}
}


Der Aufruf kann folgendermaßen aussehen:
var dataGridPageSizes = from p in entities.DataGridPageSize select p;
dataGridPageSizes = dataGridPageSizes.MyOrderBy("PageSize", true).AsQueryable();


Eventuell kommst du damit weiter. Versprechen kann ich es nicht.
23.02.2011
Jürgen Luhr 7,1k 1 9
1
Hallo,

eventuell ist Using the LINQ Dynamic Query Library hilfreich für dich auch wenn sie nicht alle LINQ Extensions anbietet, so ist sie doch auch mit LINQ to Entities verwendbar.

Ein aktuellerer blog Eintrag für C# 4.0 der sich auf obigen bezieht.

Grüße
23.02.2011
Maria Simlinger 1,1k 9
1
An Expressions habe ich auch gedacht, nur macht diese Herangehensweise den Code wieder unglaublich kompliziert, wenn man damit ganze Unterabfragen zusammenstückeln will. Für die dynamischen OrderBy-Klauseln hab ich die LINQ Dynamic Query Library integriert. Und genau diese Abfragen haben mich bisher davon abgehalten, alles in T-SQL zu schreiben. Ein Teufelskreis...
23.02.2011
Daniel Kuppitz 596 7
Ja, und dein zweites Problem löst es leider auch nicht.
Maria Simlinger 23.02.2011
1
Hallo Daniel,

hast Du Dir schon einmal Codesmith angeschaut?
http://www.codesmithtools.com/

Und dort Plinqo im speziellen? Dies gibt es einmal als replacement für das Entity Framework von MS und auch als replacement von LINQ to SQL. Ich habe damit extrem gute Erfahrung gemacht!
Das Tool Codesmith muss zwar erworben werden, doch das Framework Plinqo ist kostenlos, hat eine sehr gute Comunity und auf bugs wird schnell reagiert.
Bei Plinqo wird jede Tabelle als eigenständige Entity generiert. Es gibt query extensions, die Du beliebig erweitern kannst, sozusagen Entity auf Knopfdruck.
Du kannst Dir das ja mal in der Trial anschaun. Ich kann gar nicht mehr ohne, da es abgesehen vom Plinqo Framework, einem viel Tipparbeit abnimmt, wenn man sich einmal Templates gebastelt hat.

Folgendes geht mit Plinqo z.b:

Dim IMyTableQueryAble = PlinqoDatacontext.TableName.GetByStartDat(Date)
IMyTableQueryAble = IMyTableQueryAble.GetBySomething(CodeSmith.Data.Linq.ComparisonOperator.GreaterThanOrEquals, 15)
IMyTableQueryAble = IMyTableQueryAble.GetBySomethingMore(CodeSmith.Data.Linq.ComparisonOperator.LessThanOrEquals, 30)
Dim Test = from Items in IMyTableQueryAble Select ....
16.03.2011
Athu 43 4
0
Na immerhin hat sich die NotSupportedException in eine ArgumentException verwandelt ;-).

Ich hab es jetzt mal mit LINQ to SQL probiert. Der entscheidende Vorteil ist, dass alle Abfragen problemlos ausgeführt werden. Allerdings - und das hab ich befürchtet - wird die innere Abfrage (GetActiveProducts) x Mal ausgeführt, wobei x der Anzahl der Datensätze der äußeren Abfrage entspricht. Während der Entwicklung mag das noch OK sein, aber produktiv möchte ich sowas nicht einsetzen.

Allmählich tendiere ich dazu, das EF nur noch für die Modellierung zu nutzen und alles was Abfragen betrifft in echten SQL Funktionen und/oder Stored Procedures unterzubringen.
Der eigene LINQ Provider wäre natürlich auch noch eine Alternative, wäre aber zu fehleranfällig und würde viel zu lange dauern und den Projektabschluss unnötig nach hinten verschieben.
23.02.2011
Daniel Kuppitz 596 7
So lange wie du denkst dauert das nicht. Du musst nur IQueryable.Execute wie folgt implementieren: Expression mit dem ExpressionVisitor nach InvocationExpression's durchgehen, die Methoden per Reflection ausführen und den Rückgabewert inlinen. Danach die Expression ans EF weiterreichen.
Marvin Steppat 23.02.2011
Mag sein, dass es nicht wirklich viel ist (wenn man weiss was man tut), aber ich würde da ganz jungfräulich rangehen. In einem laufenden Projekt ist mir das zu heiß, für die privaten Spielereien aber mal eine Überlegung wert.
Daniel Kuppitz 23.02.2011
Da hast du sicher recht. Ich habe es auch nicht gemacht, weil es nötig war, sondern weil es mir interessant schien ;-)
Marvin Steppat 23.02.2011
0
40 Stunden und 2 blutrote Augen später sind alle LINQ Queries in T-SQL Table Valued Functions und Stored Procedures umgeschrieben. Die wiederum sind mit ein paar Klicks ins EF übernommen. Um dynamische SQL Anweisungen für die Sortierung bin ich mit ein paar CASE/WHEN Anweisungen auch drumrum gekommen.

Der Performancezuwachs ist nun natürlich enorm, aber was noch viel besser ist: Der Code ist an etlichen Stellen viel übersichtlicher geworden. Auch wenn LINQ meiner Meinung nach sehr gut zu lesen ist, so wird es bei größeren Abfragen doch sehr schnell übersichtlich. Wenn das Projekt wächst und eine Abhängigkeit nach der anderen dazu kommt, stellt man schnell fest, dass sich die Länge einiger Methoden durch die LINQ Queries bald jenseits von Gut und Böse befindet.

Nichtsdestotrotz ist LINQ an sich eine super Sache, ich bin nur mittlerweile nicht mehr davon überzeugt, dass es auch für komplexe datenbanklastige Anwendungen geeignet ist. Wer seine Abfragen von Anfang an auf der SQL Ebene belässt, fährt damit langfristig sicher besser.
25.02.2011
Daniel Kuppitz 596 7

Stelle deine .net-Frage jetzt!