如何将此Func转换为表达式

本文关键字:转换 表达式 Func | 更新日期: 2023-09-27 17:57:41

我在玩表达式树,并试图更好地理解它们是如何工作的。我写了一些正在使用的示例代码,希望有人能帮我。

所以我有一个有点混乱的问题:

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

然而,有了这个,我得到了同样的错误。我想我不懂表达。我想我对上面的代码所做的就是写一个表达式,然后再次将其转换为可执行代码,然后得到相同的错误。但是,如果我尝试在LINQ查询中使用userSelectorExpression,它就不能像方法一样被调用。

编辑

对于那些对例外感兴趣的人来说,这里是:

LINQ to Entities中不支持LINQ表达式节点类型"Invoke"。

我认为这意味着它不能";调用";CCD_ 7代表。因为,如上所述,它需要将其转换为表达式树。

当使用实际方法时,您会得到一条稍微详细一些的错误消息:

LINQ to Entities无法识别方法"System.String userSelector(Message,Boolean)"方法,并且此方法无法转换为存储表达式。

如何将此Func转换为表达式

无需复杂化:

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

玩了一会儿之后,我得到了我想要的。

在这种情况下,这并没有为我节省大量的代码,但它确实使基本查询更容易查看。对于未来更复杂的查询,这将非常棒!这个查询逻辑永远不会重复,但仍然可以根据需要重复使用多次

首先,我的存储库中有两种方法。其中一个统计消息的总数(我在问题中使用的例子),另一个实际按页码获取消息集合。以下是它们的结构:

获得消息总数的一个:

    /// <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,删除的发件人或收件人为false,则表达式中的函数计算结果为true。

这是上面的一个版本,更接近我问题中的例子。由于我的表情怪异,它不太可读,但至少我理解它现在的工作原理:

    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和表达式的工作方式有了新的理解:)

感谢所有为这个问题做出贡献的人!(包括你artplastika,即使我不喜欢你的答案,我仍然爱你)

也许这可以帮助您抽象条件(谓词):http://www.albahari.com/nutshell/predicatebuilder.aspx