【问题标题】:Join three list using multiple columns c# linq lambda使用多列 c# linq lambda 加入三个列表
【发布时间】:2017-06-09 06:07:42
【问题描述】:

我有这些清单:

var subjects = new List<SubjectModel>
        {
            new SubjectModel { subjId = 1, subjName = "Math" },
            new SubjectModel { subjId = 2, subjName = "Science" },
            new SubjectModel { subjId = 3, subjName = "History" },
            new SubjectModel { subjId = 4, subjName = "Language" }
        };

var quizzes = new List<QuizModel>
        {
            new QuizModel { quizId = 1, quizDate = DateTime.Parse("2016-11-25"), quizScore = 10, subjectId = 1 },
            new QuizModel { quizId = 2, quizDate = DateTime.Parse("2016-11-25"), quizScore = 15, subjectId = 1 },
            new QuizModel { quizId = 3, quizDate = DateTime.Parse("2016-11-25"), quizScore = 8, subjectId = 2 },
            new QuizModel { quizId = 4, quizDate = DateTime.Parse("2016-11-26"), quizScore = 13, subjectId = 1 },
            new QuizModel { quizId = 5, quizDate = DateTime.Parse("2016-11-26"), quizScore = 20, subjectId = 2 }
        };

var exams = new List<ExamModel>
        {
            new ExamModel { examId = 1, examDate = DateTime.Parse("2016-11-25"), examScore = 90, subjectId = 1 },
            new ExamModel { examId = 2, examDate = DateTime.Parse("2016-11-25"), examScore = 88, subjectId = 2 },
            new ExamModel { examId = 3, examDate = DateTime.Parse("2016-11-25"), examScore = 92, subjectId = 4 },
            new ExamModel { examId = , examDate = DateTime.Parse("2016-11-26"), examScore = 84, subjectId = 1 },

        };

var exercises = new List<ExerciseModel>
        {
            new ExerciseModel { exerciseId = 1, exerciseDate = DateTime.Parse("2016-11-25"), exerciseScore = 17, subjectId = 1 }, 
            new ExerciseModel { exerciseId = 2, exerciseDate = DateTime.Parse("2016-11-25"), exerciseScore = 15, subjectId = 2 },
            new ExerciseModel { exerciseId = 3, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 15, subjectId = 1 },
            new ExerciseModel { exerciseId = 4, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 12, subjectId = 4 },
            new ExerciseModel { exerciseId = 5, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 10, subjectId = 1 },
        };

我能够成功地将它们按日期和主题分组。

var allQuizzes = quizzes.GroupBy(qz => qz.quizDate, (q, values) =>
            new
            {
                Date = q,
                Quizzes = values.GroupBy(v => v.subjectId, (c, values2) => 
                    new { 
                        SubjectId = c, 
                        QuizSum = values2.Sum(v2 => v2.quizScore) 
                    })
            });

var allExercises = exercises.GroupBy(ex => ex.exerciseDate, (e, values) =>
            new {
                Date = e,
                Exercises = values.GroupBy(x => x.subjectId, (z, values2) =>
                    new {
                        SubjectId = z,
                        ExerSum = values2.Sum(r => r.exerciseScore)
                    })
            });

var allExams = exams.GroupBy(ex => ex.examDate, (e, values) =>
            new
            {
                Date = e,
                Exercises = values.GroupBy(x => x.subjectId, (z, values2) =>
                    new
                    {
                        SubjectId = z,
                        ExamSum = values2.Sum(r => r.examScore)
                    })
            });

但是,我需要将他们三个都加入才能得到所有分数的总和。决赛桌应该像这样显示。

-----------------------------------------------------------------
|      Date     |  Math  |  Science  |   History   |  Language  |
|   11/25/2016  |   132  |      111  |         0   |        92  |
|   11/26/2016  |   122  |       20  |         0   |        12  |
-----------------------------------------------------------------

我尝试加入他们,但似乎无法通过多个列加入。

【问题讨论】:

  • 你必须搜索 Pivot
  • @irenebelserion1 你有什么反馈吗,如果答案对你有帮助/对你没有帮助? :)

标签: c# asp.net .net linq lambda


【解决方案1】:

我以同一个匿名类的形式从所有 3 个集合结果中进行选择(同一个 Idea 的第一个答案是 Andrei),这样我就可以在所有列表中收集所有结果,而无需映射和转换。

var allQuiz = quizzes.GroupBy(x => new { x.subjectId, x.quizDate })
                     .Select(x => new {
                          Date = x.Key.quizDate, 
                          Subj = x.Key.subjectId, 
                          Sum = x.Sum(r=>r.quizScore)});

var allExam= exams.GroupBy(x => new { x.subjectId, x.examDate })
                     .Select(x => new {
                          Date = x.Key.examDate, 
                          Subj = x.Key.subjectId, 
                          Sum = x.Sum(r=>r.examScore)});

var allExc = exercises.GroupBy(x => new { x.subjectId, x.exerciseDate })
                     .Select(x => new {
                          Date = x.Key.exerciseDate, 
                          Subj = x.Key.subjectId, 
                          Sum = x.Sum(r=>r.exerciseScore)});

将所有结果组合在一起:

var all = allQuiz.ToList();
all.AddRange(allExam.ToList());
all.AddRange(allExc.ToList());

var result = all.GroupBy(x => new { x.Date, x.Subj })
                .Select(x => new { x.Key.Date, x.Key.Subj, Sum = x.Sum(s => s.Sum)});

var list = result.GroupBy(r => r.Date).Select(x => new {
       Date = x.Key, 
       Math = x.SingleOrDefault(t=>t.Subj==1)?.Sum ?? 0,
       Science = x.SingleOrDefault(t=>t.Subj==2)?.Sum ?? 0,
       History = x.SingleOrDefault(t=>t.Subj==3)?.Sum ?? 0,
       Language = x.SingleOrDefault(t=>t.Subj==4)?.Sum ?? 0,
       });

LinqPad 中的输出:

【讨论】:

  • 是的,就是这样。谢谢你。我确实修改了几行,我在 SingleorDefault() 部分不断收到错误。
  • @irenebelserion1 谢谢。我也会感谢您的支持:)
【解决方案2】:

这是一个想法。您可以将所有三个转换为相同的结构,而不是在分组时保持区别。例如:

var allQuizzes = quizzes.GroupBy(qz => qz.quizDate, (q, values) =>
        new
        {
            Date = q,
            Results = values.GroupBy(v => v.subjectId, (c, values2) => 
                new { 
                    SubjectId = c, 
                    Sum = values2.Sum(v2 => v2.quizScore) 
                })
        });

注意名称“Results”和“Sum” - 您可以对其他两个对象使用相同的名称。现在你有了三个集合,它们的结构都一样:

{
    Date:
    Results: [
         {SubjectId, Sum}
         {SubjectId, Sum}
         ...
    ]
}

由于它们现在都相同,您可以停止区别对待它们,使用 UNION 合并所有三个,按日期和主题分组。然后您可能可以遍历主题列表以获取必要的信息,这取决于您所说的“决赛桌”。

【讨论】:

    【解决方案3】:

    这就是我想出的。 它可能不是最好的优化,但对你来说可能就足够了。 我在测试中将结果渲染到 StringBuilder 中。

    var result =
    quizzes.Select(q => new {SubjectId = q.subjectId, Date = q.quizDate, Score = q.quizScore})
        .Union(exams.Select(e => new {SubjectId = e.subjectId, Date = e.examDate, Score = e.examScore}))
        .Union(exercises.Select(e => new {SubjectId = e.subjectId, Date = e.exerciseDate, Score = e.exerciseScore}))
        .GroupBy(arg => arg.Date,
        (key, values)=> 
        new
        {
            Key = key,
            Scores = values.GroupBy(v => v.SubjectId, (s, values2) => new { SubjectId = s, SumScore = values2.Sum(v2 => v2.Score) })
        });
    
    StringBuilder sb = new StringBuilder("Date\t\t");
    foreach (SubjectModel subject in subjects)
    {
        sb.Append($"{subject.subjName}\t");
    }
    
    sb.AppendLine();
    
    foreach (var record in result)
    {
        sb.Append($"{record.Key.ToShortDateString()}\t");
        foreach (SubjectModel subject in subjects)
        {
            int sum = record.Scores.Where(s => s.SubjectId == subject.subjId).Select(s => s.SumScore).DefaultIfEmpty(0).Single();
            sb.Append($"{sum}\t");
        }
        sb.AppendLine();
    }
    
    string finalTable = sb.ToString();
    

    【讨论】:

      【解决方案4】:

      不要使用三个不同的匿名对象来保存结果,而是创建自己的类:

      public enum TestType
      {
          Quiz,
          Exam,
          Exercise,
      }
      
      public class TestScore
      {
          public TestType Type { get; set; }
          public DateTime Date { get; set; }
          public int      Score { get; set; }
          public int      SubjectId { get; set; }
      
          // Constructors - make a TestScore object
      
          public TestScore(QuizModel q)
          {
              Type      = TestType.Quiz;
              Date      = q.quizDate;
              Score     = q.quizScore;
              SubjectId = q.SubjectId;            
          }
      
          public TestScore(ExamModel e)
          {
              Type      = TestType.Exam;
              Date      = e.examDate;
              Score     = e.examScore;
              SubjectId = e.SubjectId;            
          }
      
          public TestScore(ExerciseModel e)
          {
              Type      = TestType.Exercise;
              Date      = e.exerciseDate;
              Score     = e.exerciseScore;
              SubjectId = e.SubjectId;            
          }
      }
      

      转换为 TestScore:

      List<TestScore> scores = new List<TestScore>();
      
      scores.AddRange(quizzes.Select(q => new TestScore(q));
      scores.AddRange(exams.Select(e => new TestScore(e));
      scores.AddRange(exercises.Select(e => new TestScore(e));
      

      现在您有一个数据源而不是三个,显示结果变得容易。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-16
        • 2016-03-01
        • 1970-01-01
        • 2014-02-23
        • 1970-01-01
        • 2015-01-24
        • 2012-05-06
        • 1970-01-01
        相关资源
        最近更新 更多