【问题标题】:Linq - Order by in IncludeLinq - 按包含顺序排列
【发布时间】:2020-01-25 21:27:33
【问题描述】:

我遇到需要对 Include 对象执行 OrderBy 的情况。这就是我迄今为止尝试过的方式

Customers query = null;

try
{
    query = _context.Customers
        .Include(x => x.CustomerStatus)
        .ThenInclude(x => x.StatusNavigation)
        .Select(x => new Customers()
        {
            Id = x.Id,
            Address = x.Address,
            Contact = x.Contact,
            Name = x.Name,
            CustomerStatus = new List<CustomerStatus>
            {
                x.CustomerStatus.OrderByDescending(y => y.Date).FirstOrDefault()
            }
        })
        .FirstOrDefault(x => x.Id == 3);
}
catch (Exception ex)
{
    throw;
}

上面的代码成功地订购了包含元素,但它不包括它的子表。 例如:客户包括 CustomerStatus 但 CustomerStatus 不包括 StatusNavigation 表。

我什至试过这个,但它都帮不了我

_context.Customers
    .Include(x => x.CustomerStatus.OrderByDescending(y => y.Date).FirstOrDefault())
    .ThenInclude(x => x.StatusNavigation).FirstOrDefault(x => x.Id == 3);

我做错了什么,请指导我

即使我尝试过这种方式

var query = _context.CustomerStatus
    .GroupBy(x => x.CustomerId)
    .Select(x => x.OrderByDescending(y => y.Date).FirstOrDefault())
    .Include(x => x.StatusNavigation)
    .Join(_context.Customers, first => first.CustomerId, second => second.Id, (first, second) => new Customers
    {
        Id = second.Id,
        Name = second.Name,
        Address = second.Address,
        Contact = second.Contact,
        CustomerStatus = new List<CustomerStatus> {
            new CustomerStatus
            {
                Id = first.Id,
                CustomerId = first.CustomerId,
                Date = first.Date,
                StatusNavigation = first.StatusNavigation
            }
        },
    }).FirstOrDefault(x => x.Id == 3);

但这会击中数据库 3 次并将结果过滤到内存中。 首先从客户状态中选择所有数据,然后从状态中选择所有数据,然后从客户中选择,然后过滤内存中的所有数据。有没有其他有效的方法来做到这一点??

这是我通过实体类准备的

【问题讨论】:

标签: c# entity-framework linq asp.net-core


【解决方案1】:

正如@Chris Pratt 所说,一旦您在选择中执行new Customer,您正在创建一个新模型。您正在丢弃由 EntityFramework 构建的模型。我的建议是查询:

query = _context.Customers
    .Include(x => x.CustomerStatus)
    .ThenInclude(x => x.StatusNavigation);

这样你会有一个 IQueryable 对象,除非你从中进行选择,否则它不会被执行:

var customer3 = query.FirstOrDefault(x=>x.Id==3)

返回客户和相互关联的表(CustomerStatus 和 StatusNavigation)。然后你可以创建你想要的对象:

var customer = new Customers()
    {
        Id = customer3.Id,
        Address = customer3.Address,
        Contact = customer3.Contact,
        Name = x.Name,
        CustomerStatus = new List<CustomerStatus>
        {
            customer3.CustomerStatus.OrderByDescending(y => y.Date).FirstOrDefault()
        }
    })

通过这种方式,您可以重复使用查询来创建不同的响应对象并对数据库进行一次查询,但缺点是使用的内存比原始查询更多(即使它不应该成为太大的问题) .

如果最初从数据库返回的模型不符合要求(即您总是需要这样做:CustomerStatus = new List {...}),则可能表明数据库架构没有很好地满足应用程序的需求,因此可能需要重构。

【讨论】:

    【解决方案2】:

    我认为正在发生的事情是您实际上覆盖了IncludeThenIncludeInclude 明确地急切加载导航属性。但是,您正在做一些可能会阻碍这一点的事情。

    首先,您要选择一个新的Customer。仅此一项就足以打破Include 的逻辑。其次,您将覆盖 CustomerStatus 集合中的内容。理想情况下,这应该只是通过Include 自动加载,但是通过将其更改为仅具有第一个实体,您基本上放弃了Include 的效果。 (选择一个关系就足以导致发出一个连接,而无需显式调用Include)。第三,ThenIncludeInclude 为基础,因此覆盖它可能也会丢弃ThenIncude

    这一切都是猜想。我以前没有做过与您在这里所做的完全一样的事情,但没有其他任何意义。

    也尝试选择一个新的CustomerStatus

    CustomerStatus = x.CustomerStatus.OrderByDescending(o => o.Date).Select(s => new CustomerStatus
    {
        x.Id,
        x.Status,
        x.Date,
        x.CustomerId,
        x.Customer,
        x.StatusNavigation
     })
    

    此时您可以删除Include/ThenInclude,因为选择这些关系的行为会导致加入。

    【讨论】:

    • 我只需要该查询中的一列。我只想知道客户是否活跃。我不需要该客户的完整历史记录
    • @ChrisHadfield 我认为这个答案是正确的。他只是没有在最后添加或默认添加。但我认为在这种情况下,您不应该使用对象来拉取数据并制作视图 poco 或匿名对象,因为当您直接拉取它们时,它们的 EF 对象实际上并不相同。
    【解决方案3】:

    在阅读了 (Source 1)(Source 2) 的几个来源之后。我认为正在发生的事情是,如果您在Include 之后使用select。即使您在select 中使用Include 查询数据,它也会忽略Include。所以要解决这个问题,在调用select之前使用.AsEnumerable()

    query = _context.Customers
        .Include(x => x.CustomerStatus)
        .ThenInclude(x => x.StatusNavigation)
        .AsEnumerable()
        .Select(x => new Customers()
        {
            Id = x.Id,
            Address = x.Address,
            Contact = x.Contact,
            Name = x.Name,
            CustomerStatus = new List<CustomerStatus>
            {
                x.CustomerStatus.OrderByDescending(y => y.Date).FirstOrDefault()
            }
        })
        .FirstOrDefault(x => x.Id == 3);
    

    【讨论】:

    • AsEnumerable 只会将所有内容拉入内存。
    • @FilipCordas 是的,你是正确的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-08
    • 1970-01-01
    相关资源
    最近更新 更多