Есть ли простой способ сделать ToList в запросе LINQ с использованием синтаксиса запроса?
Рассмотрим приведенный ниже код:
StockcheckJobs =
(from job in (from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new { stockcheckItem.JobId, stockcheckItem.JobData.EngineerId } into jobs
select jobs).ToList()
let date = MJM.GetOrCreateJobData(job.Key.JobId).CompletedJob.Value
orderby date descending
select new StockcheckJobsModel.StockcheckJob()
{
JobId = job.Key.JobId,
Date = date,
Engineer = (EngineerModel)job.Key.EngineerId,
MatchingLines = job.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = job.Count(sti => sti.Quantity != sti.ExpectedQuantity)
}).ToList()
В середине есть ToList()
, потому что метод GetOrCreateJobData
не может быть переведен в sql.
В результате мне пришлось окружить первую часть моего запроса в скобках, чтобы сделать это, затем я использовал внешний запрос для завершения.
Я знаю, что я мог бы разделить это на две переменные, но я не хочу этого делать (это тоже внутри инициализатора объекта).
Есть ли какой-нибудь другой синтаксис, который я могу использовать для повышения удобочитаемости, предпочтительно удаление необходимости внешнего внутреннего запроса, когда мне нужно сделать ToList
(или иначе перейти к linq-to-objects) в в середине запроса linq?
В идеальном мире мне хотелось бы что-то вроде этого (насколько это возможно в любом случае):
StockcheckJobs =
from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new { stockcheckItem.JobId, stockcheckItem.JobData.EngineerId } into jobs
MAGIC_DO_BELOW_AS_LINQ-TO-OBJECTS_KEYWORD_OR_SYNTAX
let date = MJM.GetOrCreateJobData(jobs.Key.JobId).CompletedJob.Value
orderby date descending
select new StockcheckJobsModel.StockcheckJob()
{
JobId = jobs.Key.JobId,
Date = date,
Engineer = new ThreeSixtyScheduling.Models.EngineerModel() { Number = jobs.Key.EngineerId },
MatchingLines = jobs.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = jobs.Count(sti => sti.Quantity != sti.ExpectedQuantity)
};
Ответы
Ответ 1
Я бы поднял два вопроса с вопросом:
- Я действительно не думаю, что есть проблема с читабельностью с введением здесь дополнительной переменной. На самом деле, я думаю, это делает его более читаемым, поскольку он отделяет код "локального выполнения" от кода, выполняемого в базе данных.
- Чтобы просто переключиться на LINQ-To-Objects,
AsEnumerable
предпочтительнее ToList
.
Итак, как вы можете оставаться в области запроса полностью без промежуточного AsEnumerable()/ToList() во всем выражении запроса: обманом компилятором С# с использованием ваших собственных методов расширения, а не BCL, Это возможно, так как С# использует подход, основанный на шаблонах (а не связанный с BCL), чтобы включить выражения запроса в вызовы методов и lambdas.
Объявить такие злые классы:
public static class To
{
public sealed class ToList { }
public static readonly ToList List;
// C# should target this method when you use "select To.List"
// inside a query expression.
public static List<T> Select<T>
(this IEnumerable<T> source, Func<T, ToList> projector)
{
return source.ToList();
}
}
public static class As
{
public sealed class AsEnumerable { }
public static readonly AsEnumerable Enumerable;
// C# should target this method when you use "select As.Enumerable"
// inside a query expression.
public static IEnumerable<T> Select<T>
(this IEnumerable<T> source, Func<T, AsEnumerable> projector)
{
return source;
}
}
И тогда вы можете писать такие запросы:
List<int> list = from num in new[] { 41 }.AsQueryable()
select num + 1 into result
select To.List;
IEnumerable<int> seq = from num in new[] { 41 }.AsQueryable()
select num + 1 into result
select As.Enumerable into seqItem
select seqItem + 1; // Subsequent processing
В вашем случае ваш запрос будет выглядеть следующим образом:
StockcheckJobs =
from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new { stockcheckItem.JobId, stockcheckItem.JobData.EngineerId } into jobs
select As.Enumerable into localJobs // MAGIC!
let date = MJM.GetOrCreateJobData(localJobs.Key.JobId).CompletedJob.Value
orderby date descending
select new StockcheckJobsModel.StockcheckJob()
{
JobId = localJobs.Key.JobId,
Date = date,
Engineer = new ThreeSixtyScheduling.Models.EngineerModel() { Number = localJobs.Key.EngineerId },
MatchingLines = localJobs.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = localJobs.Count(sti => sti.Quantity != sti.ExpectedQuantity)
};
Я действительно не вижу в этом никакого улучшения. Скорее, это довольно тяжелое злоупотребление языковой функцией.
Ответ 2
Вы можете исправить проблему GetOrCreateJobData
, не переводимую в SQL.
Внедряя настраиваемый транслятор запросов для указанного выражения вызова метода, вы можете получить контроль над тем, как LINQ-to-SQL интерпретирует этот метод. Существует хорошая статья, объясняющая эту процедуру, и ссылки на соответствующие ресурсы, доступные по адресу: http://www.codeproject.com/Articles/32968/QueryMap-Custom-translation-of-LINQ-expressions
В качестве альтернативы вы можете реорганизовать метод GetOrCreateJobData
на метод расширения, который строит ту же логику с помощью выражений, так что LINQ-to-SQL может интерпретировать ее естественным образом. В зависимости от сложности метода это может быть более или менее выполнимо, чем мое первое предложение.
Ответ 3
Я нахожу, что использование синтаксиса метода делает вещи более ясными, но это просто личное предпочтение. Это, безусловно, делает верхнюю половину запроса лучше, но использование let
, хотя и возможно в синтаксисе метода, немного больше работает.
var result = stockcheckItem in MDC.StockcheckItems
.Where(item => distinctJobs.Contains(item.JobId))
.GroupBy(item => new { item.JobId, item.JobData.EngineerId })
.AsEnumerable() //switch from Linq-to-sql to Linq-to-objects
.Select(job => new StockcheckJobsModel.StockcheckJob()
{
JobId = job.Key.JobId,
Date = MJM.GetOrCreateJobData(job.Key.JobId).CompletedJob.Value,
Engineer = (EngineerModel)job.Key.EngineerId,
MatchingLines = job.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines = job.Count(sti => sti.Quantity != sti.ExpectedQuantity)
})
.Orderby(item => item.Date)
.ToList()
Ответ 4
Один из вариантов заключается в том, чтобы вся работа по совместимости с SQL работала в анонимном типе,
var jobs =
(from job in (from stockcheckItem in MDC.StockcheckItems
where distinctJobs.Contains(stockcheckItem.JobId)
group stockcheckItem by new
{ stockcheckItem.JobId, stockcheckItem.JobData.EngineerId }
into jobs
select new
{
JobId = job.Key.JobId,
Engineer = (EngineerModel)job.Key.EngineerId,
MatchingLines =
job.Count(sti => sti.Quantity == sti.ExpectedQuantity),
DifferingLines =
job.Count(sti => sti.Quantity != sti.ExpectedQuantity)
}
).AsEnumerable()
StockcheckJobs = jobs.Select(j => new StockcheckJobsModel.StockcheckJob
{
JobId = j.JobId,
Date = MJM.GetOrCreateJobData(j.JobId).CompletedJob.Value,
Engineer = j.EngineerId,
MatchingLines = j.MatchingLines,
DifferingLines = j.DifferingLines
}).OrderBy(j => j.Date).ToList();
Очевидно, что не тестировалось, но вы поняли.