将具有不同关系值的表转换为excel列
Posted
技术标签:
【中文标题】将具有不同关系值的表转换为excel列【英文标题】:Convert a table with different relational values to excel columns 【发布时间】:2019-05-10 13:32:38 【问题描述】:我有这些表:
类别
CategoryId
CategoryTitle
...
ICollection<Article> Articles
每个分类可以有几篇文章:
文章
ArticleId
ArticleTitle
NumberOfComment
NumberOfView
...
ICollection<ArticleReview> Reviews
每篇文章都有一些用户的评论:
文章评论
ArticleReviewId
ReviewPoint
ArticleId
ReviewerId
我正在尝试使用 EPPlus 包导出 excel 报告
这是我的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 作为输出:
Excel导出
ArticleTitle
Reviewer1Point
Reviewer2Point
............
ReviewerNPoint
ReviewersAvaragePoint
NumberOfComment
NumberOfView
如何使用另外 3 个类填充 ExcelExport
类?
编辑 这是我预期的 excel 输出
我的一个问题是 Reviewer Point 列是动态更改的, 一篇文章可能有 3 列(如上图),但另一篇文章可能有 4 或 5 个评论者点。Edit2 我忘了说每篇文章都有一些问题和每个问题的审稿人回答,所以如果我们有 3 个问题并且有 2 个审稿人,文章有 6 个 ArticleReview,我应该得到每个审稿人的平均 ArticleReview 并将其放在单个单元格中
【问题讨论】:
【参考方案1】:为简单起见,我假设您正在使用以下简化模型并将描述解决方案。您可以轻松地使其适应您的模型:
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;
现在我们将根据输入数据生成以下输出,具有动态数量的审阅者列:
解决方案
创建一个将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,
,
,
;
【讨论】:
更新了使用Concat
连接数组的方法。
为什么不使用 TypeDescriptor.GetProperties 添加列?
可以使用。这只是一个示例作为起点。只要保留我的,如果您使用TypeDescriptor.GetProperties
,您需要排除列表属性。
我用这篇文章导出excel,c-sharpcorner.com/article/export-to-excel-in-asp-net-mvc这个单独创建列标题并传递给ExportExcel方法
我明白了。现在你有了DataTable
(解决了最大的问题,处理列表中可变数量的项目并得到一个平坦的结果。)下一步(这是最简单的部分)是将DataTable
导出到excel。【参考方案2】:
假设您有以下模型:
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();
【讨论】:
ArticleReviewReport 如何在单行中为每篇文章扁平化?每个表格也是独立的 这完全破旧了,我想在单列上显示每条评论,但它会返回它的列表!【参考方案3】:我希望这是您正在寻找的。据我所知,目标是“扁平化”给定“类”中的数据。我将放弃导出到 Excel,因为这似乎是一个不同的问题。 “类”存在,我猜是返回 DataTable
或您想要的任何“集合”类型的方法。我猜这会使导出到 Excel 时变得更容易。
在Catergory
类中,它有一个Article’s
的“集合”。每个Article
代表集合(Excel 电子表格)中的一个“行”。每个Article
都有一个ArticleReviews
的“集合”,称为Reviews
。正如你所说……
我的一个问题是 Reviewer Point 列是动态更改的, 一篇文章可能有 3 列(如上图),但在 另一个可能是 4 或 5 个 Reviewer Point。
听起来每个Article
可能有很多审稿人,此外,并非所有审稿人都会“审阅”所有文章。鉴于这一点以及“扁平化”这些数据的要求,这意味着为“每个”审阅者创建一个列。另外,我假设只列出了审过一篇文章的审稿人,否则,为每个审稿人创建一个列会很简单。我猜目标是只有评论者“评论”了至少一 (1) 篇文章的专栏。
话虽如此,我猜第一个问题是弄清楚我们需要为审稿人提供“多少”列,以及这些审稿人的姓名是“什么”。我们需要一些方法来识别“哪个”列属于哪个审阅者。我使用Reviewer
名称来识别正确的列。那么我们如何找到审稿人……
Category
类有一个Artlicle
s 列表很方便。如果创建了一个方法来遍历每篇文章,然后遍历文章的每条评论并收集所有评论者并忽略重复项……这应该为我们提供需要为其添加列的“评论者”列表。如果该方法重新调整了Reviewer
的列表,我们可以使用它来确定我们需要多少列,以及这些列的名称应该是什么。
其中一个可能的问题是列顺序可能无法预测。根据首先是哪篇文章,将确定列的顺序。因此,我建议对列进行一些“排序”以保持某种顺序。
我添加了一个类Reviewer
,它应该有助于对列名进行排序和比较。这是一个简单的Reviewer
类,如下所示。请注意排序使用的compareTo
方法。它按审阅者 ID 排序。这将保持相同的列顺序。
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>
然后循环遍历每篇文章,循环遍历每条评论。检查“reviewer”是否已经在列表中,如果没有,则添加它们,否则忽略重复的“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"));
【讨论】:
【参考方案4】:如何使用另外 3 个类填充 excelExport 类?
根据所描述的关系,您可以枚举ExcelExport
类中的每个属性,如下所示:
NumberOfComment
等于 article.NumberOfComment
对于每个文章条目! 除非您使用另一个名为ArticleComment
的表并利用Article
类中的导航属性(使用public virtual ICollection<ArticleComment> Comments get; set;
),然后用@987654327 计算cmets 的数量@。
NumberOfReviews
等于每个文章条目的article.Reviews.Count()
。
每篇文章的Reviews
可以是以下内容:
article.Reviews.Select(s => new ArticleReviewReport
Reviewer = r.ReviewerId, // user id
ReviewPoint = r.ReviewPoint
);
看来您还必须在 ExcelExport
类中添加另一个属性以显示 ReviewersAvaragePoint
并像这样枚举:
var reviewPoints = article.Reviews.Select(s => s.ReviewPoint);
ReviewersAvaragePoint = reviewPoints.Sum()/reviewPoints.Count();
根据 OP 的编辑进行编辑
通过使用ArticleReviewReport
中的List
(例如List<ArticleReviewReport> Reviews
),您可以拥有一个灵活的数组(动态列)以相应的格式呈现。 缺少的部分是根据从 ArticleReview
表中提取的 Distinct ReviewerId
创建动态列。整篇文章如下所示:
var allReviewers = db.articleReviews/*condition*/.Select(s => s.ReviewerId).Distinct();
现在您可以将每个ArticleReviewReport
分配给相应的列。对于Reviews
成员,使用List<Dictionary<string, string>>
将是一个很好的数据类型。
【讨论】:
【参考方案5】: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)。
【讨论】:
以上是关于将具有不同关系值的表转换为excel列的主要内容,如果未能解决你的问题,请参考以下文章