Обединяване на списъци с анонимни колони от динамичен пивот

Имам два списъка, които споделят общо поле. Бих искал да се присъединя към списъците в общото поле, но един от списъците идва от SQL динамична опорна точка, така че всички имена на колони от този списък, с изключение на полето за свързване, имат неизвестни имена на колони. Въпросът ми е как да намеря тези имена на колони, за да мога да създам новия списък?

Пример

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

studentCollection е колекция от ученици

Използвам class ReportRanking тук като пример. Това е dynamic клас, който се връща от съхранена процедура, която използва динамична опорна точка. Така че не знам имената на колоните предварително. Използвам TestScore-1, TestScore-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   |
+-----------+-----------+----------+---------+---------+----------+---------+

person webworm    schedule 18.11.2016    source източник
comment
Каква е крайната цел? Искате да сериализирате резултата в json или нещо подобно?   -  person Evk    schedule 22.11.2016
comment
Крайната цел би била да имам една колекция, която мога да повторя и да създам отчет от (Excel, CSV и т.н.)   -  person webworm    schedule 22.11.2016
comment
Тъй като не знаете свойствата по време на компилиране, това ще бъде списък с неизвестни типове (обекти, динамични и т.н.). добре ли е   -  person Evk    schedule 22.11.2016
comment
Да, защото мога да повторя колекцията в този момент. След като ги събера заедно, нямам проблем с преминаването през колекцията, за да изградя заглавките на колоните и да взема данните.   -  person webworm    schedule 22.11.2016


Отговори (2)


Бих комбинирал вашите обекти в един 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;
}
person Evk    schedule 22.11.2016
comment
Както споменах в първоначалния въпрос, знам какви могат да бъдат имената на колоните за резултатите от теста. Те могат да бъдат (история, математика, правопис) или (химия, биология, математика) или (плетене на кошници, изкуство, музика) или друга комбинация. - person webworm; 22.11.2016
comment
@webworm, но кодът в отговор не се интересува от имена. Тези имена там са само за пример. - person Evk; 22.11.2016
comment
Въпреки това select Combine(student, testResult) може да е точно това, от което имах нужда. Не знаех за combine. Сега разбирам какво казваш за имената. - person webworm; 22.11.2016
comment
Когато опитам това локално, получавам [{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}] за изхода на конзолата - person webworm; 23.11.2016
comment
Можете ли да публикувате тестови данни, които използвате? С тестови данни от отговор работи добре. - person Evk; 23.11.2016
comment
Ето един пример от първия ред, когато разглеждате колекцията, върната от Dapper. [0] {{DapperRow, StudentID = '634524', HISTORY = '1', BIOLOGY = '4', ALGEBRA = '16', GEOMETRY = '9', SPANISH = '12', FRENCH = '14', HEALTH = '8', COOKING = '3', ANCIENTSTUDIES = '10', WRITING = '6', MECHANICS = '7', MODELING = '11', ELECTRONICS = '2', PROGRAMMING = '5', HARDWARE = '15', DATABASES = '13'}} object {Dapper.SqlMapper.DapperRow} - person webworm; 23.11.2016
comment
Докато преминавам през метода Combine, той никога не влиза в съдържанието на foreach loop. Никога не удря if (prop.CanRead && !exp.ContainsKey(prop.Name)) - person webworm; 23.11.2016
comment
Бихте ли могли да демонстрирате това, като промените примера? Ако можете и работи, наградата е ваша! Съжалявам, че не споменах Dapper в оригиналния пост. Опитвах се да опростя примера, доколкото е възможно. Предполагам, че отидох твърде далеч. - person webworm; 23.11.2016
comment
Добре, промених отговора, за да взема това предвид. - person Evk; 23.11.2016
comment
Това работи! Дава ми необходимите данни. Благодаря много за примера. Трябва да изчакам 16 часа, за да присвоя наградата - person webworm; 23.11.2016

Бих използвал отражение, за да получа стойността, ако не знаете именуването на това какво ще бъде свойството на класа до момента на изпълнение.

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();
    }
  }
person djangojazz    schedule 18.11.2016
comment
Виждам как използвате отражение, за да получите резултата, но как да хвана заглавката на колоната с резултата, която ще съдържа името на класа (History, Algebra, Geometry и Biology)? - person webworm; 18.11.2016
comment
Объркан съм, ако знаете името, просто бихте използвали „testResult.History“ или „testResult.Algebra“. Ако НЕ знаете името, но знаете позицията, която идва в клас POCO обект, бихте направили props[1] за 2-ра позиция след StudentID и т.н. Ако просто искате да повторите „История“ или „Алгебра“ заедно с тяхната стойност. Това е доста просто. Нека актуализирам отговора си и можете да видите. - person djangojazz; 18.11.2016
comment
Съжалявам за объркването ... НЕ ЗНАМ имената на класовете по време на компилация (История, Алгебра, Биология, технологии), но знам позицията в POCO обекта, тъй като знам колко ще има и редът не е важен. - person webworm; 18.11.2016
comment
Моят актуализиран пример трябва да работи за вас, ако просто искате да видите името заедно със стойността. - person djangojazz; 18.11.2016
comment
Получаване на следната грешка при опит за това Microsoft.CSharp.RuntimeBinder.RuntimeBinderException : Cannot perform runtime binding on a null reference - person webworm; 21.11.2016
comment
Можете ли да редактирате вашия пример? Кодът, който имате там, не работи. - person webworm; 22.11.2016
comment
Със сигурност актуализиран. Грешно казах да използвам нулев оператор за обединяване, тъй като наистина той така или иначе ще се взриви при индексирането. Имате нужда от троичен оператор за присвояване. - person djangojazz; 22.11.2016
comment
Благодаря! Ако проработи, ще се радвам да възложа наградата на вас. В момента работя по проблем с производството, но ще опитам по-късно този следобед. - person webworm; 22.11.2016
comment
testResultCollection.First().GetType() води до NULL. Това е мястото, където се хвърля изключението. - person webworm; 22.11.2016
comment
Няма проблем, имайте предвид също, че присвоявам схемата за наблюдение на отражение с първия обект във вашата колекция. Ако имате нужда от променлива 'props', за да бъде нещо, при което 2-ри или 3-ти обект може да са различни типове, ще трябва да преминете през колекцията и да определите обекта с най-много свойства като победител. При условие, разбира се, че другите се подчиняват на схемата, но им липсват само някои свойства. В противен случай, ако имате един обект от (низ, низ, int) и след това следващият е (низ, обект, дата и час, низ и т.н.), ще се взриви. - person djangojazz; 22.11.2016
comment
Актуализираният ми пример в отговора работи на моята кутия, тъй като не трябва да извиква този бит код, ОСВЕН АКО свойствата имат повече от 2 типа, което в примера никога няма да има. - person djangojazz; 22.11.2016
comment
Нека продължим тази дискусия в чата. - person webworm; 22.11.2016