I have three classes:
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; }
}
I have two lists:
IList<ObjectiveDetail> oldObj;
IList<ObjectiveDetail> newObj;
The following LINQ gives me a new list of ObjectiveDetail
objects where: the Number or the Text fields for any ObjectiveDetail
object in the list differ between oldObj
and newObj
.
IList<ObjectiveDetail> upd = newObj
.Where(wb => oldObj
.Any(db => (db.ObjectiveDetailId == wb.ObjectiveDetailId) &&
(db.Number != wb.Number || !db.Text.Equals(wb.Text))))
.ToList();
How can I modify this so the LINQ gives me a new list of ObjectiveDetail
objects where: the Number or the Text fields or the SubTopic collections for any ObjectiveDetail
object in the list differ between oldObj
and newObj
.
In other words I want an ObjectiveDetail
to be added to the upd
list if:
I hope someone can come up with just some additional line in the LINQ statement that I already have.
C# filter list with iteration. In the first example, we use a foreach loop to filter a list. var words = new List<string> { "sky", "rock", "forest", "new", "falcon", "jewelry" }; var filtered = new List<string>(); foreach (var word in words) { if (word. Length == 3) { filtered.
LINQ query expressions can be used to conveniently extract and process data from arrays, enumerable classes, XML documents, relational databases, and third-party data sources. Query expressions can be used to query and to transform data from any LINQ-enabled data source. Query expressions have deferred execution.
Instead of creating a huge and hard maintanable LINQ query that will try to find differences, I would create a list of the same objects within both list (intersection) and as a result, take sum of both collection except this intersection. To compare objects you can use IEqualityComparer<>
implementation. Here is a draft:
public class ObjectiveDetailEqualityComparer : IEqualityComparer<ObjectiveDetail>
{
public bool Equals(ObjectiveDetail x, ObjectiveDetail y)
{
// implemenation
}
public int GetHashCode(ObjectiveDetail obj)
{
// implementation
}
}
and then simply:
var comparer = new ObjectiveDetailEqualityComparer();
var common = oldObj.Intersect(newObj, comparer);
var differs = oldObj.Concat(newObj).Except(common, comparer);
This will be much easier to maintain when classes change (new properties etc.).
This should be what you need:
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();
How It Works
db.SubTopics.Count != wb.SubTopics.Count
confirms that the new object being compared (wb
) and the old object being compared (db
) have the same number of SubTopics. That part is pretty straightforward.
!db.SubTopics.All(ds => wb.SubTopics.Any(ws => ws.SubTopicId == ds.SubTopicId))
is a bit more complicated. The All()
method returns true if the given expression is true for all members of the set. The Any()
method returns true if the given expression is true for any member of the set. Therefore the entire expression checks that for every SubTopic ds
in the old object db
there is a Subtopic ws
with the same ID in the new object wb
.
Basically, the second line ensures that every SubTopic present in the old object is also present in the new object. The first line ensures that the old & new objects have the same number of SubTopics; otherwise the second line would consider an old object with SubTopics 1 & 2 the same as a new object with SubTopics 1, 2, & 3.
Caveats
This addition will not check whether the SubTopics have the same Name
; if you need to check that as well, change the ws.SubTopicId == ds.SubTopicId
in the second line to ws.SubTopicId == ds.SubTopicId && ws.Name.Equals(ds.Name)
.
This addition will not work properly if an ObjectiveDetail can contain more than one SubTopic with the same SubTopicId (that is, if SubTopicIds are not unique). If that's the case, you need to replace the second line with !db.SubTopics.All(ds => db.SubTopics.Count(ds2 => ds2.SubTopicId == ds.SubTopicId) == wb.SubTopics.Count(ws => ws.SubTopicId == ds.SubTopicId))
. That will check that each SubTopicId appears exactly as many times in the new object as it does in the old object.
This addition will not check whether the SubTopics in the new object & the old object are in the same order. For that you would need to replace the 2nd line with db.SubTopics.Where((ds, i) => ds.SubTopicId == wb.SubTopics[i].SubTopicId).Count != db.SubTopics.Count
. Note that this version also handles non-unique SubTopicId values. It confirms that the number of SubTopics in the old object such that the SubTopic in the same position in the new object is the same equals the total number of SubTopics in the old object (that is, that for every SubTopic in the old object, the SubTopic in the same position in the new object is the same).
High Level Thoughts
Konrad Kokosa's answer is better from a maintainability perspective (I've already upvoted it). I would only use a big ugly LINQ statement like this if you don't expect to need to revisit the statement very often. If you think the way you decide whether two ObjectiveDetail
objects are equal might change, or the method that uses this statement might need to be reworked, or the method is critical enough that someone new to the code looking at it for the first time needs to be able to understand it quickly, then don't use a big long blob of LINQ.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With