Select object from nested collection using Linq - c#

I have a class structure something like this:
class MyClass
{
public IEnumerable<AttributeGroup> AttributeGroups { get; set; }
}
class AttributeGroup
{
public IEnumerable<Attribute> Attributes { get; set; }
}
class Attribute
{
public string SomeProp { get; set; }
}
I need to get all 'Attributes' which has a specific 'SomeProp' value no matter which Attribute Group they belong to.
For example, SomeProperty== 'A' can be found in both MyClassObj.AttributeGroup[0] and MyClassObj.AttributeGroup[5] and I need to write a Linq (or something like that) to fetch two objects from these two different attributegroups.
Any suggestion?

First select all attributes from all attribute groups, then only select the ones with your property.
IEnumerable<Attribute> attributes =
myClassInstance
.AttributeGroups
.SelectMany(x => x.Attributes)
.Where(x => x.SomeProperty == 'A');
Other Linq-style syntax:
IEnumerable<Attribute> attributes =
from attributeGroup in myClassInstance.AttributeGroups
from attribute in attributeGroup.Attributes
where attribute.SomeProperty == 'A'
select attribute;

Have a look at SelectMany (http://msdn.microsoft.com/en-us/library/bb534336.aspx).
For example:
myClassObjs.SelectMany(o => o.AttributeGroups.SelectMany(g => g.Attributes)).Where(a => a.SomeProp == "A")
This line selects all Attribute objects of all AttributeGroups of all MyClass objects where SomeProp equals "A". a in the lambda expression for Where is of type Attribute.

Your example isn't clear; I can't tell what you mean by, "two object from these two different attributegroups". I'll guess that you want the groups that have attributes with the property in question:
from g in MyClassObj.AttributeGroups
where g.Any(attr => attr.SomeProperty == "A")
select g

Related

Find object from collection of object

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

Converting a distinct list to dictionary not working

I'm trying to convert a list of objects to a dictionary using the following code:
var MyDictionary = MyList.Distinct().ToDictionary(i => i.ObjectId, i => i);
I know that a dictionary should not contain duplicate elements, hence the .Distinct(). Yet I still get the following Exception whenever there's a duplicate element:
An item with the same key has already been added.
MyList is a list of MyObject that looks like this:
public class MyObject{
public string ObjectId { get; set; }
public string FName { get; set; }
public string LName { get; set; }
}
Is there a better way to create a dictionary from a list of objects ? or am I doing something wrong?
If you want to compare on the ObjectId, you'll need to pass in a custom comparer to .Distinct(). You can do so like this:
class MyObjectComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
return x.ObjectId == y.ObjectId;
}
public int GetHashCode(MyObject obj)
{
return obj.ObjectId.GetHashCode();
}
}
var MyDictionary = MyList
.Distinct(new MyObjectComparer())
.ToDictionary(i => i.ObjectId, i => i);
You could use Group by and then select first from the List as below:
var MyDictionary = MyList.GroupBy(i => i.ObjectId, i => i).ToDictionary(i => i.Key, i => i.First());
Distinct works using the objects built in Equals and GetHashCode methods by default but your dictionary works only over the id. You need to pass in a IEqualityComparer in to distinct that does the comparison on Id to test if items are equal or make MyObject implment Equals and GetHashCode and have that compare on the Id.

Selecting both parent and child items when querying child properties

In my data structures I have the following classes:
public partial class Item
{
// stuff
public int QuoteId { get; set; }
public virtual ItemType ItemType { get; set; }
}
public partial class ItemType
{
//stuff
public virtual ICollection<Item> Items { get; set; }
}
What I want to do is get a list of all the ItemTypes, each of which has its Items collection populated according to a QuoteId.
So, for example if there are three item types, only two of which have items with a quote Id of 50:
ItemType1
Item.QuoteId == 50
ItemType2
ItemType3
Item.QuoteId == 50
I've managed to get something close with this query:
r.ItemTypes.Select(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId));
But what this gives you (as you might expect, since I'm Selecting on Item) is an IEnumerable<IEnumerable<Item>>. This has the structure that I'm after but doesn't have the ItemType data.
I realise this is a dumb question, but I'm frustrated by my inability to get the answer.
r.ItemTypes.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
If you need to get all ItemTypes and only specific Items for every, you can do this:
r.ItemTypes.Select(x => new
{
x,
FilteredItems = x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
});
After that you need to assign x.Items to FilteredItems for every ItemType
You have to select the Item.ItemType property if you want the all ItemTypes of a given QuoteId. You also have to use SelectMany to flatten the "nested" collections:
IEnumerable<ItemType> types = r.ItemTypes
.SelectMany(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
.Select(i => i.ItemType));
If you are not interested in the nested ItemType(don't know the logic) you can use Backs' approach:
IEnumerable<ItemType> types = r.ItemTypes
.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
var result = from itemTypes in r.ItemTypes
where itemTypes.QuoteId equals CurrentQuote.QuoteId
select new {itemType = itemTypes}

Linq with DbContext - item with two keys complicate scenario

I have this scenario:
public class A
{
public string Name{get;set;}
public int Number{get;set;}
}
public class B
{
public A AInstance{get;set;}
}
and I have this function:
public List<B> GetBFromA(List<A> aList)
{
...
List<B> database_bInstances = db.GetBInstances();
//here I want linq query that will filter to me the B instances from the database, according to the aList
}
I hope the idea is clear. the scenario is that I get all the B instances from the database and filter all the B according to the list of A in the input. if for some B its A instance(identified by Name and Number exists in the input aList it will stay if not it will removef from the B list).
Edit: I am using entity framework DbContext! those it is not letting me to make:
database_bInstances.Where(b => aList.Any(a => (a.Name == b.AInstance.Name)
&& (a.Number == b.AInstance.Number)))
and it throws exception: Unable to create a constant value of type A. Only primitive types or enumeration types are supported in this context.
If Equals works for A then this will work
List<B> database_bInstances = db.GetBInstances();
List<B> new_list = database_bInstances
.Where(b => aList.Contains(b.AInstance));
If not then do this:
List<B> database_bInstances = db.GetBInstances();
List<B> new_list = database_bInstances
.Where(b => aList.Any(a => b.AInstance.Name == a.Name && b.AInstance.Number == a.Number));
This expression will give you the desired Bs.
database_bInstances.Where(b => aList.Any(a => (a.Name == b.AInstance.Name)
&& (a.Number == b.AInstance.Number)))
While this will work, it does not look to me like the best thing to do, but without more information I can not make a better suggestion.
If you have properly implemented Equals and GetHashCode or if you use an O/R mapper that ensures reference equality for objects representing the same database row you can just use Contains instead of Any.
database_bInstances.Where(b => aList.Contains(b.AInstance))

Succinct LINQ filter expression

I have an MVC controller that will filter a product list based on a category.
Products = repository.Products.Where(p => category == null || p.Category1 == "category1" );
If I wanted to let the user filter the product with two categories, I would have to add in another if statement that contains Category1 and Category2. I can imagine if I have more categories, and the user can choose category 1,3,5 and so on, the permutation will get crazily large.
Is there a proper way of doing this?
I am assuming that your object model is defined along the lines of:
public class Product
{
// ...
public Category Category1 { get; set; }
public Category Category2 { get; set; }
public Category Category3 { get; set; }
// ...
}
(where you might be using strings instead of having a category class)
If the object model is within your control, then I would recommend changing it so that a product has a collection of categories rather than several named properties for Category1, Category2, Category3 etc, so more like this:
public class Product
{
// ...
public IList<Category> Categories { get; set; }
// ...
}
If the product class is fixed and it already has multiple individual category properties, I would recommend writing an extension method for your product class that returns a list of categories that are non-null. That way you can write a where expression more succinctly.
For example:
public static class ProductExtension
{
public static IList<Category> GetCategories(this Product product)
{
List<Category> categories = new List<Category>();
if (product.Category1 != null)
{
categories.Add(product.Category1);
}
if (product.Category2 != null)
{
categories.Add(product.Category2);
}
// etc.
return categories;
}
}
...which could then be used along the lines of
repository.Products.Where(p => p.GetCategories().Contains("category1"));
Another option is to create a ProductFilter object to do the filtering for you.
Give the ProductFilter class a field for every category that is possible to filter on, which each store predicates, and a PassesFilter(Product p) method which determines whether p passes the predicate for all categories where a predicate has been set, e.g.
method PassesFilter(Product p):
if Category1Filter is not null:
if p does not pass Category1Filter:
return false
if Category2Filter is not null:
if p does not pass Category2Filter:
return false
return true
(Excuse the pseudo-code, I don't do C# and it's late)
So you could use it like so:
ProductFilter pf = new ProductFilter();
...
/*build up your filters for all categories that apply in this search...*/
pf.ColourFilter = (Product p) => { return p.Colour == "Red"; };
pf.PriceFilter = (Product p) => { return p.Price > 100.00; };
...
Products = repository.Products.Where(p => category == null || pf.PassesFilter(p) );
You could also easily implement the PassesFilter method differently to handle OR instead of AND (or create a class for each implementation).
I know that using predicates in the way I described would allow you to put a price predicate in the colour predicate field, but I just thought I'd throw this example out there to illustrate the concept of using an object to do the work of lambdas :-)
1.You may use Expression to constructor condition expression
2.Use expression in the linq.

Categories