Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How might I clean up this lambda?

I have an expression that I use a few times in several LINQ queries so I separated it out into it's own method that returns the expression. The lambda portion of the function is kind of messy looking. Anyone want to take a crack at refactoring it and making it more readable and/or smaller?

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

A quick English description of what it's doing is it is checking the boolean sent and checking either the Message.FromUser or the Message.ToUser based on that boolean.

If the user is looking at his/her outbox, sent is true and it will see if x.FromUser.Username == username and x.SenderDeleted == false.

If the user is looking at his/her inbox then it does the same logic, but sent is false and it checks x.ToUser and x.RecipientDeleted instead.

Maybe this is the easiest way but I'm open to some refactoring.

ANSWER EDIT

I really liked Davy8's answer but I decided to take a step further and do two expressions instead of one expression with a nested function. Now I have the following methods:

    /// <summary>
    /// Expression to determine if a message belongs to a user.
    /// </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>> MessageBelongsToUser(string username, bool sent)
    {
        return x => (sent ? x.FromUser : x.ToUser).Username.Equals(username, StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>
    /// Expression to determine if a message has been deleted by the user.
    /// </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>> UserDidNotDeleteMessage(string username, bool sent)
    {
        return x => sent ? !x.SenderDeleted : !x.RecipientDeleted;
    }

So now my queries look like this:

    /// <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>
    /// <param name="sent">True if retrieving sent messages.</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(MessageBelongsToUser(username, sent))
            .Where(UserDidNotDeleteMessage(username, sent))
            .OrderByDescending(x => x.SentDate)
            .Skip(itemsPerPage * (page - 1))
            .Take(itemsPerPage);
        return query;
    }

    /// <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(MessageBelongsToUser(username, sent))
            .Where(UserDidNotDeleteMessage(username, sent))
            .Count();
        return query;
    }

I would say those are some very English readable queries, thanks guys!

Reference: http://www.codetunnel.com/blog/post/64/how-to-simplify-complex-linq-expressions

like image 697
Chev Avatar asked Oct 24 '22 10:10

Chev


1 Answers

Split it out into a separate function:

    private Expression<Func<Message, bool>> UserSelector(string username, bool sent)
    {
        return message=> InnerFunc(message, username, sent);
    }

    private static bool InnerFunc(Message message, string username, bool sent)
    {
        if(sent)
        {
            return string.Equals(message.FromUser.Username, username, StringComparison.InvariantCultureIgnoreCase) && !message.SenderDeleted;
        }
        return string.Equals(message.ToUser.Username, username, StringComparison.InvariantCultureIgnoreCase) && !message.RecipientDeleted;
    }

Or alternatively it can be inlined to maintain closure usage:

    private Expression<Func<Message, bool>> UserSelector(string username, bool sent)
    {
        Func<Message, bool> innerFunc = message =>
        {
            if (sent)
            {
                return string.Equals(message.FromUser.Username, username, StringComparison.InvariantCultureIgnoreCase) &&
                        !message.SenderDeleted;
            }
            return string.Equals(message.ToUser.Username, username, StringComparison.InvariantCultureIgnoreCase) &&
                    !message.RecipientDeleted;
        };
        return message => innerFunc(message);
    }

(Edited to use string.Equals with StringComparison.InvariantCultureIgnoreCase for odd edge cases with different culture settings.)

like image 97
Davy8 Avatar answered Oct 27 '22 09:10

Davy8