【问题标题】:How to turn this Func into an expression?如何把这个 Func 变成一个表达式?
【发布时间】:2011-05-26 15:09:05
【问题描述】:

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

所以我有这个有点混乱的查询:

/// <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&lt;T&gt; 的,所以这个 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)' 方法,并且此方法无法转换为存储表达式。

【问题讨论】:

  • 注意:我知道,最终只使用我的原始查询会简单得多。我只是对表达式如何工作以供将来参考感兴趣。
  • 如果你把它作为一个单独的方法会发生什么?
  • @FlyingStreudel,同样的错误。这是因为查询正在被翻译成表达式树。
  • 糟糕,我没有注意到 EF 标签。是的,sql不能'
  • 我认为问题不在于它不能把它变成一个表达式树,而是它不能把得到的表达式树变成SQL。

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


【解决方案1】:

无需复杂化:

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

【讨论】:

    【解决方案2】:

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

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

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

    获得消息总数的那个:

        /// <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 被序列化到表达式中,对于发件人或收件人,deleted 为 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、lambda 和表达式的工作原理有了新的理解:)

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

    【讨论】:

    • 现在我正在尝试清理表达式中的 lambda,如果可能的话。随意贡献:stackoverflow.com/questions/6142432/…
    • 对此我不确定,但我认为您不需要在其中添加Expresion&lt;...&gt;。 this 和原始 lambda 之间的根本区别在于,它返回一个 bool(谓词)并且可以直接传递给 Where 方法。在问题的 lambda 中,您调用了在谓词中返回字符串的委托。解决方法是您将所有逻辑放在一个 lambda 中,而无需调用另一个。与Expression无关
    • @Mike 二 是必需的。如果我删除它,它仍然可以编译,但在运行时会失败。这是因为 sent 布尔值被序列化到表达式中,但不是在它是普通函数时。我认为...无论如何,如果我删除它,它就会失败。
    【解决方案3】:

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

    【讨论】:

    • 他说'对网络上其他代码的模糊引用,我必须在上面“谷歌”'))
    • 至少他把我和一些artplastika联系起来了。 “我受够了……”怎么了?无论如何,艺术是对的。我只想了解如何将此Func&lt;Message, string&gt; 转换为Expression&lt;deletegate&gt; 以包含在查询中。不是如何“抽象”。
    猜你喜欢
    • 2011-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-29
    • 1970-01-01
    • 2013-03-21
    • 2017-06-18
    相关资源
    最近更新 更多