如何使用EF Core和LINQ从相关表中按日期排序

amrnrhlw  于 2023-04-03  发布在  其他
关注(0)|答案(1)|浏览(201)

我有一个MS SQL数据库,其中包含这些表
Item { ItemId | SerialNumber | Type | Height | ... }

Review { ReviewId | Date | ... | ItemId }
其中Review.ItemId是将每个Review链接到其相应Item的外键。
现在,使用EF Core 6和LINQ(在.NET 6.0上),我使用一些可选的搜索参数获取N行(用于分页)项目。它们按ItemId排序。对于每个项目,我还添加了从表Review中获取的最后一次审查日期,并设置为Item类的[NotMapped]属性。

public static List<Item> GetInstruments(Dictionary<string, int> searchParameters, QueryParameters queryParameters)
{
    List<Item> items = new();

    try
    {
        using (var dbContext = new DbContext())
        {
            var dataQuery = dbContext.Items.AsQueryable().AsEnumerable();

            if (searchParameters != null)
            {
                foreach (var searchParameter in searchParameters)
                {
                    if (searchParameter.Key == "SerialNumber")
                    {
                        dataQuery = dataQuery.Where(i => i.SerialNumber == searchParameter.Value);
                    }
                    if (searchParameter.Key == "Type")
                    {
                        dataQuery = dataQuery.Where(i => i.Type == searchParameter.Value);
                    }
                    if (searchParameter.Key == "Height")
                    {
                        dataQuery = dataQuery.Where(i => i.Height == searchParameter.Value);
                    }
                }
            }

            var offset = (queryParameters.PageNumber - 1) * queryParameters.RowsPerPage;
            if (queryParameters.OrderBy == null)
            {
                dataQuery = dataQuery.OrderByDescending(i => i.ItemId);
            }
            else
            {
                var porpertyInfo = typeof(Item).GetProperty(queryParameters.OrderBy);
                if (queryParameters.Ascending == true)
                {
                    dataQuery = dataQuery.AsEnumerable().OrderBy(i => porpertyInfo.GetValue(i, null));
                }
                else
                {
                    dataQuery = dataQuery.AsEnumerable().OrderByDescending(i => porpertyInfo.GetValue(i, null));
                }
            }
            dataQuery = dataQuery
                    .Skip(offset)
                    .Take(queryParameters.RowsPerPage);

            items = dataQuery.ToList();
            foreach (var instrument in items)
            {
                instrument.DateLastReview = dbContext.Reviews
                    .Where(c => c.ItemId == instrument.ItemId)
                    .Max(c => c.Date);
            }
        }
    }
    catch (Exception e)
    {
        ...
    }

    return items;
}

我之所以这样做是因为我不想先获取整个数据库然后排序,而是先排序再获取(我不知道这是否清楚)。
在使用它之后,我发现最好按最后的Review.Date排序,但是,同样,我想排序然后获取,我仍然想显示没有评论的项目(如左连接或外部应用)。
首先,我尝试在经典的SQL中做这件事,当我测试它时,它工作了(顺便说一句,如果有更好的方法在SQL中做下面的事情,我也会接受它)

SELECT i.ItemId, i.SerialNumber, i.Type, i.Height, r.Date
FROM Item i
OUTER APPLY
(
    SELECT Max(r.Date) as Date
    FROM Review r
    WHERE r.ItemId = i.ItemId
) r
ORDER BY r.Date DESC

但是我被EF和LINQ难倒了。
我首先尝试只获取项目和审查日期,但我显然做错了。我知道这是不完整的,做我想要的一切,但我首先想只获取项目和LastReviewDate。

var dataQuery = from il in dbContext.Items
                select new
                {
                    il,
                    DateLastReview = dbContext.Reviews
                                     .Where(r => il.ItemId == r.ItemId)
                                     .Max(c => r.Date)
                };

所以现在我在这里,因为我不想只是添加一个DateLastReview列到Item表;如果我没有找到任何解决方案,或者您认为它对性能和最佳实践更好,但我不想重复一个字段,我会这样做。
有没有人能帮助我,同时保持排序/排序,然后获取数据的想法。

e5nqia27

e5nqia271#

不要这样做:

dbContext.Items.AsQueryable().AsEnumerable();

这将导致从数据库中获取所有内容到内存中(而实际上没有获取所需的信息来按相关实体进行排序)。我没有你的设置,但所需的排序可以很简单地用“静态”查询来完成,例如使用以下实体:

public class Blog
{
    public int Id { get; private set; }
    // ...
    public List<Post> Posts { get; } = new(); // do not forget the relationship setup
}

public class Post
{
    public int Id { get; private set; }
    // ...
    public DateTime PublishedOn { get; set; }
    public Blog Blog { get; set; } = null!; // do not forget the relationship
}

var blogs = ctx.Blogs
    .OrderByDescending(blog => blog.Posts.Max(post => post.PublishedOn))
    .ToList();

这将导致以下SQL(Postgres)用于我的设置:

SELECT b."Id", b."Name"
FROM "Blogs" AS b
ORDER BY (
   SELECT max(p."PublishedOn")
   FROM "Posts" AS p
   WHERE b."Id" = p."BlogId") DESC

至于动态查询生成-你-删除AsEnumerable调用和反射的东西(var porpertyInfo = typeof(Item).GetProperty(queryParameters.OrderBy);...),你将需要通过使用一些开关情况或生成expression tree或使用一些第三方库,如System.Linq.Dynamic.CoreLINQKit硬编码。
参见this answer

相关问题