在LINQ

时间:2015-07-30 15:46:27

标签: c# linq

我正在使用此代码对游戏中的玩家进行排名。

    private void RecalculateUserRanks(GWDatabase db)
    {
        // RankedScore is a precalculated Double.
        var users = db.UserStatistics.Where(x => x.RankedScore > 0);
        var usersWithRank = users.OrderByDescending(x => x.RankedScore)
               .Select(x => new
               {
                    x.Id,
                    x.RankedScore
               });

        int position = 0;
        foreach (var u in usersWithRank)
        {
            position++;
            db.UserStatistics.First(x => x.Id == u.Id).Rank = position;
        }

        db.SaveChanges();
    }

这不是最漂亮的,随着玩家数量的增加,这可能需要一些时间并使用一些记忆。

我可以在纯TSQL中这样做:

;WITH r AS
  (
    SELECT 
        [Id]
        ,[RankedScore]
        ,ROW_NUMBER() OVER(ORDER BY [RankedScore] DESC) AS Rnk
    FROM [dbo].[UsersStatistics]
)
UPDATE u
    SET u.Rank = r.Rnk
FROM [dbo].[UsersStatistics] u
INNER JOIN r ON r.Id = u.Id

但我宁愿将所有逻辑保留在C#代码中,因为数据库现在一直在重建(所有其他逻辑也在那里)。

所以我的问题是,如果有一种更聪明的方法可以在C#LINQ(或Lambda,如果那是你的东西)中执行此操作而不在for循环中迭代它,而不将所有数据拖到SQL之外?

1 个答案:

答案 0 :(得分:1)

我假设有效率'你的意思是“有效阅读”。为了加快计算速度,您可以考虑使用log n的排序列表;那些使用//get sorted list of IDs var SortedIds = db.UserStatistics .OrderByDescending(x => x.RankedScore) .Select(x => x.Id); //Fill in Values into result-set db.UserStatistics = db.UserStatistics .Where(x => x.RankedScore > 0) .ForEach(x => u.Rank = SortedIds.IndexOf(x.id)); 时间插入新成员时保持自己排序。

这与你发布的几乎相同,除了懒惰评估可能会节省一点时间:

db.UserStatistics = db.UserStatistics.ForEach(u => 
   u.Rank = db.UserStatistics
    .Where(x => x.RankedScore > 0)
    .OrderByDescending(x => x.RankedScore)
    .IndexOf(u.id));

将排名和未排名的球员放在一起似乎有些不一致。

这样可以让未排名的玩家获得排名-1,同时保存一步。缺点是,所有用户都将被更改,而不是那些具有等级的用户:

self.mainView