如何将此Func转换为表达式?

时间:2011-05-26 15:09:06

标签: c# linq entity-framework delegates linq-to-entities

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

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

/// <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”。

我认为这意味着它无法“调用”userSelector代表。因为,如上所述,它需要将其转换为表达式树。

使用真实方法时,会收到更详细的错误消息:

  

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

3 个答案:

答案 0 :(得分:2)

无需复杂化:

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

答案 1 :(得分:0)

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

答案 2 :(得分:0)

好好玩了一下后,我得到了我想要的东西。

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

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

获得消息总数的那个:

    /// <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等价物。如果用户名与发件人或收件人的用户名匹配,则表达式中的函数的计算结果为true,并且基于提供的序列化到表达式中的布尔值sent,对于发件人或收件人,已删除为false。

以上是上述版本,更接近我的问题中的示例。它不具有可读性,因为我的表达是怪诞的,但至少我理解它现在是如何工作的:

    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,即使我不喜欢你的回答我仍然爱你)