.net 优化提取ID然后基于该ID提取消息的查询

flseospp  于 2023-02-26  发布在  .NET
关注(0)|答案(3)|浏览(137)

我是实体框架的新手,想知道这个函数是否可以优化。本质上,我想做的是得到一个在过去3天内发生的所有唯一ID的列表。然后提取每个ID的最后6条记录。下面的方法对我很有效,但我想知道这个方法是否可以针对1000条记录进行优化。
我的结构如下所示

public class ModelMessage
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ModelMessageId { get; set; }
    public string TaskId { get; set; }
    public string Msg { get; set; }
    public DateTime DateAdded { get; set; }
}

这就是我正在做的

private List<ModelMessage> Results()
    {
        //last 3 days
        int days = days * -1;
        List<ModelMessage> collection = new List<ModelMessage>();
        DateTime currenteDate = DateTime.UtcNow.Date.AddDays(days);
        DbContext dbcontext = new DbContext(ConString);
        
        //Get all the unique ids that were added 3 days ago
        List<string> listIds = dbcontext.Messages.Where(x => (x.DateAdded > currenteDate)).Select(x => x.TaskId).Distinct().ToList();
        foreach (string id in listIds)
        {
            //Get the last 6 records of each of those ids
            collection.AddRange(dbcontext.Messages.Where(x => (x.TaskId == id)).OrderBy(order => order.DateAdded).TakeLast(6));
        }
        return collection;
    }
}

任何建议或想法,如果这个方法可以优化(C#和实体相关)的速度和性能。

bogh5gae

bogh5gae1#

在EF中编写查询逻辑时,尽量不要运行多个查询。每次运行foreachToList()First()等时,都是在 * 运行 * 查询。
相反,将查询(IQueryable)组合在一起,并在最后运行查询,如下所示:

var taskIds = (from m in db.ModelMessages
               where m.DateAdded >= DateTime.UtcNow.Date.AddDays(-3)
               select m.TaskId).Distinct();

var q = from t in taskIds
        from m in db.ModelMessages
                    .Where(m => m.TaskId == t)
                    .OrderByDescending(m => m.DateAdded)
                    .Take(6)
        select m;

var results = q.ToList();

对于SQL Server,它将转换为单个SQL查询。在EF Core中:

SELECT [t1].[ModelMessageId], [t1].[DateAdded], [t1].[Msg], [t1].[TaskId]
  FROM (
      SELECT DISTINCT [m].[TaskId]
      FROM [ModelMessages] AS [m]
      WHERE [m].[DateAdded] >= DATEADD(day, CAST(-3.0E0 AS int), CONVERT(date, GETUTCDATE()))
  ) AS [t]
  INNER JOIN (
      SELECT [t0].[ModelMessageId], [t0].[DateAdded], [t0].[Msg], [t0].[TaskId]
      FROM (
          SELECT [m0].[ModelMessageId], [m0].[DateAdded], [m0].[Msg], [m0].[TaskId], ROW_NUMBER() OVER(PARTITION BY [m0].[TaskId] ORDER BY [m0].[DateAdded] DESC) AS [row]
          FROM [ModelMessages] AS [m0]
      ) AS [t0]
      WHERE [t0].[row] <= 6
  ) AS [t1] ON [t].[TaskId] = [t1].[TaskId]

EF6生成的查询略有不同,但逻辑上是等效的:

SELECT
    [Project3].[ModelMessageId] AS [ModelMessageId],
    [Project3].[TaskId] AS [TaskId],
    [Project3].[Msg] AS [Msg],
    [Project3].[DateAdded] AS [DateAdded]
    FROM ( SELECT
        [Limit1].[ModelMessageId] AS [ModelMessageId],
        [Limit1].[TaskId] AS [TaskId],
        [Limit1].[Msg] AS [Msg],
        [Limit1].[DateAdded] AS [DateAdded]
        FROM   (SELECT DISTINCT
            [Extent1].[TaskId] AS [TaskId]
            FROM [dbo].[ModelMessages] AS [Extent1]
            WHERE [Extent1].[DateAdded] >= @p__linq__0 ) AS [Distinct1]
        CROSS APPLY  (SELECT TOP (6) [Project2].[ModelMessageId] AS [ModelMessageId], [Project2].[TaskId] AS [TaskId], [Project2].[Msg] AS [Msg], [Project2].[DateAdded] AS [DateAdded]
            FROM ( SELECT
                [Extent2].[ModelMessageId] AS [ModelMessageId],
                [Extent2].[TaskId] AS [TaskId],
                [Extent2].[Msg] AS [Msg],
                [Extent2].[DateAdded] AS [DateAdded]
                FROM [dbo].[ModelMessages] AS [Extent2]
                WHERE ([Extent2].[TaskId] = [Distinct1].[TaskId]) OR (([Extent2].[TaskId] IS NULL) AND ([Distinct1].[TaskId] IS NULL))
            )  AS [Project2]
            ORDER BY [Project2].[DateAdded] DESC ) AS [Limit1]
    )  AS [Project3]
    ORDER BY [Project3].[TaskId] ASC, [Project3].[DateAdded] ASC
fgw7neuy

fgw7neuy2#

您的代码效率非常低,因为它要拉回整个列表,然后再将其推回到服务器,而且它实际上是一个自连接。
我认为最好使用窗口函数来完成。不确定是否有任何方法可以在实体框架中不执行原始SQL来完成此操作,但您需要的SQL将类似于以下内容:

SELECT *
FROM (
    SELECT *,
        FIRST_VALUE(DateAdded) OVER (PARTITION BY TaskId ORDER BY DateAdded DESC) AS Recent,
        ROW_NUMBER() OVER (PARTITION BY TaskId ORDER BY DateAdded DESC) AS rn
    FROM Messages
) m
WHERE Recent > @currentDate AND rn <= 6;

如果有人能把这个翻译成EF,请随意添加到我的答案中。

vhipe2zx

vhipe2zx3#

您可以使用IQueryable<T>GroupBy扩展方法:

private List<ModelMessage> Results()
{
    // your code
    
    //Get all messages grouped by TaskId.
    List<IGrouping<string, ModelMessage>> tasks = dbcontext.Messages.Where(x => x.DateAdded > currenteDate).GroupBy(x => x.TaskId).ToList();

    foreach (IGrouping<string, ModelMessage> task in tasks)
    {
        //Get the last 6 messages
        collection.AddRange(task.OrderBy(order => order.DateAdded).TakeLast(6));
    }

    return collection;
}

GroupBy返回一个IGrouping<TKey,TElement>,您可以对其进行迭代以获取每组TElement

相关问题