我使用.NET 3.5(客户要求)获得了一些看起来像这样的代码:
void Process (ActionState state)
{
var orderItemQuery = from OrderItem item in order.OrderItems
orderby item.OrderLineNumber ascending
select item;
foreach (OrderItem item in orderItemQuery)
{
ActionData actionData;
switch (state)
{
case ActionState.Prepare:
actionData = (
from ActionData ad in db.ActionDataTable
where ad.ObjectId == item.ProductId
select ad
).First();
case ActionState.QualityCheck:
actionData = (
from ActionData ad in db.ActionDataTable
where ad.ObjectId == item.OrderItemId
select ad
).First();
default:
throw new InvalidOperationException();
}
// ...
}
}
基本上,迭代第一个查询的结果,并使用特定的外键根据当前ActionData
从数据库中获取ActionState
记录。实际上,这里和那里有更多的嵌套和一些检查,但它基本上是相同的。
这最初适用于测试数据库,但客户只是向我发送了他们的实时数据的副本,而且速度非常慢。运行整个批次大约需要15分钟,包括其他一些处理。通过性能测试器运行代码后,我发现整个过程中最慢的部分是对每个案例的.First()
调用。
如果这是普通的SQL,我会根据state
的值编译循环外的存储过程,并使用它。既然我做不到,那还有什么选择呢?我怎样才能加快速度呢?
答案 0 :(得分:2)
您的问题是您的代码对db执行100500个请求。您需要在一个请求中获得所需内容。为此,您需要编写正确的linq查询。像这样的东西:
if(state!=ActionState.Prepare&&state!=ActionState.QualityCheck)
throw new InvalidOperationException();
var orderSelector = state == ActionState.Prepare
?o=>o.ProductId
:?o=>o.OrderItemId
var orderWithActionQuery = order.OrderItems.Orderby(o => o.OrderLineNumber).GroupJoin(
db.ActionDataTable,
orderSelector,
ad => ad.ObjectId,
(x, y) => new { item = x, actionDatas = y })
.Select(c => new {item = c.item, actionData = c.actionDatas.FirstOrDefault()});
foreach(var orderWithActionData in orderWithActionQuery)
{
//orderWithActionData.item is orderItem
//orderWithActionData.actionData is ation data of this item
}
在此代码中,对数据库的请求将与foreach一致发送。这将是单一的请求,性能会很好
答案 1 :(得分:0)
问题是您为循环中的每个项目调用数据库 您应该在一个查询中一起烘焙查询 这样,查询应作为数据库上的一个大型查询执行,结果应该从SQL服务器中获取。
您可以使用SQL事件探查器轻松发现问题并查看大量select x,y,z,... from ActionData
个查询。
这个选择OrderItem和ActionData到包含两个值的临时匿名类型。
void Process (ActionState state)
{
var orderItemQuery = from OrderItem item in order.OrderItems
orderby item.OrderLineNumber ascending
select item;
var orderItemWithActionDataQuery =
from item in orderItemQuery
select new{Item = item,
ActionData = (
from ActionData ad in db.ActionDataTable
where ad.ObjectId == item.ProductId
select ad
).First()};
switch (state)
{
case ActionState.Prepare:
// done
break;
case ActionState.QualityCheck:
orderItemWithActionDataQuery =
from item in orderItemQuery
select new{Item = item,
ActionData = (
from ActionData ad in db.ActionDataTable
where ad.ObjectId == item.OrderItemId
select ad
).First()};
default:
throw new InvalidOperationException();
}
foreach (var combinedItem in orderItemWithActionDataQuery)
{
OrderItem item = combinedItem.Item;
ActionData actionData = combinedItem.ActionData;
// ...
}
}
答案 2 :(得分:0)
您可能想要查询您的查询。我怀疑你遇到了n + 1问题,因为每次都会遇到你的数据库.First被调用。由于您是在循环中执行此操作,因此您的数据库可能会看到过多的命中。评估循环外的状态,并根据状态设置单独的查询,而不进行循环。
答案 3 :(得分:0)
您可能会发现这非常有效:
不是将Select放入For循环,而是生成OrderItemIds数组并使用Array.Contains();
void Process (ActionState state)
{
var orderItemQuery = from OrderItem item in order.OrderItems
orderby item.OrderLineNumber ascending
select item;
IEnumerable<ActionData> actionDatas = null;
if (state == ActionState.Prepare)
{
var productIds = orderItemQuery.Select(o => o.ProductId).ToArray();
actionDatas = db.ActionDataTable.Where(a => productIds.Contains(a.ObjectId));
}
else if(state == ActionState.QualityCheck)
{
var orderItemIds = orderItemQuery.Select(o => o.OrderItemId).ToArray();
actionDatas = db.ActionDataTable.Where(a => orderItemIds.Contains(a.ObjectId));
}
else
{
throw new InvalidOperationException();
}
// Do stuff against the complete list of actionDatas without requerying.
}
}