Find object from collection of object - c#

I have a class
public class ABC
{
public int Id { get; set; }
public string Name { get; set; }
public Enum Msg { get; set; }
}
and collection of this class and single object
List<ABC> objColl = new List<ABC>();
ABC obj = new ABC();
Assume collection have items and i am trying to find single object which already exists in collection.
i want to find a single object inside that collection whether it exists or not.
I had already tried
var res = objColl.contains(obj);
it always return false. i dont want compare each property of object or loop.

Use Any with your criteria:
bool res = objColl.Any(s => s.Id == obj.Id);
if you want to use Contains then override Equals().

When you call Contains(), it searches for an item in the collection that is equal to the argument you've provided. Since you have not overridden Equals(), it uses the default implementation.
You have two options:
Override Equals() in class ABC to specify checking only the properties you want to check;
Use LINQ: objColl.Any(e => e.[some property] == obj.[some property])

You can use FirstOrDefault()
Returns the first element of a sequence, or a default value if no
element is found.
var res = objColl.FirstOrDefault(x => x.Id == obj.Id);

var res = objColl.Where(s=>s.Id == obj.Id).Any();

Related

Checking whether a property value equals to a predefined one in a list of objects

I have two classes like this
public class Stock
{
public StockItem Item;
public string Location;
public int Quantity;
public string Price;
}
public class StockItem
{
public string Code;
public string Name;
public string Unit;
}
And I have a list that contains multiple instances of the Stock class
var stockList = new List<Stock>();
I am trying to determine whether the Name property of each instance inside the list is equal to a predefined string. Currently, I am looping through the list like this
foreach (var stock in stockList)
{
if (stock.Item.Name.ToLower() == "test")
{
Console.WriteLine("Found the class")
break;
}
}
However, I was wondering if there was a more efficient way to do this using Linq or something similar to the .Contains method. Is there a simpler or more efficient way to accomplish this?
whether the Name property of each instance inside the list is equal
to a predefined string
Not much more efficient but simpler:
bool allAreEqual = stockList.All(x => x.Item.Name.Equals("test", StringComparison.OrdinalIgnoreCase))
If you instead want to find the first which matches the condition(what your loop really does):
Stock si = stockList.FirstOrDefault(x => x.Item.Name.Equals("test", StringComparison.OrdinalIgnoreCase));
Now you know if such a Stock exists(si != null) at all and you got it.
All in linq will return True or false
stockList.All(p => p.Item.Name.ToLower() == "test");
You can use the Linq Any() method:
bool containsItemName = stockList.Any(x => x.Item.Name.Equals("MyName", StringComparison.InvariantCultureIgnoreCase));
Are you really looking at all instances? From your question, it seems as if Anymight be the way to go, see here.
stockList.Any(p => p.Item.Name.ToLower() == "test");
You can get a result what you wanted by calling Any
bool result = stockList.Any(
stock => stock.Item.Name.Equals("text", StringComparison.InvariantCultureIgnoreCase)
);
In this code, the parameter name stock can be changed whatever you want.

Find an object within a list and replace its value

I have the following list and class:
List<MyClass> MyList
public class MyClass
{
public int id { get; set; }
public bool checked { get; set; }
}
I also have the two variables:
int idToFind = 1234;
bool newcheckedvalue = true;
What I need to do is search the list and find the MyClass object where the id equals that value of idToFind. Once I have the object in the list, I then want to change the value of the checked property in the class to that of the newcheckedvalue value.
LINQ seems to be the solution to this problem, I just can't get the expression right. Can I do this in a single LINQ expression?
LINQ is for querying collection, not for modification. You can find the object like:
var item = MyList.FirstOrDefault(r=> r.id == idtoFind && r.checked == newcheckedvalue);
To find the item based on the ID only you can do:
var item = MyList.FirstOrDefault(r=> r.id == idtoFind);
Later you can set/modify its property.
//Make sure to check against Null, as if item is not found FirstOrDefault will return null
item.checked = newcheckedvalue; //or any other value
Example (to be noted that MyClass type has to be a class, a reference type, in this example):
var found = MyList.Where(ml=>ml.Id == 1234);
foreach(var f in found)
f.checked = newcheckedvalue;

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

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:
It has Text in oldObj that's different from Text in newObj
It has a Number in oldObj that's different from the Number in newObj
It has a SubTopics collection with three elements in oldObj and 4 elements in newObj
It has a SubTopics collection with no elements in oldObj and 2 elements in newObj
It has a SubTopics collection with 2 elements in oldObj and no elements in newObj
It has a SubTopics collection with elements with SubTopicId of 1 and 2 in oldObj and 1 and 3 in newObj
I hope someone can come up with just some additional line in the LINQ statement that I already have.
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.
Normally I would go with #Konrad Kokosa way. But it looks like you need a fast solution.
I tried it with some data. It gives the expected result. I am sure that you can modify the code for desired results.
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);
Problems
Your LINQ query was not that bad but some proplems needed to be solved:
Using .Any() in a .Where() means that the query is much slower than needed. This is because for every item in objNew, you iterate the items of objOld.
!db.Text.Equals(wb.Text) throws an exception when db.Text is null.
Your code doesn't detect new items added to objNew that doesn't exists in objOld. I don't know if that is a problem because you didn't told us if that is possible.
Solution
If you compare collections, it would be a good idea to override the Equals() and GetHashcode() methods:
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();
}
}
Then it is easy:
var dictionary = oldObj.ToDictionary(o => o.ObjectiveDetailId);
IList<ObjectiveDetail> upd = newObj
.Where(n => !EqualsOld(n, dictionary))
.ToList();
using this method:
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);
}
If I get it right, you want to make a deep comparison between two .NET objects, regardless of LINQ. Why don't you use something like comparenetobjects?
Trying to implement a deep comparison through LINQ would probably be slower and more complex than making the comparison in the memory. Even if you chose to do it in LINQ realm, you would finally retrieve the whole object and perhaps you would do it with more than one queries, adding performance overhead. Therefore, I would suggest to eagerly load your data object from database and make the deep comparison without a specific linq query.
Hope I helped!
Find the entities that are not updated then exclude:
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 );
This would work w/ EF and run completely server-side if newOds and oldOds are IQueryable<ObjectiveDetail>s from a DbContext
I have tried what you wanted but it is not too "neat" and it was not possible for me to make "one-liner-linq-expression" type code. Check it out and see if it is acceptable to you.
Also you need to check the performance but as you said there are not many objects so performance might not be of concern.
Also I have not tested it properly so if you wish to accept it then please do testing.
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);
}
});
You can write similar code to add and delete as well.
Hope this helps.
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();
Have a look at below code. I created this function to compare two object then returns matched properties fields as an object.It may help full to you.
/// <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;
}

Can I use LINQ to compare what is missing, added or updated between two collections

I have the following class. To make it so I could compare I added an Equals method:
public ObjectiveDetail()
public int ObjectiveDetailId { get; set; }
public int Number { get; set; }
public string Text { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as ObjectiveDetail);
}
public bool Equals(ObjectiveDetail other)
{
if (other == null)
return false;
return this.Number.Equals(other.Number) &&
(
this.Text == other.Text ||
this.Text != null &&
this.Text.Equals(other.Text)
);
}
}
I have two ICollection collections:
ICollection<ObjectiveDetail> _obj1; // Reference
ICollection<ObjectiveDetail> _obj2; // May have more, less or different objectDetails from the reference.
The common tfield with the collections is ObjectiveDetailId. Is there a way I can use three LINQ expressions to create:
A collection of rows in _obj2 and not _obj1
A collection of rows in _obj1 and not _obj2
A collection of rows different between _obj1 and _obj2
Note this is similar to another question I asked earlier but I think this is a bit simpler now I have added the Equals method. uld do this?
You can use Except to subtract sets:
var in2butNot1 = _obj2.Except(_obj1);
var in1butNot2 = _obj1.Except(_obj2);
However, this may not be what you are looking to get, because objects that have "changed" will be treated as simply "not equal" to each other.
It appears that your objects have an ID field. You can order the objects on that ID, and then traverse both collections as if you were producing a merge. This would let you detect insertions, updates, and deletions with a straightforward chain of ifs.
You can also use IDs to decide what's common and what's changed:
var ids1 = new HashSet<int>(_obj1.Select(o => o.ObjectiveDetailId));
var ids2 = new HashSet<int>(_obj2.Select(o => o.ObjectiveDetailId));
var in2butNot1 = _obj2.Where(o => !ids1.Contains(o.ObjectiveDetailId));
var in1butNot2 = _obj1.Where(o => !ids2.Contains(o.ObjectiveDetailId));
You should always override Equals and GetHashCode:
A collection of rows in _obj2 and not _obj1
var inObj2NotInObj1 = _obj2.Except(_obj1).ToList();
A collection of rows in _obj1 and not _obj2
var inObj1NotInObj2 = _obj1.Except(_obj2).ToList();
A collection of rows different between _obj1 and _obj2
Specify different, if you mean not Equals, that is what you have above.
What I mean with not Equals is when the objects have the same
ObjectiveDetailId but different "Number" or "Text" field values.
If you create a dictionary which maps an ID to the original (_obj1) objects, you could then look up the original with a matching ID for each new (_obj2) object and compare:
var oldDictionary = _obj1.ToDictionary(old => old.ObjectiveDetailId);
var updated = _obj2.Where(current => {
ObjectiveDetail old = null;
var isExisting = oldDictionary.TryGetValue(current.ObjectiveDetailId, out old);
return isExisting && !old.Equals(current);
});

Insert an object in the list in C#

I have a list of class objects UserData. I get an object from this list through where method
UserData.Where(s => s.ID == IDKey).ToList(); //ID is unique
I would like to make some changes in the object and insert at the same location in the list. However, I donot have the index of this object.
Any idea how to do it ?
Thanks
You can get the index using the method
UserData.FindIndex(s => s.ID == IDKey)
It will return an int.
When your getting the item from a LIST its an reference type, if your updating anything to it then it will automatically change the values in LIST. Please check your self after updating...........
Item whichever your getting from
UserData.Where(s => s.ID == IDKey).ToList();
is an reference type.
As long as UserData is reference type, the list only holds references to instances of that object. So you can change its properties without the need of remove/insert (and obviously do not need index of that object).
I also suggest you want to use Single method (instead of ToList()) as long as the id is unique.
Example
public void ChangeUserName(List<UserData> users, int userId, string newName)
{
var user = users.Single(x=> x.UserId == userId);
user.Name = newName; // here you are changing the Name value of UserData objects, which is still part of the list
}
just fetch the object using SingleOrDefault and make related changes; you do not need to add it to the list again; you are simply changing the same instance which is an element of the list.
var temp = UserData.SingleOrDefault(s => s.ID == IDKey);
// apply changes
temp.X = someValue;
If I'm misunderstanding you then please correct me, but I think you're saying that you essentially want to iterate through the elements of a list, and if it matches a condition then you want to alter it in some way and add it to another list.
If that's the case, then please see the code below to see how to write an anonymous method using the Where clause. The Where clause just wants an anonymous function or delegate which matches the following:
parameters: ElementType element, int index -- return: bool result
which allows it to either select or ignore the element based upon the boolean return. This allows us to submit a simple boolean expression, or a more complex function which has additional steps, as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StackOverflow
{
class Program
{
static void Main(string[] args)
{
int IDKey = 1;
List<SomeClass> UserData = new List<SomeClass>()
{
new SomeClass(),
new SomeClass(1),
new SomeClass(2)
};
//This operation actually works by evaluating the condition provided and adding the
//object s if the bool returned is true, but we can do other things too
UserData.Where(s => s.ID == IDKey).ToList();
//We can actually write an entire method inside the Where clause, like so:
List<SomeClass> filteredList = UserData.Where((s) => //Create a parameter for the func<SomeClass,bool> by using (varName)
{
bool theBooleanThatActuallyGetsReturnedInTheAboveVersion =
(s.ID == IDKey);
if (theBooleanThatActuallyGetsReturnedInTheAboveVersion) s.name = "Changed";
return theBooleanThatActuallyGetsReturnedInTheAboveVersion;
}
).ToList();
foreach (SomeClass item in filteredList)
{
Console.WriteLine(item.name);
}
}
}
class SomeClass
{
public int ID { get; set; }
public string name { get; set; }
public SomeClass(int id = 0, string name = "defaultName")
{
this.ID = id;
this.name = name;
}
}
}

Categories