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

时间:2014-01-22 04:58:26

标签: c# linq

我有三个班级:

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

我有两个清单:

IList<ObjectiveDetail> oldObj;
IList<ObjectiveDetail> newObj;

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

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

如何修改此项,以便LINQ为我提供ObjectiveDetail个对象的新列表,其中:任何ObjectiveDetail对象的数字或文本字段或SubTopic集合列表在oldObjnewObj之间有所不同。

换句话说,如果符合以下条件,我希望将ObjectiveDetail添加到upd列表中:

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

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

9 个答案:

答案 0 :(得分:7)

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

public class ObjectiveDetailEqualityComparer : IEqualityComparer<ObjectiveDetail>
{
    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);

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

答案 1 :(得分:3)

这应该是你需要的:

IList<ObjectiveDetail> 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))有点复杂。如果给定表达式对于集合的所有成员为真,则All()方法返回true。如果给定表达式对于集合的任何成员为真,则Any()方法返回true。因此,整个表达式检查旧对象ds中的每个SubTopic db是否存在新对象ws中具有相同ID的子主题wb

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


<强>注意事项

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

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

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


高层次的想法

从可维护性的角度来看,康拉德科科萨的回答更好(我已经对它进行了投票)。如果您不希望经常重新访问该语句,我只会使用像这样的丑陋的LINQ语句。如果您认为决定两个ObjectiveDetail对象是否相等的方式可能会发生变化,或者使用此语句的方法可能需要重新设置,或者该方法非常重要,以至于有人对代码进行了新的查看第一次需要能够快速理解它,然后不要使用大量的LINQ。

答案 2 :(得分:2)

通常我会选择@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);

答案 3 :(得分:2)

<强>问题

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

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

<强>解决方案

如果你比较集合,那么覆盖Equals()和GetHashcode()方法是个好主意:

public partial class ObjectiveDetail
{
    public ObjectiveDetail()
    {
        this.SubTopics = new List<SubTopic>();
    }
    public int ObjectiveDetailId { get; set; }
    public int Number { get; set; }
    public string Text { get; set; }
    public virtual ICollection<SubTopic> 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<ObjectiveDetail> upd = newObj
    .Where(n => !EqualsOld(n, dictionary))
    .ToList();

使用此方法:

private bool EqualsOld(ObjectiveDetail newItem, Dictionary<int, ObjectiveDetail> 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);
}

答案 4 :(得分:1)

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

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

希望我帮忙!

答案 5 :(得分:1)

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

IEnumerable<ObjectiveDetail> newOds = ...;
IEnumerable<ObjectiveDetail> 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 );

如果newOds oldOds IQueryable<ObjectiveDetail>来自DbContext

,则可以使用EF并在服务器端完全运行

答案 6 :(得分:0)

我已经尝试了你想要的东西,但它不是太“整洁”,我不可能制作“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);
            }
        });

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

希望这会有所帮助。

答案 7 :(得分:0)

IList<ObjectiveDetail> 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();

答案 8 :(得分:0)

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

/// <summary>
    /// Compare two objects, returns destination object with matched properties, values. simply Reflection to automatically copy and compare properties of two object
    /// </summary>
    /// <param name="source"></param>
    /// <param name="destination"></param>
    /// <returns>destination</returns>
    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;

    }