英文:
How to order by date from related table with EF Core and LINQ
问题
我有一个MS SQL数据库,其中包含以下表格:
Item { ItemId | SerialNumber | Type | Height | ... }
和
Review { ReviewId | Date | ... | ItemId }
其中 Review.ItemId
是一个外键,用于将每个评论与其相应的项目关联起来。
到目前为止,使用 EF Core 6 和 LINQ(在 .NET 6.0 上)我正在获取具有一些可选搜索参数的项目的 N 行(用于分页)。它们按 ItemId
排序。对于每个项目,我还会添加最后一次从 Review 表中获取的评论日期,并将其设置为 Item 类的 [NotMapped]
属性。
public static List<Item> GetInstruments(Dictionary<string, int> searchParameters, QueryParameters queryParameters)
{
...
...
}
我以这种特定方式做这个是因为我不想获取整个数据库然后进行排序,而是先排序然后再获取(我不知道是否清楚)。希望我做得对。
在使用它之后,我发现最好按照最后的 Review.Date
进行排序,但是,再次强调,我想先排序然后再获取,并且我仍然希望显示没有评论的项目(就像使用 LEFT JOIN 或 OUTER APPLY 那样)。
首先,我尝试在经典的 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 中遇到了困难。
我首先尝试只获取项目和评论日期,但显然我做错了。我知道这样做不完整,不能完成我想要的一切,但我首先只想获取项目和最后的审阅日期。
var dataQuery = from il in dbContext.Items
select new
{
il,
DateLastReview = dbContext.Reviews
.Where(r => il.ItemId == r.ItemId)
.Max(c => r.Date)
};
现在我这样是因为我不只是想向 Item 表中添加一个 DateLastReview 列;如果找不到解决方案或者你认为对性能和最佳实践更好,我会这样做,但我不想重复一个字段。
在保持排序/订购然后获取数据的想法的同时,有人可以帮我解决这个问题吗?
英文:
I have a MS SQL database with these tables
Item { ItemId | SerialNumber | Type | Height | ... }
and
Review { ReviewId | Date | ... | ItemId }
with Review.ItemId
being a foreign key to link each Review to their respective Item.
For now, with EF Core 6 and using LINQ (on .NET 6.0) I am fetching N rows (for pagination) of items with some optional search parameters. They are ordred by ItemId
.
For each item, I also add the last review date that I was getting from the table Review and set as a [NotMapped]
property of the Item class.
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;
}
I'm doing it in this specific way because I don't want to fetch the whole DB then sort but sort then fetch (I don't know if it's clear). Hopefully I did it correctly.
After using it I found it would be better to sort by last Review.Date
BUT, again, I want to sort then fetch AND I still want to display items that have no reviews (as with an LEFT JOIN or a OUTER APPLY).
First I tried to do it in classic SQL and it worked when I tested it (btw if there is a better way to do the following in SQL I'll also take it)
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
But I'm stumped with EF and LINQ.
I tried first to just get the Items and the Review Date but I obviously doing it wrong. I know it's incomplete to do everything I want but I first wanted to just get the items and the LastReviewDate.
var dataQuery = from il in dbContext.Items
select new
{
il,
DateLastReview = dbContext.Reviews
.Where(r => il.ItemId == r.ItemId)
.Max(c => r.Date)
};
So now I'm here because I don't want to just add a DateLastReview column to the Item table; I will if I don't find any solution or if you think it's better for performance and best practice but I didn't want to duplicate a field.
Could anyone help me with this while keeping the idea of sorting/ordering then fetching data.
答案1
得分: 1
不要这样做:
dbContext.Items.AsQueryable().AsEnumerable();
这会导致从数据库将所有内容加载到内存中(实际上没有获取到按相关实体排序所需的信息)。我没有你的设置,但可以使用“静态”查询非常简单地完成所需的排序,例如使用以下实体:
public class Blog
{
public int Id { get; private set; }
// ...
public List<Post> Posts { get; } = new(); // 不要忘记关系设置
}
public class Post
{
public int Id { get; private set; }
// ...
public DateTime PublishedOn { get; set; }
public Blog Blog { get; set; } = null!; // 不要忘记关系
}
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);
...),您需要使用一些开关语句硬编码,或生成表达式树或使用一些第三方库,如System.Linq.Dynamic.Core
或LINQKit
。
另请参阅此答案。
英文:
Don't do this:
dbContext.Items.AsQueryable().AsEnumerable();
This will lead to fetching everything into memory from the database (without actually fetching the needed information to sort by related entity). I don't have your setup but the needed ordering can be done quite simply with "static" query, for example using the following entities:
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();
Which results in following SQL (Postgres) for my setup:
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
As for dynamic query generation - you - remove the AsEnumerable
call and for the reflection stuff (var porpertyInfo = typeof(Item).GetProperty(queryParameters.OrderBy);
...) you will need either to hardcode it by using some switch cases or generate expression tree or use some 3rd party library like System.Linq.Dynamic.Core
or LINQKit
.
Also see this answer.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论