使用LINQ。 有两个不同的列表。 如何识别不匹配的对象

我有三节课:

public partial class Objective{ public Objective() { this.ObjectiveDetails = new List(); } public int ObjectiveId { get; set; } public int Number { get; set; } public virtual ICollection ObjectiveDetails { get; set; } } public partial class ObjectiveDetail { public ObjectiveDetail() { this.SubTopics = new List(); } public int ObjectiveDetailId { get; set; } public int Number { get; set; } public string Text { get; set; } public virtual ICollection SubTopics { get; set; } } public partial class SubTopic { public int SubTopicId { get; set; } public string Name { get; set; } } 

我有两个清单:

 IList oldObj; IList newObj; 

以下LINQ为我提供了一个新的ObjectiveDetail对象列表,其中:列表中任何ObjectiveDetail对象的Number或Text字段在oldObjnewObj之间不同。

 IList upd = newObj .Where(wb => oldObj .Any(db => (db.ObjectiveDetailId == wb.ObjectiveDetailId) && (db.Number != wb.Number || !db.Text.Equals(wb.Text)))) .ToList(); 

我如何修改它,以便LINQ为我提供一个新的ObjectiveDetail对象列表,其中:Number或Text字段列表中任何ObjectiveDetail对象的SubTopic集合oldObjnewObj之间不同。

换句话说,我希望在以下情况下将ObjectiveDetail添加到upd列表:

  • 它在oldObj中有Text,与newObj中的Text不同
  • 它在oldObj中有一个与newObj中的Number不同的Number
  • 它有一个SubTopics集合,其中包含oldObj中的三个元素和newObj中的4个元素
  • 它有一个SubTopics集合,oldObj中没有元素,newObj中没有2个元素
  • 它有一个SubTopics集合,在oldObj中有2个元素,在newObj中没有元素
  • 它有一个SubTopics集合,在oldObj中SubTopicId为1和2,在newObj中为1和3

我希望有人能在我已经拥有的LINQ语句中提出一些额外的内容。

这应该是你需要的:

 IList upd = newObj.Where(wb => oldObj.Any(db => (db.ObjectiveDetailId == wb.ObjectiveDetailId) && (db.Number != wb.Number || !db.Text.Equals(wb.Text) || db.SubTopics.Count != wb.SubTopics.Count || !db.SubTopics.All(ds => wb.SubTopics.Any(ws => ws.SubTopicId == ds.SubTopicId)) ))).ToList(); 

这个怎么运作

db.SubTopics.Count != wb.SubTopics.Count确认要比较的新对象( wb )和要比较的旧对象( db )具有相同数量的SubTopics。 那部分非常简单。

!db.SubTopics.All(ds => wb.SubTopics.Any(ws => ws.SubTopicId == ds.SubTopicId))有点复杂。 如果给定表达式对于集合的所有成员都为true,则All()方法返回true。 如果给定表达式对于集合的任何成员为true,则Any()方法返回true。 因此,整个表达式检查对于旧对象db中的每个SubTopic ds ,在新对象wb存在具有相同ID的Subtopic ws

基本上,第二行确保旧对象中存在的每个SubTopic也存在于新对象中。 第一行确保旧对象和新对象具有相同数量的SubTopics; 否则第二行将考虑SubTopics 1和2的旧对象与SubTopics 1,2和3的新对象相同。


注意事项

此添加不会检查SubTopics是否具有相同的Name ; 如果您还需要检查,请将第二行中的ws.SubTopicId == ds.SubTopicId && ws.Name.Equals(ds.Name)ws.SubTopicId == ds.SubTopicId && ws.Name.Equals(ds.Name)

如果ObjectiveDetail可以包含多个具有相同SubTopicId的SubTopic(即,如果SubTopicId不是唯一的),则此添加将无法正常工作。 如果是这种情况,则需要将第二行替换为!db.SubTopics.All(ds => db.SubTopics.Count(ds2 => ds2.SubTopicId == ds.SubTopicId) == wb.SubTopics.Count(ws => ws.SubTopicId == ds.SubTopicId)) 。 这将检查每个SubTopicId在新对象中的显示次数与在旧对象中的次数完全相同。

此添加不会检查新对象和旧对象中的SubTopics是否具有相同的顺序。 为此你需要用db.SubTopics.Where((ds, i) => ds.SubTopicId == wb.SubTopics[i].SubTopicId).Count != db.SubTopics.Count替换第二行db.SubTopics.Where((ds, i) => ds.SubTopicId == wb.SubTopics[i].SubTopicId).Count != db.SubTopics.Count 。 请注意,此版本还处理非唯一的SubTopicId值。 它确认了旧对象中SubTopics的数量,使得新对象中相同位置的SubTopic相同,等于旧对象中SubTopics的总数(即旧对象中每个SubTop的数量, SubTopic在新对象中的相同位置是相同的)。


高层次的想法

从可维护性的角度来看,Konrad Kokosa的答案更好(我已经对它进行了改进)。 如果您不希望经常重新访问该语句,我只会使用像这样的丑陋的LINQ语句。 如果你认为你决定两个ObjectiveDetail对象是否相等的方式可能会改变,或者使用这个语句的方法可能需要重做,或者方法足够重要,以至于第一次查看它的代码的新手需要能够快速理解它,然后不要使用一大堆LINQ。

我会在两个列表(交集)中创建一个相同对象的列表,而不是创建一个巨大且难以管理的可validationLINQ查询,因此,除了这个交集之外,还要获取两个集合的总和。 要比较对象,可以使用IEqualityComparer<>实现。 这是一个草案:

 public class ObjectiveDetailEqualityComparer : IEqualityComparer { public bool Equals(ObjectiveDetail x, ObjectiveDetail y) { // implemenation } public int GetHashCode(ObjectiveDetail obj) { // implementation } } 

然后简单地说:

 var comparer = new ObjectiveDetailEqualityComparer(); var common = oldObj.Intersect(newObj, comparer); var differs = oldObj.Concat(newObj).Except(common, comparer); 

当类更改(新属性等)时,这将更容易维护。

通常情况下我会选择@Konrad Kokosa方式。 但看起来你需要一个快速的解决方案。

我尝试了一些数据。 它给出了预期的结果。 我相信您可以修改所需结果的代码。

 var updatedObjects = oldObj.Join(newObj, x => x.ObjectiveDetailId, y => y.ObjectiveDetailId, (x, y) => new { UpdatedObject = y, IsUpdated = !x.Text.Equals(y.Text) || x.Number != y.Number //put here some more conditions }) .Where(x => x.IsUpdated) .Select(x => x.UpdatedObject); 

问题

您的LINQ查询并不是那么糟糕,但需要解决一些问题:

  • .Where() .Any()中使用.Where()意味着查询比需要慢得多 。 这是因为对于objNew每个项目,您都会迭代objOld的项目。
  • !db.Text.Equals(wb.Text)db.Textnull时抛出exception。
  • 您的代码未检测到添加到objNew中的新项目, objNew项目在objNew中不存在。 我不知道这是不是一个问题,因为你没有告诉我们这是否可行。

如果比较集合,最好覆盖Equals()和GetHashcode()方法:

 public partial class ObjectiveDetail { public ObjectiveDetail() { this.SubTopics = new List(); } public int ObjectiveDetailId { get; set; } public int Number { get; set; } public string Text { get; set; } public virtual ICollection SubTopics { get; set; } public override bool Equals(Object obj) { var typedObj = obj as ObjectiveDetail; return Equals(typedObj); } public bool Equals(ObjectiveDetail obj) { if ((object)obj == null) return false; return ObjectiveDetailId == obj.ObjectiveDetailId && Number == obj.Number && Text == obj.Text && SubTopics != null && obj.SubTopics != null && // Just in the unlikely case the list is set to null SubTopics.Count == obj.SubTopics.Count; } public override int GetHashCode() { return new { A = ObjectiveDetailId, B = Number, C = Text }.GetHashCode(); } } 

然后很容易:

 var dictionary = oldObj.ToDictionary(o => o.ObjectiveDetailId); IList upd = newObj .Where(n => !EqualsOld(n, dictionary)) .ToList(); 

使用这种方法:

 private bool EqualsOld(ObjectiveDetail newItem, Dictionary dictionary) { ObjectiveDetail oldItem; var found = dictionary.TryGetValue(newItem.ObjectiveDetailId, out oldItem); if (!found) return false; // This item was added to the new list return oldItem.Equals(newItem); } 

如果我做对了,你想要在两个.NET对象之间进行深入的比较,无论LINQ如何。 你为什么不使用comparenetobjects之类的东西 ?

尝试通过LINQ实现深度比较可能比在内存中进行比较更慢更复杂。 即使您选择在LINQ域中执行此操作,您最终也会检索整个对象,也许您会使用多个查询来执行此操作,从而增加了性能开销。 因此,我建议您急切地从数据库加载数据对象,并在没有特定linq查询的情况下进行深度比较。

希望我帮忙!

找到未更新的实体,然后排除:

 IEnumerable newOds = ...; IEnumerable oldOds = ...; // build collection of exclusions // start with ObjectiveDetail entities that have the same properties var propertiesMatched = oldOds.Join( newOds, o => new { o.ObjectiveDetailId, o.Number, o.Text }, n => new { n.ObjectiveDetailId, n.Number, n.Text }, ( o, n ) => new { Old = o, New = n } ); // take entities that matched properties and test for same collection // of SubTopic entities var subTopicsMatched = propertiesMatched.Where( g => // first check SubTopic count g.Old.SubTopics.Count == g.New.SubTopics.Count && // match g.New.SubTopics.Select( nst => nst.SubTopicId ) .Intersect( g.Old.SubTopics.Select( ost => ost.SubTopicId ) ) .Count() == g.Old.SubTopics.Count ) // select new ObjectiveDetail entities .Select( g => g.New ); // updated ObjectiveDetail entities are those not found // in subTopicsMatched var upd = newOds.Except( subTopicsMatched ); 

如果newOdsoldOds是来自oldOdsIQueryable这将使用EF并在服务器端完全运行

我已经尝试了你想要的东西,但它不是太“整洁”,我不可能制作“one-liner-linq-expression”类型代码。 检查一下,看看它是否可以接受。

您还需要检查性能,但正如您所说,没有太多对象,因此性能可能不会受到关注。

另外我没有正确测试它,所以如果你想接受它,那么请做测试。

  var oldObj = _objectiveDetailService.GetObjectiveDetails(id); var newObj = objective.ObjectiveDetails.ToList(); var upd = newObj .Where(wb => oldObj .Any(db => (db.ObjectiveDetailId == wb.ObjectiveDetailId) && (db.Number != wb.Number || !db.Text.Equals(wb.Text)))) .ToList(); newObj.ForEach(wb => { var comOld = oldObj.Where(db => wb.ObjectiveDetailId == db.ObjectiveDetailId && db.Number == wb.Number && db.Text.Equals(wb.Text)).FirstOrDefault(); if (comOld != null && wb.SubTopics.Any(wb2 => comOld.SubTopics.Where(oldST => wb2.SubTopicId == oldST.SubTopicId).Any(a => !a.Name.Equals(wb2.Name)))) { upd.Add(wb); } }); 

您也可以编写类似的代码来添加和删除。

希望这可以帮助。

 IList upd = newObj .Where(wb => oldObj .Any(db => (db.ObjectiveDetailId == wb.ObjectiveDetailId) && (db.Number != wb.Number || !db.Text.Equals(wb.Text))) ||!oldObj.Any(o=>o.DetailId == wb.DetailId) //check if it's there or a new one //check count || ((wb.SubTopics.Count!= oldObj.FirstOrDefault(o=>o.DetailId == wb.DetailId).SubTopics.Count || //check Ids match, or you can add more properties with OR wb.SubTopics.Any(wbs=>oldObj.FirstOrDefault(o=>o.DetailId == wb.DetailId) .SubTopics.Any(obs=>obs.SubTopicId !=wbs.SubTopicId)))) ).ToList(); 

看看下面的代码。 我创建了这个函数来比较两个对象然后返回匹配的属性字段作为对象。它可能对你有帮助。

 ///  /// Compare two objects, returns destination object with matched properties, values. simply Reflection to automatically copy and compare properties of two object ///  ///  ///  /// destination public static object CompareNameAndSync(object source, object destination) { Type stype = source.GetType(); Type dtype = destination.GetType(); PropertyInfo[] spinfo = stype.GetProperties(); PropertyInfo[] dpinfo = dtype.GetProperties(); foreach (PropertyInfo des in dpinfo) { foreach (PropertyInfo sou in spinfo) { if (des.Name == sou.Name) { des.SetValue(destination, sou.GetValue(source)); } } } return destination; }