【问题标题】:Entity Framework GroupBy take the oldest with mySQLEntity Framework GroupBy 用 mySQL 取最老的
【发布时间】:2016-04-22 06:26:17
【问题描述】:

我有一个庞大的项目列表,需要按一个属性对它们进行分组。然后应该选择每个组中最老的。

简化示例:选择每个FirstName 中最老的用户。

using (ED.NWEntities ctx = new ED.NWEntities())
{
    IQueryable<ED.User> Result = ctx.User.GroupBy(x => x.FirstName)
                                    .Select(y => y.OrderBy(z => z.BirthDate)
                                    .FirstOrDefault())
                                    .AsQueryable();
}

班级User:

public partial class User
{
    public int UserID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Nullable<System.DateTime> BirthDate { get; set; }
}

我想知道为什么这条语句花了这么长时间直到我在Result 设置断点并查看生成的 SQL 语句:

{SELECT
`Apply1`.`UserID`, 
`Apply1`.`FIRSTNAME1` AS `FirstName`, 
`Apply1`.`LastName`, 
`Apply1`.`BirthDate`
FROM (SELECT
`Distinct1`.`FirstName`, 
(SELECT
`Project2`.`UserID`
FROM `User` AS `Project2`
 WHERE (`Distinct1`.`FirstName` = `Project2`.`FirstName`) OR ((`Distinct1`.`FirstName` IS  NULL) AND (`Project2`.`FirstName` IS  NULL))
 ORDER BY 
`Project2`.`BirthDate` ASC LIMIT 1) AS `UserID`, 
(SELECT
`Project2`.`FirstName`
FROM `User` AS `Project2`
 WHERE (`Distinct1`.`FirstName` = `Project2`.`FirstName`) OR ((`Distinct1`.`FirstName` IS  NULL) AND (`Project2`.`FirstName` IS  NULL))
 ORDER BY 
`Project2`.`BirthDate` ASC LIMIT 1) AS `FIRSTNAME1`, 
(SELECT
`Project2`.`LastName`
FROM `User` AS `Project2`
 WHERE (`Distinct1`.`FirstName` = `Project2`.`FirstName`) OR ((`Distinct1`.`FirstName` IS  NULL) AND (`Project2`.`FirstName` IS  NULL))
 ORDER BY 
`Project2`.`BirthDate` ASC LIMIT 1) AS `LastName`, 
(SELECT
`Project2`.`BirthDate`
FROM `User` AS `Project2`
 WHERE (`Distinct1`.`FirstName` = `Project2`.`FirstName`) OR ((`Distinct1`.`FirstName` IS  NULL) AND (`Project2`.`FirstName` IS  NULL))
 ORDER BY 
`Project2`.`BirthDate` ASC LIMIT 1) AS `BirthDate`
FROM (SELECT DISTINCT 
`Extent1`.`FirstName`
FROM `User` AS `Extent1`) AS `Distinct1`) AS `Apply1`}

问题:有没有办法解决他的效率更高?子选择很昂贵,EF 每列生成一个。我使用 mySQL .NET 连接器版本 6.9.5.0

【问题讨论】:

  • 由于某种原因无法重现,对我来说,这一切都在一个查询中完成
  • @AlexanderDerck 您在使用 mySQL 吗?我使用 .NET 连接器版本 6.9.5.0
  • 另一个框架增加复杂性的案例?

标签: c# mysql entity-framework linq


【解决方案1】:

在不同的地方使用 Jon Skeet 的 answer..

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

你可以试试:

using (ED.NWEntities ctx = new ED.NWEntities())
{
    IQueryable<ED.User> Result = ctx.User.OrderBy(y => y.BirthDate)
                                    .DistinctBy(z => z.FirstName)
                                    .AsQueryable();
}

【讨论】:

  • 但是这个是从数据库中取出整个表,然后再处理数据?
  • 首先获取平面数据,然后在内存中进行分组可能是克服 MySqls 在子查询中阻塞倾向的有效方法。
【解决方案2】:

你可以尝试做一些更接近你在 sql 中做的事情(没有“row_number like”函数)......看看生成了什么。

var maxAges = ctx.User.GroupBy(x => x.FirstName)
                      .Select(g => new {
                         firstName = g.Key,
                         maxAge = g.Min(x => x.BirthDate)
                      });
var result = from u in ctx.User
             join a in maxAges on new{f = u.FirstName, b =u.BirthDate} equals new{f = a.firstName, b =a.maxAge}
             select u;

(混合流利和查询语法,因为我发现查询语法对于连接更清晰,但是……这只是个人观点)

【讨论】:

  • 有没有办法让ID加入?这将避免在BirthDate + FirstName 重复的情况下出现无效值
  • 是的,您也可以包含 ID,我们可以按 FirstName + Birthdate + ID 进行分组
  • @fubo 在这种情况下你会随机选择一个吗?您可以在枚举结果中进行第一次查询(因为您不应该从 db 返回太多结果),但这并不是很好(但是...这是优化)
  • @bit 这不会返回与 OP 查询相同的结果
【解决方案3】:

您首先将它们分组,然后对每个子查询进行排序。当然,它会很慢。

尝试先订购餐桌,这样您只需执行一次。然后将它们分组并拿走第一个。

IQueryable<ED.User> Result = ctx.User
    .OrderBy(x => x.BirthDate)
    .GroupBy(x => x.FirstName, (k,g) => g.FirstOrDefault())
    .AsQueryable();

【讨论】:

  • 您的方法也导致 4 个子选择 - 没有改进
【解决方案4】:

我很确定,当您使用 mySQL 时,您可以创建一个与您的 SELECT 语句不同的 GROUP BY 子句。换句话说,您选择的行不能是聚合函数的一部分。所以这样的查询应该可以工作:

SELECT
      FirstName
      ,LastName
      ,BirthDate
  FROM Users
  GROUP BY FirstName
  ORDER BY BirthDate

请在您的 mySQL 查询浏览器中尝试此操作。您可以直接将此查询与您的实体框架上下文一起使用,如下所示:

string query = ".."; // the query above

var res = context.Database.SqlQuery<Users>(query).ToList();

【讨论】:

    【解决方案5】:

    看看这个,你的previous 和其他一些问题(如this),看起来使用 EF 和 MySQL 很痛苦。

    你最终可以试试这个 LINQ 查询

    var query = db.User.Where(user => !db.User.Any(
        u => u.UserID != user.UserID && u.FirstName == user.FirstName &&
        (u.BirthDate < user.BirthDate || (u.BirthDate == user.BirthDate && u.UserID < user.UserID))));
    

    生成这个简单的 SQL 查询

    SELECT
    `Extent1`.`UserID`, 
    `Extent1`.`FirstName`, 
    `Extent1`.`LastName`, 
    `Extent1`.`BirthDate`
    FROM `Users` AS `Extent1`
     WHERE NOT EXISTS(SELECT
    1 AS `C1`
    FROM `Users` AS `Extent2`
     WHERE ((`Extent2`.`UserID` != `Extent1`.`UserID`) AND (`Extent2`.`FirstName` = `Extent1`.`FirstName`)) AND ((`Extent2`.`BirthDate` < `Extent1`.`BirthDate`) OR ((`Extent2`.`BirthDate` = `Extent1`.`BirthDate`) AND (`Extent2`.`UserID` < `Extent1`.`UserID`))))
    

    虽然我不确定性能会受到什么影响。

    【讨论】:

    • 这和我的问题一样慢。可能我需要添加一些索引
    • 很公平。我有那种感觉。使FirstName 成为必需并在其上创建索引可能会有所帮助。除此之外,我没有看到任何可行的查询构造,也许你应该考虑内存方法。
    【解决方案6】:

    您需要索引,但这并不能保证最佳性能,因为 EF 生成的查询很可能是大型嵌套子查询。

    如果性能仍然存在问题,您可以返回每个组最旧的用户 ID,然后运行另一个查询来获取用户对象。

    更糟糕的情况,使用内联 sql、视图或存储过程。

    由于我不使用 Mysql,也不知道你有什么索引,所以我会把这个任务留给你。

      var oldestUsers = (from u in users
                           group u by u.FirstName into grp 
                           select new {
                               grp.Key,
                               oldestUser = (from u in grp
                                             orderby u.BirthDate descending
                                             select u).First()
                           }).ToList();
    
        foreach (var u in oldestUsers)
        {
            Console.WriteLine("{0} {1:D}", u.oldestUser.FirstName, u.oldestUser.BirthDate);
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-26
      • 2018-11-25
      • 2014-08-29
      • 2019-02-15
      • 2015-01-22
      • 2017-12-20
      • 2020-11-26
      • 2011-11-28
      相关资源
      最近更新 更多