Подтвердить что ты не робот

Есть ли простой способ сделать 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)
     };
4b9b3361

Ответ 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();

Очевидно, что не тестировалось, но вы поняли.