我有以下对象:
我希望能够列出所有公司,并按总订单价值的总和进行订购。
我试过这个:
IQueryable<Company> query = _context.Companies
.Include(c => c.InstalledBases)
.ThenInclude(ib=>ib.Orders);
query = query.OrderBy(company =>
company.InstalledBases.Sum(ib =>
ib.Orders.Sum(order => order.Value)));
但是这段代码给了我警告:&#34; LINQ表达式Sum()无法翻译,将在本地进行评估&#34;。 我有成千上万的公司,安装基地和订单,所以查询需要很长时间才能完成。
我使用的是Asp Core 2.0.0
有没有办法避免本地评估加快查询速度?
修改
我还使用Skip和Take来限制目前浏览的公司:
result = result.Skip(request.Start).Take(25);
答案 0 :(得分:0)
嗯,这里的问题是,实际上没有办法将这一切包装在一个查询中。相关实体价值的总和需要group by子句,该子句不能返回公司的数据。但是,由于您已加入InstalledBases
和Orders
,在内存中运行总和订单并不会让您付出任何代价:您&# 39;无论如何都已经返回了所有数据。
如果它太慢,那么你真的应该将它卸载到存储过程中。
答案 1 :(得分:0)
因此公司有零个或多个InstalledBases,而且每个InstalledBase都属于一个公司。
同样:InstalledBase有零个或多个订单,每个订单只属于一个InstalledBase。
这些是真正的一对多关系。 Entity Framework One-to-many relations的配置类似于以下内容:
class Company
{
public int Id {get; set;}
// every company has zero or more InstalledBases:
public virtual ICollection<InstalledBase> InstalledBases {get; set;}
}
class InstalledBase
{
public int Id {get; set;}
// every InstalledBase belongs to exactly one Company using foreign key:
public int CompanId {get; set;}
public virtual Company Company {get; set;}
// every InstalledBase has zero or more Orders:
public virtual ICollection<Order> Orders {get; set;}
}
class Order
{
public int Id {get; set;}
// every Order belongs to exactly one InstalledBase using foreign key:
public int InstalledBaseId{get; set;}
public virtual InstalledBase InstalledBase {get; set;}
}
您希望所有公司及其所有已安装的基地及其所有订单。由此产生的公司序列应按公司所有已安装基础中所有订单总和的总和进行排序。
数据库查询的一个较慢的方面是将所有提取的数据从数据库传输到您的进程。因此,限制传输的数据是明智的。
如果您使用Include,将传输完整记录。这样您就可以传输大量您不需要的数据。例如,每家公司都有一个Id。本公司的每个InstalledBase都有一个具有相同价值的CompanyId。
因此,如果您要求公司拥有20个已安装的基础,则CompanyId的相同价值将被转移21次。多么浪费处理能力!
因此,请考虑仅使用Select语句传输您计划使用的数据。这也将帮助您解决总和问题。
将总和作为属性添加到您的选择中。然后对序列进行排序并仅返回请求的数据:
var result = myDbContext.Companies
// if you don't need all companies use a Where
.Where(company => company.City = "London")
// use a select to fetch only the data you need:
.Select(company => new
{
// Fetch only the Company data you plan to use:
Company = new
{
Id = company.Id,
Name = company.Name,
...
InstalledBases = company.InstalledBases.Select(installedBase => new
{ // use only the properties you plan to use
Location = installedBase.Location,
...
// no need to transfer CompanyId, you've already got it:
// CompanyId = installedBase.CompanyId,
// fetch the Orders of the InstalledBase:
Orders = installedBase.Orders.Select(order => new
{ // Again, only the properties you'll use
...
})
.ToList(),
})
.ToList(),
},
// for the ordering:
SumOfSums = company.InstalledBases
.Select(installedBase => installedBases.Orders.Count())
.Sum();
})
.OrderBy(intermediateResult => intermediateResult.SumOfSums)
.Select(intermediateResult => intermediateResult.Company);
答案 2 :(得分:0)
var q =
from o in _context.Set<Order>()
join ib in _context.Set<InstalledBase>() on o.InstalledBaseID equals ib.ID
join c in _context.Set<Company>() on ib.OwnerCompanyID equals c.ID
let t = new
{
o.InstalledBaseID,
o.Value,
c.ID,
c.Name
}
group t by new { t.ID, t.Name } into g
select new
{
CompanyId = g.Key.ID,
Name = g.Key.Name,
CompanyTotal = g.Sum(f => f.Value)
};
var r = q.ToList();
我不知道你所拥有的阶级关系,但这应该有效,我们正在做的是使用let子句生成Company,InstalledBase和Orders的平面视图,并将该平面视图分组并总结一下你需要。 我没有机会验证这个查询,但我认为它应该适用于ASP.NET,但不确定它是否适用于.Net Core。
PS,如果您将数据集作为Context而不是context.Set()的属性,则可以使用set name,如context.Orders。