我正在玩表达树并试图更好地理解它们是如何工作的。我写了一些我正在使用的示例代码,希望有人可以帮助我。
所以我有一个有点混乱的问题:
/// <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)'方法,并且此方法无法转换为商店表达式。
答案 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,即使我不喜欢你的回答我仍然爱你)