有没有一种使用查询语法在LINQ查询中执行ToList的简洁方法?

时间:2012-09-20 15:10:07

标签: c# linq linq-to-sql syntax readability

请考虑以下代码:

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)
     };

4 个答案:

答案 0 :(得分:5)

您可以解决GetOrCreateJobData无法转换为SQL的问题。

通过为指定的方法调用表达式实现自定义查询转换器,您可以控制LINQ-to-SQL如何解释该方法。有一篇很好的文章解释了这个程序,并链接到可用的相关资源:http://www.codeproject.com/Articles/32968/QueryMap-Custom-translation-of-LINQ-expressions

或者,您可以将GetOrCreateJobData方法重构为扩展方法,该方法使用表达式构建相同的逻辑,以便LINQ-to-SQL可以自然地解释它。根据方法的复杂程度,这可能比我的第一个建议或多或少可行。

答案 1 :(得分: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()

答案 2 :(得分:3)

我会提出两点问题:

  1. 我真的不认为在这里引入额外的变量有任何可读性问题。事实上,我认为它使更多可读,因为它分离了"本地执行"来自在数据库上执行的代码的代码。
  2. 要简单地切换到LINQ-To-Objects,AsEnumerable优于ToList
  3. 也就是说,在整个查询表达式中没有中间AsEnumerable()/ ToList()的情况下,你可以一直保持查询范围:通过欺骗C#编译器来使用自定义扩展方法而不是BCL。这是可能的,因为C#使用基于模式的"接近(而不是与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)
         };
    
    但是,我真的不认为这是任何改进。相反,它非常滥用语言功能。

答案 3 :(得分:0)

一种选择是将所有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();

显然没有经过测试,但你明白了。