Как превратить этот Func в выражение? - PullRequest
3 голосов
/ 26 мая 2011

Я играю с деревьями выражений и пытаюсь лучше понять, как они работают.Я написал некоторый пример кода, с которым я работаю, и, надеюсь, кто-то может мне помочь.

Итак, у меня есть несколько запутанный запрос:

/// <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 - это данные структуры сущностейконтекст.Этот запрос прекрасно работает, но его нелегко прочитать.Я решил разложить встроенный оператор IF в Func следующим образом:

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;
}

Кажется, что это будет прекрасно работать, но есть проблема.Поскольку запрос относится к IQueryable<T>, это выражение LINQ переводится в SQL для выполнения в источнике данных.Это здорово, но из-за этого он не знает, что делать с вызовом userSelector(x), и выдает исключение.Он не может перевести этот делегат в выражение.

Итак, теперь, когда я понимаю, почему это не удается, я хотел бы попытаться заставить его работать.Это гораздо больше работы для того, что мне нужно, но я делаю это просто из чистого интереса.Как я могу превратить это Func в выражение, которое можно перевести на SQL?

Я попытался сделать это:

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

При этом, однако, я получаю ту же ошибку.Я думаю, что я не понимаю выражения.Я думаю, что все, что я делаю с приведенным выше кодом, - это написание выражения, но затем снова превращаю его в исполняемый код и затем получаю ту же ошибку.Однако, если я попытаюсь использовать userSelectorExpression в запросе LINQ, его нельзя будет вызвать как метод.

В настоящий момент я в замешательстве.Любое разъяснение будет высоко ценится.Спасибо!

EDIT

Для тех, кто заинтересован в исключении, вот оно:

Тип узла выражения LINQ 'Invoke' не поддерживается в LINQ to Entities.

Я понял, что это не может "вызвать" делегат userSelector.Поскольку, как указано выше, необходимо преобразовать его в дерево выражений.

При использовании реального метода вы получите немного более подробное сообщение об ошибке:

LINQ to Entities делаетне распознает метод метода System.String userSelector (Message, Boolean), и этот метод не может быть преобразован в выражение хранилища.

Ответы [ 3 ]

2 голосов
/ 26 мая 2011

Не нужно усложнять:

return sent
    ? _dataContext.Messages.Count(x => x.Sender.ToLower() == username.ToLower())
    : _dataContext.Messages.Count(x => x.Recipient.ToLower() == username.ToLower());
0 голосов
/ 26 мая 2011

Ну, немного поиграв, я получил то, что хотел.

В данном случае это не спасло меня от тонны кода, но значительно облегчает просмотр базового запроса. Для более сложных запросов в будущем это будет здорово! Эта логика запроса никогда не повторяется, но все равно используется повторно столько раз, сколько мне нужно.

Сначала у меня есть два метода в моем хранилище. Один подсчитывает общее количество сообщений (тот, который я использовал в качестве примера в моем вопросе), а другой, который фактически получает коллекцию сообщений по номеру страницы. Вот как они структурированы:

Тот, который получает общее количество сообщений:

    /// <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;
    }

Тот, который получает сообщения и страницы их:

    /// <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;
    }

Очевидно, что вызов UserSelector(string, bool) - вот что важно. Вот как выглядит этот метод:

    /// <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);
    }

Таким образом, этот метод создает выражение для оценки и правильно переводится в его эквивалент SQL. Функция в выражении оценивается как истина, если имя пользователя совпадает с именем пользователя отправителя или получателя, а удаленное - ложь для отправителя или получателя на основе предоставленного логического значения sent, которое сериализовано в выражение.

Вот версия выше, которая ближе к примеру в моем вопросе. Это не так читаемо, потому что мое выражение гротескно, но сейчас я понимаю, как оно работает:

    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;
    }

Это на самом деле довольно классная штука. Потребовалось много времени, чтобы понять, но это кажется действительно мощным. Теперь у меня новое понимание того, как работают LINQ, lambdas и выражения:)

Спасибо всем, кто внес свой вклад в этот вопрос! (включая тебя, артпластика, я все еще люблю тебя, даже если я не люблю твой ответ)

0 голосов
/ 26 мая 2011

Может быть, это поможет вам абстрагироваться от условий (предикатов): http://www.albahari.com/nutshell/predicatebuilder.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...