【问题标题】:Joing lists with anonymous columns from dynamic pivot使用来自动态数据透视表的匿名列加入列表
【发布时间】:2016-11-18 16:46:48
【问题描述】:

我有两个共享一个公共字段的列表。但是,我想加入公共字段上的列表,其中一个列表来自 SQL 动态数据透视表,因此该列表中的所有列名(链接字段除外)都具有未知的列名。我的问题是如何找到这些列名以便创建新列表?

示例

class Student 
{
 int StudentID {get; set;}
 string FirstName {get; set;}
 string LastName {get; set;}
}

studentCollection 是学生的集合

我在这里以class ReportRanking 为例。它是一个dynamic 类,它从使用动态数据透视的存储过程返回。所以我不提前知道列名。我使用TestScore-1TestScore-2 等作为占位符来显示返回的内容。列名将包含学生参加的测试的名称。列中的值将是他们收到的分数。

class StudentTestScores
{
 int StudentID {get; set;}
 int TestScore-1 {get; set;}
 int TestScore-2 {get; set;}
 int TestScore-3 {get; set;}
 ...
}

testResultCollection 是 StudentScores 的集合。

+-----------+---------+----------+----------+---------+
| StudentId | History | Algebra | Geometry | Biology |
+-----------+---------+----------+----------+---------+
|     1     |    88   |    96    |    87    |    91   |
+-----------+---------+----------+----------+---------+
|     2     |    92   |    75    |    88    |    74   |
+-----------+---------+----------+----------+---------+

因为结果来自动态数据,所以在编译时我不知道 StudentTestScores 中的列名称是什么。它们代表学生参加的考试的名称。如何引用列名以便将列表组合成一个新的复合列表?

var testResults = from student in studentCollection 
                    join testResult in testResultCollection 
                      on student.StudentId equals testResult.StudentId 
                    select new {
                      student.StudentId,
                      student.FirstName,
                      student.LastName,
                      testResult.XXXXXXX // Not sure how to reference the test scores
                      ...
                    }

这就是我最终需要的......

+-----------+-----------+----------+---------+---------+----------+---------+
| StudentId | FirstName | LastName | History | Algebra | Geometry | Biology |
+-----------+-----------+----------+---------+---------+----------+---------+
|     1     | Bob       | Smith    |    88   |    96   |    87    |    91   |
+-----------+-----------+----------+---------+---------+----------+---------+
|     2     | Sally     | Jenkins  |    92   |    75   |    88    |    74   |
+-----------+-----------+----------+---------+---------+----------+---------+

【问题讨论】:

  • 最终目标是什么?您想将结果序列化为 json 或类似的东西吗?
  • 最终目标是拥有一个集合,我可以对其进行迭代并从(Excel、CSV 等)创建报告。
  • 由于您在编译时不知道属性,这将是未知类型(对象、动态等)的列表。可以吗?
  • 是的,因为那时我可以遍历集合。一旦我把它们放在一起,我就可以循环遍历集合以构建列标题并获取数据。

标签: c# linq join dynamic pivot


【解决方案1】:

我会将您的对象合并为一个ExpandoObject,并将其作为动态返回。这样你就可以像往常一样访问它的属性(因为它是动态的),并且任何反射代码(比如序列化到 json\csv)也可以像往常一样探索它的属性。代码如下:

class Student
{
    public int StudentId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class StudentTestScores {
    public int StudentId { get; set; }
    public int History { get; set; }
    public int Algebra { get; set; }
    public int Geometry { get; set; }
    public int Biology { get; set; }
}

static void Main(string[] args) {
    var studentCollection = new List<Student>(new [] {
        new Student() {StudentId = 1, FirstName = "Test", LastName = "Test"}, 
    });
    var testResultCollection = new List<StudentTestScores>(new [] {
        new StudentTestScores() {StudentId = 1, Algebra = 2, Biology = 5, Geometry = 3}, 
    });
    var testResults = from student in studentCollection
                      join testResult in testResultCollection
                        on student.StudentId equals testResult.StudentId
                      select Combine(student, testResult);
    Console.WriteLine(JsonConvert.SerializeObject(testResults));
    // outputs [{"StudentId":1,"FirstName":"Test","LastName":"Test","History":0,"Algebra":2,"Geometry":3,"Biology":5}]
    Console.ReadKey();
}



static dynamic Combine(params object[] objects) {            
    var exp = (IDictionary<string, object>) new ExpandoObject();
    foreach (var o in objects) {
        var dict = o as IDictionary<string, object>;
        if (dict != null) {
            foreach (var prop in dict) {
                if (!exp.ContainsKey(prop.Key)) {
                    exp.Add(prop.Key, prop.Value);
                } 
            }
        }
        else {
            foreach (var prop in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) {
                if (prop.CanRead && !exp.ContainsKey(prop.Name)) {
                    exp.Add(prop.Name, prop.GetValue(o));
                }
            }
        }
    }            
    return exp;
}

【讨论】:

  • 正如我在原始问题中提到的,我确实知道测试结果的列名可能是什么。它们可以是(历史、数学、拼写)或(化学、生物学、数学)或(篮子编织、艺术、音乐)或任何其他组合。
  • @webworm 但答案中的代码不关心名称。那些名字只是举例。
  • 然而,select Combine(student, testResult) 可能正是我所需要的。我不知道combine。我现在明白你对这些名字的看法了。
  • 当我在本地尝试这个时,我得到[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}] 用于控制台输出
  • 您可以发布您使用的测试数据吗?使用来自答案的测试数据,它可以正常工作。
【解决方案2】:

如果您在运行时之前不知道类属性的命名,我会使用反射来获取值。

public class Student
  {
    public int StudentID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
  }

  public class StudentTestScores
  {
    public int StudentID { get; set; }
    public int TestScoreGen {get; set;}
  }

  class Program
  {

    static void Main(string[] args)
    {
      var studentCollection = new List<Student> { new Student { StudentID = 1, FirstName = "Brett", LastName = "X" }, new Student { StudentID = 2, FirstName = "John", LastName = "Y" } };
      var testResultCollection = new List<StudentTestScores> { new StudentTestScores { StudentID = 1, TestScoreGen = 94 }, new StudentTestScores { StudentID = 2, TestScoreGen = 86 } };
      var props = testResultCollection.First().GetType().GetProperties();

      //Check my properties
      props.ToList().ForEach(x => Console.WriteLine(x));

      var testResults = from student in studentCollection
                        join testResult in testResultCollection
                          on student.StudentID equals testResult.StudentID
                        select new
                        {
                          student.StudentID,
                          student.FirstName,
                          student.LastName,
                          resultName = testResult.GetType().GetProperty(props[1].Name),
                          resultValue = testResult.GetType().GetProperty(props[1].Name).GetValue(testResult, null)
                        };

      testResults.ToList().ForEach(x => Console.WriteLine($"{x.StudentID} {x.FirstName} {x.LastName} {x.resultName} {x.resultValue}"));

      Console.ReadLine();
    }
  }

11-22 更新

如果属性不存在,您可能会遇到问题。在这种情况下,反射会爆炸,因为那里什么都没有。这相当于 SQL 中的左连接。您可能正在加入有时存在,有时不存在的东西。在这种情况下,您只需要知道如何处理这样的事情。我已经更新了上面的例子,说明如何合成它。基本上我看到我有 2 个或更多属性,但我没有。然后,如果我没有使用三元运算符,我会选择要做什么。我认为三元运算符非常适合直接分配 if、then、else。

public class Student
  {
    public int StudentID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
  }

  public class StudentTestScores
  {
    public int StudentID { get; set; }
    //public int TestScoreGen {get; set;}
  }

  class Program
  {

    static void Main(string[] args)
    {
      var studentCollection = new List<Student> { new Student { StudentID = 1, FirstName = "Brett", LastName = "X" }, new Student { StudentID = 2, FirstName = "John", LastName = "Y" } };
      //var testResultCollection = new List<StudentTestScores> { new StudentTestScores { StudentID = 1, TestScoreGen = 94 }, new StudentTestScores { StudentID = 2, TestScoreGen = 86 } };
      var testResultCollection = new List<StudentTestScores> { new StudentTestScores { StudentID = 1 }, new StudentTestScores { StudentID = 2 } };
      var props = testResultCollection.First().GetType().GetProperties();

      //Check my properties
      props.ToList().ForEach(x => Console.WriteLine(x));

      var testResults = from student in studentCollection
                        join testResult in testResultCollection
                          on student.StudentID equals testResult.StudentID
                        select new
                        {
                          student.StudentID,
                          student.FirstName,
                          student.LastName,
                          resultName = props.Count() > 1 ? testResult.GetType().GetProperty(props[1]?.Name)?.ToString() : "Nothing",
                          result = props.Count() > 1 ? testResult.GetType().GetProperty(props[1]?.Name).GetValue(testResult, null) : "0"
                        };

      testResults.ToList().ForEach(x => Console.WriteLine($"{x.StudentID} {x.FirstName} {x.LastName} {x.resultName} {x.result}"));

      Console.ReadLine();
    }
  }

【讨论】:

  • 我知道你是如何使用反射来获取分数的,但是我将如何获取包含类名的分数列的标题(HistoryAlgebraGeometry,和Biology)?
  • 我很困惑,如果您知道名称,您只会使用“testResult.History”或“testResult.Algebra”。如果您不知道名称但知道它在类 POCO 对象中的位置,您将为 StudentID 之后的第二个位置执行 props[1] 等等。如果您只想呼应“历史”或“代数”及其价值。这很简单。让我更新我的答案,你可以看到。
  • 抱歉造成混淆...我不知道编译时类的名称(History、Algebra、Biology、tec..),但我知道 POCO 对象中的位置因为我知道会有多少,顺序并不重要。
  • 如果您只想查看名称和值,那么我更新的示例应该对您有用。
  • 尝试此Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Cannot perform runtime binding on a null reference时出现以下错误
猜你喜欢
  • 1970-01-01
  • 2019-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-14
  • 2023-03-14
相关资源
最近更新 更多