簡體   English   中英

使用LINQ。 有兩個不同的列表。 如何識別不匹配的對象

[英]Using LINQ. With two different lists. How can I identify objects that do not match

我有三個班:

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對象的Number或Text字段在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對象列表,其中: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語句中提出一些額外的內容。

我將在兩個列表(交集)中創建一個相同對象的列表,而不是創建一個巨大且難以管理的可驗證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);

當類改變時(新屬性等),這將更容易維護。

這應該是你需要的:

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))有點復雜。 如果給定表達式對於集合的所有成員都為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.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在新對象中的相同位置是相同的)。


高層次的想法

從可維護性的角度來看,康拉德科科薩的答案更好(我已經對它進行了改進)。 如果您不希望經常重新訪問該語句,我只會使用像這樣的丑陋的LINQ語句。 如果你認為你決定兩個ObjectiveDetail對象是否相等的方式可能會改變,或者使用這個語句的方法可能需要重做,或者該方法足夠重要,以至於第一次查看它的代碼的新手需要能夠快速理解它,然后不要使用大量的LINQ。

通常情況下,我會選擇@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時拋出異常。
  • 您的代碼未檢測到新的項目加入到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);
}

如果我做對了,你想要在兩個.NET對象之間做一個深入的比較,無論LINQ如何。 你為什么不使用comparenetobjects之類的東西

嘗試通過LINQ實現深度比較可能比在內存中進行比較更慢更復雜。 即使您選擇在LINQ域中執行此操作,您最終也會檢索整個對象,也許您會使用多個查詢來執行此操作,從而增加了性能開銷。 因此,我建議您急切地從數據庫加載數據對象,並在沒有特定linq查詢的情況下進行深度比較。

希望我幫忙!

找到未更新的實體,然后排除:

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 );

這將工件W / EF和運行完全的服務器端,如果newOdsoldOdsIQueryable<ObjectiveDetail>從A S DbContext

我已經嘗試了你想要的東西,但它不是太“整潔”,我不可能制作“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<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();

看看下面的代碼。 我創建了這個函數來比較兩個對象然后返回匹配的屬性字段作為對象。它可能對你有幫助。

/// <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;

    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM