检测具有相同子项的实体

我有两个实体, ClassStudent ,以多对多关系链接。

从外部应用程序导入数据时,遗憾的是一些类是一式两份创建的。 “重复”类具有不同的名称,但是相同的主题和相同的学生。

例如:

{Id = 341,Title =’10rs / PE1a’,SubjectId = 60,Students = {Jack,Bill,Sarah}}

{Id = 429,Title =’10rs / PE1b’,SubjectId = 60,Students = {Jack,Bill,Sarah}}

匹配这些重复类的名称没有一般规则,因此识别这两个类是重复的唯一方法是它们具有相同的SubjectIdStudents

我想使用LINQ来检测所有重复项(并最终合并它们)。 到目前为止,我尝试过:

 var sb = new StringBuilder(); using (var ctx = new Ctx()) { ctx.CommandTimeout = 10000; // Because the next line takes so long! var allClasses = ctx.Classes.Include("Students").OrderBy(o => o.Id); foreach (var c in allClasses) { var duplicates = allClasses.Where(o => o.SubjectId == c.SubjectId && o.Id != c.Id && o.Students.Equals(c.Students)); foreach (var d in duplicates) sb.Append(d.LongName).Append(" is a duplicate of ").Append(c.LongName).Append("
"); } } lblResult.Text = sb.ToString();

这不好,因为我得到错误:

NotSupportedException :无法创建“TeachEDM.Student”类型的常量值。 在此上下文中仅支持基本类型(例如Int32,String和Guid’)。

显然它不喜欢我在LINQ中匹配o.SubjectId == c.SubjectId

此外,这似乎是一种可怕的方法,而且非常慢。 对数据库的调用需要5分钟以上。

我真的很感激一些建议。

SubjectId的比较不是问题,因为c.SubjectId是基本类型的值( int ,我猜)。 这个例外抱怨Equals(c.Students)c.Students是一个常数(关于查询duplicates )但不是原始类型。

我也会尝试在内存中进行比较而不是在数据库中进行比较。 当你开始第一个foreach循环时,你正在将整个数据加载到内存中:它执行查询allClasses 。 然后在循环内部将IQueryable allClasses扩展为IQueryable duplicates ,然后在内部foreach循环中执行。 这是外部循环的每个元素的一个数据库查询! 这可以解释代码的糟糕表现。

所以我会尝试在内存中执行第一个foreach的内容。 为了比较Students列表,有必要逐个元素进行比较,而不是对学生集合的引用,因为它们肯定是不同的。

 var sb = new StringBuilder(); using (var ctx = new Ctx()) { ctx.CommandTimeout = 10000; // Perhaps not necessary anymore var allClasses = ctx.Classes.Include("Students").OrderBy(o => o.Id) .ToList(); // executes query, allClasses is now a List, not an IQueryable // everything from here runs in memory foreach (var c in allClasses) { var duplicates = allClasses.Where( o => o.SubjectId == c.SubjectId && o.Id != c.Id && o.Students.OrderBy(s => s.Name).Select(s => s.Name) .SequenceEqual(c.Students.OrderBy(s => s.Name).Select(s => s.Name))); // duplicates is an IEnumerable, not an IQueryable foreach (var d in duplicates) sb.Append(d.LongName) .Append(" is a duplicate of ") .Append(c.LongName) .Append("
"); } } lblResult.Text = sb.ToString();

按名称排序序列是必要的,因为我相信, SequenceEqual比较SequenceEqual长度,然后将元素0与元素0进行比较,然后将元素1与元素1进行比较,依此类推。


编辑您的评论第一个查询仍然很慢。

如果你有1300个课程,每个课程有30个学生,那么渴望加载( Include )的表现可能会受到数据库和客户端之间传输的数据相乘的影响。 这里解释了这一点: 我可以在EntityFramework中的ObjectSet上使用多少Include以保持性能? 。 查询很复杂,因为它需要在类和学生之间进行JOIN ,并且对象实现也很复杂,因为EF必须在创建对象时过滤掉重复的数据。

另一种方法是在第一个查询中只加载没有学生的类,然后在循环内逐个加载学生。 它看起来像这样:

 var sb = new StringBuilder(); using (var ctx = new Ctx()) { ctx.CommandTimeout = 10000; // Perhaps not necessary anymore var allClasses = ctx.Classes.OrderBy(o => o.Id).ToList(); // <- No Include! foreach (var c in allClasses) { // "Explicite loading": This is a new roundtrip to the DB ctx.LoadProperty(c, "Students"); } foreach (var c in allClasses) { // ... same code as above } } lblResult.Text = sb.ToString(); 

在这个例子中你将有1 + 1300个数据库查询,而不是只有一个,但你不会有急切加载时出现的数据乘法,而且查询更简单(类和学生之间没有JOIN )。

这里解释了明显的加载:

  • http://msdn.microsoft.com/en-us/library/bb896272.aspx
  • 对于POCO(也适用于EntityObject派生实体): http : //msdn.microsoft.com/en-us/library/dd456855.aspx
  • 对于EntityObject派生实体,您还可以使用EntityCollectionLoad方法: http : //msdn.microsoft.com/en-us/library/bb896370.aspx

如果您使用Lazy Loading,则不需要使用LoadProperty的第一个foreach ,因为第一次访问时将加载Students集合。 它应该导致相同的1300个额外查询,如explicite加载。