2 Stimmen

Wie verwandelt man diese Func in einen Ausdruck?

Ich spiele mit den Ausdrucksbäumen herum und versuche, ihre Funktionsweise besser zu verstehen. Ich habe ein paar Beispielcodes geschrieben, mit denen ich arbeite und hoffe, dass mir jemand helfen kann.

Ich habe also diese etwas chaotische Anfrage:

/// <summary>
/// Retrieves the total number of messages for the user.
/// </summary>
/// <param name="username">The name of the user.</param>
/// <param name="sent">True if retrieving the number of messages sent.</param>
/// <returns>The total number of messages.</returns>
public int GetMessageCountBy_Username(string username, bool sent)
{
    var query = _dataContext.Messages
        .Where(x => (sent ? x.Sender.ToLower() : x.Recipient.ToLower()) == username.ToLower())
        .Count();
    return query;
}

_dataContext ist der Datenkontext des Entity Frameworks. Diese Abfrage funktioniert wunderbar, aber sie ist nicht einfach zu lesen. Ich habe beschlossen, die Inline-IF-Anweisung in eine Func wie diese:

public int GetMessageCountBy_Username(string username, bool sent)
{
    Func<Message, string> userSelector = x => sent ? x.Sender : x.Recipient;
    var query = _dataContext.Messages
        .Where(x => userSelector(x).ToLower() == username.ToLower())
        .Count();
    return query;
}

Das scheint gut zu funktionieren, aber es gibt ein Problem. Denn die Abfrage ist gegen IQueryable<T> Dieser LINQ-Ausdruck wird in SQL übersetzt, um in der Datenquelle ausgeführt zu werden. Das ist großartig, aber deshalb weiß es nicht, was es mit dem Aufruf von userSelector(x) und löst eine Ausnahme aus. Es kann diesen Delegaten nicht in einen Ausdruck übersetzen.

Nun, da ich verstehe, warum es nicht funktioniert, möchte ich versuchen, es zum Laufen zu bringen. Es ist viel mehr Arbeit für das, was ich brauche, aber ich tue es nur aus reinem Interesse. Wie könnte ich das drehen Func in einen Ausdruck, der in SQL übersetzt werden kann?

Ich habe versucht, dies zu tun:

Expression<Func<Message, string>> userSelectorExpression = x => sent ? x.Sender : x.Recipient;
Func<Message, string> userSelector = userSelectorExpression.Compile();

Dabei erhalte ich jedoch den gleichen Fehler. Ich glaube, ich bin nicht zu verstehen, Ausdrücke. Ich glaube, alles, was ich mit dem obigen Code mache, ist, einen Ausdruck zu schreiben, ihn dann aber wieder in ausführbaren Code umzuwandeln und dann denselben Fehler zu bekommen. Wenn ich jedoch versuche, Folgendes zu verwenden userSelectorExpression innerhalb der LINQ-Abfrage kann sie nicht wie eine Methode aufgerufen werden.

Modifier

Für diejenigen, die sich für die Ausnahme interessieren, hier ist sie:

Der LINQ-Ausdrucksknotentyp "Invoke" wird in LINQ to Entities nicht unterstützt.

Ich habe das so verstanden, dass es nicht die userSelector Delegierte. Denn, wie oben erwähnt, muss er sie in einen Ausdrucksbaum übersetzen.

Wenn Sie eine echte Methode verwenden, erhalten Sie eine etwas ausführlichere Fehlermeldung:

LINQ to Entities erkennt die Methode 'System.String userSelector(Message, Boolean)' nicht, und diese Methode kann nicht in einen Speicherausdruck übersetzt werden.

2voto

artplastika Punkte 1960

Kein Grund zur Kompliziertheit:

return sent
    ? _dataContext.Messages.Count(x => x.Sender.ToLower() == username.ToLower())
    : _dataContext.Messages.Count(x => x.Recipient.ToLower() == username.ToLower());

0voto

Chev Punkte 56446

Nachdem ich ein bisschen herumgespielt hatte, bekam ich, was ich wollte.

Das hat mir in diesem Fall zwar nicht tonnenweise Code erspart, aber es macht die Basisabfrage viel übersichtlicher. Für kompliziertere Abfragen in der Zukunft wird dies großartig sein! Diese Abfragelogik wird nie wiederholt, aber dennoch so oft wiederverwendet, wie ich sie brauche.

Zunächst habe ich zwei Methoden in meinem Repository. Die eine zählt die Gesamtzahl der Nachrichten (die, die ich als Beispiel in meiner Frage verwendet habe) und eine, die eine Sammlung von Nachrichten nach Seitenzahl abruft. Sie sind folgendermaßen strukturiert:

Diejenige, die eine Gesamtzahl von Nachrichten erhält:

    /// <summary>
    /// Retrieves the total number of messages for the user.
    /// </summary>
    /// <param name="username">The name of the user.</param>
    /// <param name="sent">True if retrieving the number of messages sent.</param>
    /// <returns>The total number of messages.</returns>
    public int GetMessageCountBy_Username(string username, bool sent)
    {
        var query = _dataContext.Messages
            .Count(UserSelector(username, sent));
        return query;
    }

Derjenige, der Nachrichten erhält und sie auslagert:

    /// <summary>
    /// Retrieves a list of messages from the data context for a user.
    /// </summary>
    /// <param name="username">The name of the user.</param>
    /// <param name="page">The page number.</param>
    /// <param name="itemsPerPage">The number of items to display per page.</param>
    /// <returns>An enumerable list of messages.</returns>
    public IEnumerable<Message> GetMessagesBy_Username(string username, int page, int itemsPerPage, bool sent)
    {
        var query = _dataContext.Messages
            .Where(UserSelector(username, sent))
            .OrderByDescending(x => x.SentDate)
            .Skip(itemsPerPage * (page - 1))
            .Take(itemsPerPage);
        return query;
    }

Offensichtlich ist es die Aufforderung UserSelector(string, bool) Das ist hier das Entscheidende. So sieht diese Methode aus:

    /// <summary>
    /// Builds an expression to be reused in a LINQ query.
    /// </summary>
    /// <param name="username">The name of the user.</param>
    /// <param name="sent">True if retrieving sent messages.</param>
    /// <returns>An expression to be used in a LINQ query.</returns>
    private Expression<Func<Message, bool>> UserSelector(string username, bool sent)
    {
        return x => ((sent ? x.FromUser : x.ToUser).Username.ToLower() == username.ToLower()) && (sent ? !x.SenderDeleted : !x.RecipientDeleted);
    }

Diese Methode erstellt also einen Ausdruck, der ausgewertet und ordnungsgemäß in sein SQL-Äquivalent übersetzt wird. Die Funktion im Ausdruck wird zu true ausgewertet, wenn der Benutzername mit dem Benutzernamen des Absenders oder des Empfängers übereinstimmt, und deleted ist false entweder für den Absender oder den Empfänger, basierend auf dem übergebenen Boolean sent die in den Ausdruck serialisiert wird.

Hier ist eine Version des obigen Beispiels, die dem Beispiel in meiner Frage näher kommt. Es ist nicht so lesbar, da mein Ausdruck grotesk ist, aber zumindest verstehe ich jetzt, wie es funktioniert:

    public int GetMessageCountBy_Username(string username, bool sent)
    {
        Expression<Func<Message, bool>> userSelector = x => ((sent ? x.FromUser : x.ToUser).Username.ToLower() == username.ToLower()) && (sent ? !x.SenderDeleted : !x.RecipientDeleted);

        var query = _dataContext.Messages
            .Count(userSelector);
        return query;
    }

Das ist eigentlich eine ziemlich coole Sache. Es hat viel Zeit gekostet, das herauszufinden, aber es scheint wirklich leistungsstark zu sein. Ich habe jetzt ein neues Verständnis davon, wie LINQ, Lambdas und Ausdrücke funktionieren :)

Vielen Dank an alle, die zu dieser Frage beigetragen haben! (einschließlich dir artplastika, ich liebe dich immer noch, auch wenn ich deine Antwort nicht liebe)

-1voto

Hasanain Punkte 925

Vielleicht könnte dies hilfreich sein, um von Bedingungen (Prädikaten) zu abstrahieren: http://www.albahari.com/nutshell/predicatebuilder.aspx

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X