如何把这个Func变成一个表达? [英] How to turn this Func into an expression?

查看:87
本文介绍了如何把这个Func变成一个表达?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在玩表情树,试图更好地了解他们的工作方式。我写了一些我正在使用的示例代码,希望有人可以帮助我。



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

  ///< summary> 
///检索用户的邮件总数。
///< / summary>
///< param name =username>用户的名称< / param>
///< param name =sent>如果检索发送的邮件数,则为true< / param>
///< returns>消息的总数。< / 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();
返回查询;
}

_dataContext 是实体框架数据上下文。这个查询工作很好,但这不容易阅读。我决定将inline IF语句确定为 Func ,如下所示:

  public int GetMessageCountBy_Username(string username,bool sent)
{
Func< Message,string> userSelector = x =>发了? x.Sender:x.Recipient;
var query = _dataContext.Messages
.Where(x => userSelector(x).ToLower()== username.ToLower())
.Count();
返回查询;
}

这似乎会很好,但是有一个问题。因为查询是针对 IQueryable 这个LINQ表达式正在被转换成SQL以在数据源中执行。这很棒,但是由于这个原因,它不知道如何处理对 userSelector(x)的调用,并引发异常。它不能将此委托转换成表达式。



所以现在我明白为什么它是失败的,我想尝试使它的工作。这对我所需要的工作要大得多,但我只是出于纯粹的兴趣而做。我如何将这个 Func 转换成可以转换为SQL的表达式?



我试图这样做:

 表达式< Func<消息,字符串>> userSelectorExpression = x =>发了? x.Sender:x.Recipient; 
Func< Message,string> userSelector = userSelectorExpression.Compile();

然而,我收到同样的错误。我想我没有理解表达。我认为我正在用上面的代码编写一个表达式,但是再次将其转换为可执行代码,然后得到相同的错误。但是,如果我尝试在LINQ查询中使用 userSelectorExpression ,则无法像方法一样调用。



我现在很困惑任何澄清都将不胜感激。谢谢!



EDIT



对于那些对异常感兴趣的人,这里是:


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


我认为这意味着它不能调用 userSelector 委托。因为,如上所述,它需要将其转换成表达式树。



当使用真正的方法时,会得到一个稍微更详细的错误消息:


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



解决方案

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

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



首先我的存储库中有两种方法。一个统计消息的总数(我用作我的问题的例子)和一个实际上通过页码获取消息的集合。以下是他们的结构:



获得总计消息的人:

  ///< summary> 
///检索用户的邮件总数。
///< / summary>
///< param name =username>用户的名称< / param>
///< param name =sent>如果检索发送的邮件数,则为true< / param>
///< returns>消息的总数。< / returns>
public int GetMessageCountBy_Username(string username,bool sent)
{
var query = _dataContext.Messages
.Count(UserSelector(username,sent));
返回查询;
}

获取消息并将其页面的人:

  ///< summary> 
///从用户的数据上下文中检索消息列表。
///< / summary>
///< param name =username>用户的名称< / param>
///< param name =page>页码。< / param>
///< param name =itemsPerPage>每页显示的项目数。< / param>
///< returns>一个可枚举的消息列表< / 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 *(第-1页))
.Take(itemsPerPage);
返回查询;
}

显然是调用 UserSelector(string, bool)这是这里的大事。以下是该方法的样子:

  ///< summary> 
///构建一个要在LINQ查询中重用的表达式。
///< / summary>
///< param name =username>用户的名称< / param>
///< param name =sent>如果检索发送的邮件,则为true< / param>
///< returns>在LINQ查询中使用的表达式< / returns>
私人表达式< Func< Message,bool>>> UserSelector(string username,bool sent)
{
return x => ((sent?x.FromUser:x.ToUser).Username.ToLower()== username.ToLower())&&& (发送?!x.SenderDeleted:!x.RecipientDeleted);
}

所以这个方法构建一个要评估的表达式,并将其正确地转换为SQL当量。如果用户名与发件人或收件人的用户名匹配,则表达式中的函数将返回true,并且根据提供的布尔值发送的,对发件人或收件人的删除为false这个序列化到表达式中。



这是上面的一个版本,这更接近我的问题的例子。它不是可读的,因为我的表达式是怪诞的,但至少我明白现在如何工作:

  public int GetMessageCountBy_Username(string username, bool sent)
{
表达式< Func< Message,bool>> userSelector = x => ((sent?x.FromUser:x.ToUser).Username.ToLower()== username.ToLower())&&& (发送?!x.SenderDeleted:!x.RecipientDeleted);

var query = _dataContext.Messages
.Count(userSelector);
返回查询;
}

这其实是很酷的东西。花了很多时间弄清楚,但这似乎真的很强大。我现在对LINQ,lambdas和表达式的工作方式有了新的认识:)



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


I am playing around with expression trees and trying to better understand how they work. I wrote some sample code that I'm working with and hopefully someone can help me out.

So I have this somewhat messy 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(x => (sent ? x.Sender.ToLower() : x.Recipient.ToLower()) == username.ToLower())
        .Count();
    return query;
}

_dataContext is the entity framework data context. This query works beautifully, but it's not easy to read. I decided to factor the inline IF statement out into a Func like this:

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

This seems like it would work great, but there is a problem. Because the query is against IQueryable<T> this LINQ expression is being translated into SQL to be executed at the data source. That's great, but because of this it does not know what to do with the call to userSelector(x) and throws an exception. It cannot translate this delegate into an expression.

So now that I understand why it's failing I would like to try and make it work. It's far more work for what I need, but I'm doing it just out of pure interest. How might I turn this Func into an expression that can be translated into SQL?

I tried to do this:

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

With this however, I get the same error. I think I'm failing to understand expressions. I think all I'm doing with the above code is writing an expression but then turning it into executable code again and then getting the same error. However, if I try to use userSelectorExpression within the LINQ query it can't be called like a method.

I'm fairly confused at the moment. Any clarification would be much appreciated. Thanks!

EDIT

For those interested in the exception, here it is:

The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

I took this to mean that it could not "invoke" the userSelector delegate. Because, as stated above, it needs to translate it into an expression tree.

When using a real method, you get a slightly more verbose error message:

LINQ to Entities does not recognize the method 'System.String userSelector(Message, Boolean)' method, and this method cannot be translated into a store expression.

解决方案

Well after playing around a bit, I got what I wanted.

This didn't save me tons of code in this case, but it does make the base query much easier to look at. For more complicated queries in the future this will be awesome! This query logic never gets repeated, but still gets re-used as many times as I need to.

First I have two methods in my repository. One counts the total number of messages (the one I used as the example in my question) and one that actually gets a collection of messages by page number. Here is how they are structured:

The one that gets a total count of messages:

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

The one that gets messages and pages them:

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

Obviously it is the call to UserSelector(string, bool) that is the big deal here. Here is what that method looks like:

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

So this method builds an expression to be evaluated and properly gets translated into it's SQL equivalent. The function in the expression evaluates to true if the username matches the username of either the sender or the recipient and deleted is false for either sender or recipient, based on the supplied boolean sent that gets serialized into the expression.

Here is a version of the above, that is closer to the example in my question. It's not as readable since my expression is grotesque but at lease I understand how it's working now:

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

This is actually pretty cool stuff. Took a lot of time to figure out but this seems really powerful. I now have a new understanding of how LINQ, lambdas, and expressions work :)

Thanks to everyone who contributed to this question! (including you artplastika, I still love you even if I don't love your answer)

这篇关于如何把这个Func变成一个表达?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆