我有一个庞大的物品清单,需要按一个物业分组。然后应该选择每组中最老的。
简化示例:选择每个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 Connector版本6.9.5.0
答案 0 :(得分:3)
在不同的情况下使用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();
}
答案 1 :(得分:2)
你可能会尝试做一些更接近你在sql中做的事情(没有“row_number like”函数)...并看看生成了什么。
app:tabGravity="fill"
app:tabMode="fixed"
(混合流畅和查询语法,因为我发现连接的查询语法更清晰,但......这只是个人观点)
答案 2 :(得分:1)
您首先对它们进行分组,然后对每个子查询进行排序。当然,这会很慢。
首先尝试订购表格,这样您只需要执行一次。然后将它们分组并取第一个。
IQueryable<ED.User> Result = ctx.User
.OrderBy(x => x.BirthDate)
.GroupBy(x => x.FirstName, (k,g) => g.FirstOrDefault())
.AsQueryable();
答案 3 :(得分:1)
我非常确定在使用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();
答案 4 :(得分:0)
看看这个,你的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`))))
虽然我不确定会对性能产生什么影响。
答案 5 :(得分:0)
您需要索引,但这并不能保证最佳性能,因为EF生成的查询很可能是一个大型嵌套子查询。
如果性能仍然存在问题,您可以为每个组返回最旧的用户ID,并运行另一个查询以获取User对象。
更糟糕的情况是,使用内联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);
}