Преобразовать таблицу с различными реляционными значениями в столбцы Excel - PullRequest
0 голосов
/ 09 декабря 2018

У меня есть эти таблицы:

Категория

CategoryId
CategoryTitle
...
ICollection<Article> Articles  

Каждая категория может иметь несколько статей:

Артикул

ArticleId
ArticleTitle  
NumberOfComment
NumberOfView
...
ICollection<ArticleReview> Reviews 

И у каждой статьи есть несколько отзывов от одного пользователя:

ArticleReview

ArticleReviewId 
ReviewPoint
ArticleId
ReviewerId

Я пытаюсь экспортировать отчет Excel с использованием EPPlus пакет
Вот мой ExcelExport класс:

public class excelExport 
{
    public string ArticleTitle { get; set; }

    public int NumberOfComment { get; set; }
    public int NumberOfReviews { get; set; }
    public List<ResearchReviewReport> Reviews { get; set; }
}

public class ArticleReviewReport
{
    public string Reviewer { get; set; }
    public int ReviewPoint { get; set; }
}

Примечание : Так как количество рецензий на статью другое, я использую отношение один ко многим , но в конечном итоге все они должны быть сведены в один ряд.Теперь я создаю новый класс, который не принадлежит базе данных, и передаю этот класс ExcelPackage классу для генерации xlsx в качестве вывода:

ExcelExport

ArticleTitle 
Reviewer1Point
Reviewer2Point
............
ReviewerNPoint
ReviewersAvaragePoint
NumberOfComment
NumberOfView  

Как я могу заполнить класс ExcelExport, используя еще 3 класса?


Редактировать
Вот мой ожидаемый результат Excel
Excel final Export

Одной из моих проблем является динамическое изменение столбца Point of Point,
для одной статьи может быть 3 столбца (как на верхнем изображении), но в другой может быть 4 или 5 ReviewerТочка.
Edit2
я забыл сказать, что есть какой-то вопрос для каждой статьи, и рецензент отвечает на каждый вопрос, поэтому, если у нас есть 3 вопроса, и есть 2 рецензента, статья имеет 6 ArticleReviewя должен получить среднее значение ArticleReview для каждого рецензента и поместить его в одну ячейку

Ответы [ 5 ]

0 голосов
/ 16 декабря 2018

Для простоты я предполагаю, что вы используете следующие упрощенные модели и опишете решение.Вы можете легко адаптировать его к своим моделям:

public class Article
{
    public string Title { get; set; }
    public DateTime Date { get; set; }
    public List<Review> Reviews { get; set; }
}
public class Review
{
    public int Points { get; set; }
}

Теперь мы собираемся сгенерировать следующий вывод, имеющий динамическое количество столбцов рецензента, в зависимости от входных данных:

enter image description here

Решение

Достаточно создать функцию для преобразования List<Article> в DataTable.Чтобы создать такой DataTable, для каждого свойства Article добавьте новый столбец.Затем найдите максимальное количество Reviews списка и добавьте это количество столбцов.Затем в цикле для каждого Article, включая его список Review, создайте массив объектов и добавьте в DataTable.Очевидно, что вы также можете выполнять вычисления на полях.

Вот функция:

public DataTable GetData(List<Article> list)
{
    var dt = new DataTable();
    dt.Columns.Add("Title", typeof(string));
    dt.Columns.Add("Date", typeof(DateTime));
    var max = list.Max(x => x.Reviews.Count());
    for (int i = 0; i < max; i++)
        dt.Columns.Add($"Reviewer {i + 1} Points", typeof(int));
    foreach (var item in list)
        dt.Rows.Add(new object[] { item.Title, item.Date }.Concat(
            item.Reviews.Select(x => x.Points).Cast<object>()).ToArray());
    return dt;
}

Тестовые данные

Вот мои тестовые данные:

var list = new List<Article>
{
    new Article(){
        Title = "Article 1", Date = new DateTime(2018,1,1),
        Reviews = new List<Review> {
            new Review(){Points=10},
        },
    },
    new Article(){
        Title = "Article 2", Date = new DateTime(2018,1,2),
        Reviews = new List<Review> {
            new Review(){Points=10}, new Review(){Points=9}, new Review(){Points=8},
        },
    },
    new Article(){
        Title = "Article 3", Date = new DateTime(2018,1,3),
        Reviews = new List<Review> {
            new Review(){Points=9},
        },
    },
};
0 голосов
/ 14 декабря 2018
public IEnumarable<ExcelExport> GetExcelExports()
{
    return _context.Articles.Select(a => new ExcelExport
    {
       ArticleTitle = a.ArticleTitle,
       NumberOfComment = a.NumberOfComment,
       NumberOfReviews = a.NumberOfView,
       Reviewer1Point = a.Reviews.Any(e => e.ReviewerId = 1) ? a.Reviews.Where(e => e.ReviewerId = 1).Sum(e => e.ReviewPoint) : 0,
       Reviewer2Point = a.Reviews.Any(e => e.ReviewerId = 2) ? a.Reviews.Where(e => e.ReviewerId = 2).Sum(e => e.ReviewPoint) : 0,
       ....
       ReviewerNPoint = a.Reviews.Any(e => e.ReviewerId = N) ? a.Reviews.Where(e => e.ReviewerId = N).Sum(e => e.ReviewPoint) : 0
     });
}

Вы также должны .Include (e => e.Reviews), если вы используете отложенную загрузку.

0 голосов
/ 13 декабря 2018

Как заполнить класс excelExport другими 3 классами?

На основе описанных связей вы можете перечислить каждое свойство внутри ExcelExport класса, как показано ниже:

NumberOfComment равно article.NumberOfComment для каждой статьи! Если не используется другая таблица с именем ArticleComment и используется свойство навигации внутри Article класса (с использованием public virtual ICollection<ArticleComment> Comments { get; set;}), то подсчитайте количество комментариев с помощью article.Comments.Count().

NumberOfReviews равно article.Reviews.Count() для каждой записи статьи.

Reviews для каждой статьи может выглядеть примерно так:

article.Reviews.Select(s => new ArticleReviewReport { 
       Reviewer = r.ReviewerId, // user id
       ReviewPoint = r.ReviewPoint
});

ItПохоже, вы также должны добавить еще одно свойство в свой класс ExcelExport, чтобы отобразить ReviewersAvaragePoint и перечислить его следующим образом:

var reviewPoints = article.Reviews.Select(s => s.ReviewPoint);
ReviewersAvaragePoint = reviewPoints.Sum()/reviewPoints.Count();

Редактировать на основе редактирования OP

Используя List из ArticleReviewReport (например, List<ArticleReviewReport> Reviews), вы получаете гибкий массив (динамические столбцы) для представления в соответствующем формате. Отсутствующая часть создает динамические столбцы на основе Distinct ReviewerId, извлеченного из таблицы ArticleReview.Что-то вроде следующего для целых статей:

var allReviewers = db.articleReviews/*condition*/.Select(s => s.ReviewerId).Distinct();

Теперь вы можете назначить каждый ArticleReviewReport соответствующему столбцу.Использование List<Dictionary<string, string>> было бы хорошим типом данных для Reviews члена.

0 голосов
/ 14 декабря 2018

Надеюсь, это то, что вы ищете.Из того, что я могу расшифровать, цель состоит в том, чтобы «сгладить» данные из данных «классов».Я собираюсь отказаться от экспорта в Excel, так как это, похоже, другая проблема.Существуют «классы», я предполагаю, что метод вернул DataTable или любой тип «коллекции», который вы пожелаете.Я предполагаю, что это упростит процесс экспорта в Excel.

В классе Catergory он имеет «коллекцию» Article’s.Каждый Article представляет «строку» в коллекции (электронная таблица Excel).Каждый Article имеет «коллекцию» ArticleReviews, называемую Reviews.Как вы заявили…

Одна из моих проблем - динамическое изменение столбца «Точка обзора», поскольку в одной статье может быть 3 столбца (как на верхнем изображении), а в другой может быть 4 или 5 Точек обзора.

Похоже, что для каждого Article может быть много рецензентов. Кроме того, не все рецензенты обязательно «рецензируют» все статьи.Учитывая это и требование «сгладить» эти данные, будет означать создание столбца для «каждого» рецензента.Кроме того, я предполагаю, что в списке есть только рецензенты, которые рецензировали одну из статей, иначе было бы просто создать колонку для каждого рецензента.Я предполагаю, что цель состоит в том, чтобы иметь только те столбцы, в которых рецензент «рецензировал» хотя бы одну (1) статью.

С учетом сказанного, я предполагаю, что первая проблема состоит в том, чтобы выяснить «сколько» столбцовпотребность в рецензентах и ​​«что», имена этих рецензентов.Нам понадобится какой-то способ определить, «какой» столбец принадлежит рецензенту.Я использую имя Reviewer, чтобы определить правильный столбец.Итак, как нам найти рецензентов ...

Удобно, что класс Category имеет список Artlicle s.Если был создан метод, который просматривал каждую статью, а затем проходил каждую рецензию статьи, собирал всех рецензентов и игнорировал дубликаты… это должно дать нам список «рецензентов», для которых нам нужно добавить столбцы.Если метод перенастроил список Reviewer, мы могли бы использовать это, чтобы определить не только, сколько столбцов нам нужно, но и каковы должны быть имена этих столбцов.

Одна из возможных проблем заключается в том, что порядок столбцов может быть непредсказуемым.В зависимости от того, какая статья является первой, собирается определить порядок столбцов.Поэтому я рекомендую некоторую «сортировку» столбцов, чтобы поддерживать некоторый порядок.

Я добавил класс Reviewer, который должен помочь в сортировке и сравнении имен столбцов.Это простой Reviewer класс, как показано ниже.Обратите внимание на метод compareTo, который используется сортировкой.Сортировка по идентификатору рецензентов.Это сохранит тот же порядок столбцов.

public class Reviewer : IComparable<Reviewer> {

  public int ReviewerID { get; set; }
  public string ReviewerName { get; set; }

  public Reviewer() {
  }

  public Reviewer(int reviewerID, string reviewerName) {
    ReviewerID = reviewerID;
    ReviewerName = reviewerName;
  }

  public override string ToString() {
    return "ReviewerID: " + ReviewerID.ToString();
  }

  public override bool Equals(object obj) {
    return this.ReviewerName.Equals(((Reviewer)obj).ReviewerName);
  }

  public override int GetHashCode() {
    return ReviewerName.GetHashCode();
  }

  public int CompareTo(Reviewer other) {
    return this.ReviewerID.CompareTo(other.ReviewerID);
  }
}

Это повлияет на класс ArticleReview, и в нем потребуются некоторые изменения.Некоторые переменные кажутся ненужными, и отображаются только необходимые переменные.Основным изменением является объект Reviewer сверху для определения рецензента.

public class ArticleReview {

  public long ArticleId { get; set; }
  public Reviewer TheReviewer { get; set; }
  public int ReviewPoint { get; set; }

  public ArticleReview() {
  }

  public ArticleReview (long articleId, Reviewer reviewerId, int reviewPoint) {
    ArticleId = articleId;
    TheReviewer = reviewerId;
    ReviewPoint = reviewPoint;
  }
}

Следующий класс Article.Он содержит все рецензии на эту статью.Похоже, что есть столбец под названием «Средняя точка».Это выглядит как «вычисленное» значение из обзоров.Поэтому я предполагаю, что для класса Article было бы удобно «вычислить» это значение для нас.Здесь есть все отзывы ... все, что нужно, это сложить все пункты и поделить на количество отзывов.Этот метод добавлен в класс Article.

public class Article {
  public long ArticleId { get; set; }
  public string ArticleTitle { get; set; }
  public int NumberOfComment { get; set; }
  public int NumberOfView { get; set; }
  public virtual ICollection<ArticleReview> Reviews { get; set; }

  public Article() {
  }

  public Article(long articleId, string articleTitle, int numberOfComment, int numberOfView, ICollection<ArticleReview> reviews) {
    ArticleId = articleId;
    ArticleTitle = articleTitle;
    NumberOfComment = numberOfComment;
    NumberOfView = numberOfView;
    Reviews = reviews;
  }

  public decimal GetAverage() {
    if (Reviews.Count <= 0)
      return 0;
    decimal divisor = Reviews.Count;
    int totPoints = 0;
    foreach (ArticleReview review in Reviews) {
      totPoints += review.ReviewPoint;
    }
    return totPoints / divisor;
  }
}

Наконец, класс Category содержит все Article s.Этот класс - то, где мы должны сделатьвсе вещи столбца, описанные ранее.Первая часть - это получение List<Reviewer> без дубликатов.Для этого потребуется просмотреть все статьи, а затем просмотреть все обзоры в каждой статье.В этом процессе мы можем исследовать «рецензентов» и создать недублированный список всех пользователей.Код создает новый пустой List<Reviewer>, затем проходит по каждой статье, цикл по каждому обзору.Выполняется проверка, чтобы убедиться, что «рецензент» уже есть в списке, если нет, то добавьте их, в противном случае игнорируйте дубликат «рецензента». Список отсортирован для поддержания порядка столбцов, а затем возвращается.

Полагаю, этот список можно было бы использовать разными способами для решения загадки «столбцы».В этом примере в класс Category добавлен другой метод.Метод GetDataTable возвращает DataTable из данных в статьях.Для начала в таблицу добавляются первые четыре столбца: «Заголовок», «#ofView», «#ofComment» и «Средняя точка». Далее следует цикл по всем рецензентам, чтобы добавить столбцы рецензентов.Имя рецензента используется в качестве имени столбца.Вот как мы определяем, какой столбец принадлежит рецензенту при добавлении данных.

Наконец, цикл для каждого Article добавляет данные.Каждая статья создает новую строку.Первые три столбца в строке могут быть установлены ... Заголовок, просмотр, комментарий и Среднее.Далее мы перебираем все отзывы.Для каждого отзыва targetName задается имя рецензента, затем цикл по каждому столбцу, пока он не найдет имя столбца, соответствующее имени рецензента.Найдя, мы знаем, что это столбец, к которому принадлежат данные. Добавьте значение и вырвитесь из цикла столбцов и получите следующий обзор.

public class Category {
  public long CategoryId { get; set; }
  public string CategoryTitle { get; set; }
  //...
  public virtual ICollection<Article> Articles { get; set; }

  public Category() {
  }

  public Category(long categoryId, string categoryTitle, ICollection<Article> articles) {
    CategoryId = categoryId;
    CategoryTitle = categoryTitle;
    Articles = articles;
  }

  public DataTable GetDataTable() {
    List<Reviewer> allReviewers = GetNumberOfReviewers();
    DataTable dt = new DataTable();
    dt.Columns.Add("Title", typeof(string));
    dt.Columns.Add("#ofView", typeof(long));
    dt.Columns.Add("#ofComment", typeof(long));
    dt.Columns.Add("Average point", typeof(decimal));
    foreach (Reviewer reviewer in allReviewers) {
      dt.Columns.Add(reviewer.ReviewerName, typeof(long));
    }
    foreach (Article article in Articles) {
      DataRow newRow = dt.NewRow();
      newRow["Title"] = article.ArticleTitle;
      newRow["#ofView"] = article.NumberOfView;
      newRow["#ofComment"] = article.NumberOfComment;
      newRow["Average point"] = article.GetAverage();
      foreach (ArticleReview review in article.Reviews) {
        string targetName = review.TheReviewer.ReviewerName;
        for (int i = 4; i < dt.Columns.Count; i++) {
          if (targetName == dt.Columns[i].ColumnName) {
            newRow[review.TheReviewer.ReviewerName] = review.ReviewPoint;
            break;
          }
        }
      }
      dt.Rows.Add(newRow);
    }
    return dt;
  }

  private List<Reviewer> GetNumberOfReviewers() {
    // we need a list of all the different reviewers
    List<Reviewer> reviewers = new List<Reviewer>();
    foreach (Article article in Articles) {
      foreach (ArticleReview review in article.Reviews) {
        if (!reviewers.Contains(review.TheReviewer)) {
          reviewers.Add(review.TheReviewer);
        }
      }
    }
    reviewers.Sort();
    return reviewers; 
  }
}

Собрав все это вместе, приведенный ниже код создает некоторые данныепоказывать.Затем DataTable используется от DataSource до DataGridView.Надеюсь, это поможет.

DataTable dt;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  Category cat = new Category();
  cat.CategoryId = 1;
  cat.CategoryTitle = "Category 1";
  cat.Articles = GetArticles();
  dt = cat.GetDataTable();
  dataGridView1.DataSource = dt;
}

private List<Article> GetArticles() {
  List<Article> articles = new List<Article>();
  Article art = new Article(1, "Article 1 Title", 10, 1200, GetReviews(1));
  articles.Add(art);
  art = new Article(2, "Article 2 Title", 32, 578, GetReviews(2));
  articles.Add(art);
  art = new Article(3, "Article 3 Title", 15, 132, GetReviews(3));
  articles.Add(art);
  art = new Article(4, "Article 4 Title", 13, 133, GetReviews(4));
  articles.Add(art);
  art = new Article(5, "Article 5 Title", 55, 555, GetReviews(5));
  articles.Add(art);
  art = new Article(6, "Article 6 Title", 0, 0, GetReviews(6));
  articles.Add(art);
  return articles;
}

private ICollection<ArticleReview> GetReviews(int reviewId) {
  ICollection<ArticleReview> reviews = new List<ArticleReview>();
  ArticleReview ar;
  Reviewer Reviewer1 = new Reviewer(1, "Reviewer 1");
  Reviewer Reviewer2 = new Reviewer(2, "Reviewer 2");
  Reviewer Reviewer3 = new Reviewer(3, "Reviewer 3");
  Reviewer Reviewer4 = new Reviewer(4, "Reviewer 4");
  Reviewer Reviewer5 = new Reviewer(5, "Reviewer 5");
  Reviewer Reviewer6 = new Reviewer(6, "Reviewer 6");

  switch (reviewId) {
    case 1:
      ar = new ArticleReview(1, Reviewer1, 15);
      reviews.Add(ar);
      ar = new ArticleReview(1, Reviewer2, 35);
      reviews.Add(ar);
      ar = new ArticleReview(1, Reviewer3, 80);
      reviews.Add(ar);
      ar = new ArticleReview(1, Reviewer5, 55);
      reviews.Add(ar);
      ar = new ArticleReview(1, Reviewer6, 666);
      reviews.Add(ar);
      break;
    case 2:
      ar = new ArticleReview(2, Reviewer1, 50);
      reviews.Add(ar);
      ar = new ArticleReview(2, Reviewer2, 60);
      reviews.Add(ar);
      ar = new ArticleReview(2, Reviewer3, 40);
      reviews.Add(ar);
      break;
    case 3:
      ar = new ArticleReview(3, Reviewer1, 60);
      reviews.Add(ar);
      ar = new ArticleReview(3, Reviewer2, 60);
      reviews.Add(ar);
      ar = new ArticleReview(3, Reviewer3, 80);
      reviews.Add(ar);
      break;
    case 4:
      ar = new ArticleReview(4, Reviewer1, 30);
      reviews.Add(ar);
      ar = new ArticleReview(4, Reviewer2, 70);
      reviews.Add(ar);
      ar = new ArticleReview(4, Reviewer3, 70);
      reviews.Add(ar);
      break;
    case 5:
      ar = new ArticleReview(5, Reviewer3, 44);
      reviews.Add(ar);
      break;
    case 6:
      break;
    default:
      break;
  }
  return reviews;
}

Используя EPPlus, ниже приведен один из способов использования DataTable выше и экспорта DataTable в лист Excel.

private void btn_ExportToExcel_Click(object sender, EventArgs e) {
  using (var p = new ExcelPackage()) {
    var ws = p.Workbook.Worksheets.Add("MySheet");
    ws.Cells["A1"].LoadFromDataTable(dt, true);
    p.SaveAs(new FileInfo(@"D:\Test\ExcelFiles\EpplusExport.xlsx"));
  }
}

enter image description here

0 голосов
/ 11 декабря 2018

Предполагая, что у вас есть следующая модель:

public class Category
{
    public long CategoryId { get; set; }
    public string CategoryTitle { get; set; }
    public virtual ICollection<Article> Articles { get; set; }
}

public class Article
{
    public long ArticleId { get; set; }
    public long CategoryId { get; set; }
    public string ArticleTitle { get; set; }
    public int NumberOfComment { get; set; }
    public int NumberOfView { get; set; }
    public virtual Category Category { get; set; }
    public virtual ICollection<ArticleReview> Reviews { get; set; }
}
public class ArticleReview
{
    public long ArticleReviewId { get; set; }
    public long ArticleId { get; set; }
    public string ReviewerId { get; set; }
    public int ReviewPoint { get; set; }
    public virtual Article Article { get; set; }
}
public class ExcelExport
{
    public string ArticleTitle { get; set; }
    public int NumberOfComment { get; set; }
    public int NumberOfReviews { get; set; }
    public List<ArticleReviewReport> Reviews { get; set; }
}

public class ArticleReviewReport
{
    public string Reviewer { get; set; }
    public int ReviewPoint { get; set; }
}

В конце концов у вас будет список ExcelExport, и запрос должен выглядеть так (_context является экземпляром вашего объекта DbContext):

public List<ExcelExport> GetExcelExports()
{
    return _context.Articles.Select(a => new ExcelExport
    {
        ArticleTitle = a.ArticleTitle,
        NumberOfComment = a.NumberOfComment,
        NumberOfReviews = a.NumberOfView,
        Reviews = a.Reviews.Select(r => new ArticleReviewReport
        {
            Reviewer = r.ReviewerId,
            ReviewPoint = r.ReviewPoint
        }).ToList()
    }).ToList();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...