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.
Related
I'm stuck at the following Problem:
I want to load one List, but I can pass more than one Parameter what could be the criteria to find the Lists I want.
Now I have the following structure:
House{
Name;
ID;
Alias;
}
Also I Have:
Person{
Name;
Alias;
}
This means, 1 house can hold multiple persons and multiple persons with the same name can be in multiple houses.
So now I want to call my Function e.G. "GetHouses(string criteria)" as criteria could be:
a Name of a house
an ID of a house
a part of the name of the house
a Name of one of the Persons in the house
Now I just read every house and it's data and I select afterwards by the criteria.
IMPORTANT NOTE: I can not change the logic until here!
So now as I try to find the matching criteria I came up with using LINQ as it is very fast. It works as long as I don't compare to the Persons:
result = (from x in result
where (
(string.Equals(x.Name, criteria))
|| (string.Equals(x.ID, criteria))
|| (x.Name.Contains(criteria))
select x).ToList();
Now I want to load every Person to the houses I found and check if a Name of the Persons in the house would match the criteria.
Is there a way to do this within the LINQ I have already?
Or do I have to go though the result with:
result.ForEach(x => ...)
Would be nice if it would work with the LINQ.
I did a similar Logic with the
result.FindAll(new Predicate<House>((x) => { ... LoadPersons(criteria) {... } }));
But that took to long.
Kind regards,
Asat0r
Presuming that you have a PersonList in your House-class you could use Enumerable.Any:
var matchingHouses = from house in allHouses
where string.Equals(house.Name, criteria)
|| string.Equals(house.ID, criteria)
|| house.Name.Contains(criteria)
|| house.PersonList.Any(resident => string.Equals(resident.Name, criteria))
select house;
If you have a method to return the "residents" you can use this. If you later want to access these persons you could create an anonymous type to store them:
var housesAndResidents = from house in allHouses
let matchingResidentList = LoadPersons(house.ID)
.Where(resident => string.Equals(resident.Name, criteria))
.ToList()
where string.Equals(house.Name, criteria)
|| string.Equals(house.ID, criteria)
|| house.Name.Contains(criteria)
|| matchingResidentList.Any()
select new { house, matchingResidentList };
You can access these properties in the following way:
var matchingHouseList = housesAndResidents.ToList();
// you don't need the list, you can use foreach directly,
// but whenever you access housesAndResidents you will execute that query
// ToList materializes this query into a collection, so you can enumerate it or use the Count property
foreach(var x in matchingHouseList )
{
Console.WriteLine("House:{0} Matching-Resident(s):{1}"
, x.house.Name
, String.Join(", ", x.matchingResidentList.Select(r => r.Name)));
}
Here is a simple solution using three classes: Program, House and Person.
The class House "houses" (pun intended) a list of the class Person (and your own values: name, id and alias). In this way the residents are a part of the house and not stored somewhere else. Storing the residents outside the House class is the same as having your lunchbox's contents outside the box in your backpack.
The class Person stores the basic information about the house's residents. Having a list of residents inside the House class makes it easier to compare the search criteria with the people.
In the class Program you'll find the Main and the FindHouses classes. These kinda explains themselves
class Person
{
public string name;
public string alias;
public Person(string _name, string _alias = "")
{
name = _name;
alias = _alias;
}
}
class House
{
public string name;
public string id;
public string alias;
public List<Person> people = new List<Person>();
public House(string _name, string _id, string _alias = "")
{
name = _name;
id = _id;
alias = _alias;
}
}
class Program
{
static List<House> houses = new List<House>();
static void Main(string[] args)
{
// Add houses here
foreach (House house in FindHouses("criteria"))
{
// Do stuff
}
}
// Find all the houses in which the criteria exists
static List<House> FindHouses(string criteria)
{
// Return the list of all houses in which the criteria exists
return houses.Where(h =>
h.name.Contains(criteria) ||
h.id == criteria ||
h.alias.Contains(criteria) ||
h.people.Any(p =>
p.name.Contains(criteria) ||
p.alias.Contains(criteria))).ToList();
}
}
I dont know if this is too big of a change in your code, but anyways, I hope this helps.
I know you mentioned you didn't want to load the persons into the houses, but this makes the search for houses based on "all values" much easier
I have two lists, one which is a list of Equipment and one which is a list of WorkflowItems with an Equipment property. The Equipment property within the List<WorkflowItem> list only has one of it's values hydrated, ProcessId. The List<Equipment> has two properties hydrated, ProcessId and Name. I want to hydrate the List<WorkflowItem>.Equipment.Name with the value from the single Equipment record in the List<Equipment>
This LINQ query below will select a generic item out doing basically what I'm looking for, but I would rather just fill in the original list.
var list = from item in workflowItems
join equipment in barcodeEquipmentList on
item.Equipment.ProcessId equals equipment.ProcessId
select new
{
ProcessId = item.Equipment.ProcessId,
EquipmentName = equipment.Name
};
Edit
The list is going to be relatively small, even doing something like this would be fine (aside from the fact that this does not work)
workflowItems.ForEach(x => x.Equipment = from e in barcodeEquipmentList
where e.Process.Id == x.Equipment.Process.Id
select e
);
...final edit
but this does work:
workflowItems.ForEach(x => x.Equipment = barcodeEquipmentList
.Where(e => e.Process.Id == x.Equipment.Process.Id)
.FirstOrDefault());
This piece of code should match your needs:
public class Equipment {
public int ProcessId { get; set; }
public string Name { get; set; }
}
public class WorkflowItem {
public Equipment { get; set; }
public void LoadEquipmentFrom(IEnumerable<Equipment> cache){
var equipment = cache.FirstOrDefault(e => e.ProcessId == Equipment.ProcessId);
if(equipment != null)
Equipment.Name = equipment.Name;
}
}
You could also assign the instance from cache to the existing one, it wouldn't matter since both must have the same Identifier Equipment = equipment;. That would be easier if you have more properties to set. To optimize further use an IDictionary<int, Equipment> instead of that IEnumerable<Equipment>, because you'll be reading that collection very often.
I'm guessing you are implementing a kind of ORM, in this case I can give you a good advice: "There is already something out there that'll fit your needs.".
Since the dataset was not very big (less than 20 records), I was able to this as below without a hit to performance
workflowItems.ForEach(x => x.Equipment = barcodeEquipmentList
.Where(e => e.Process.Id == x.Equipment.Process.Id)
.FirstOrDefault());
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
I'm trying to update an item inside my object graph which could span 'n' level in depth.
The following is my object model:
public class Entity
{
public string Name { get; set; }
}
public class Category : Entity
{
public List<Category> Categories { get; set; }
public List<Product> Products { get; set; }
}
public class Product : Entity
{
}
My view is bound to an ObservableCollection<Category> Categories. What I want to do is given a category name, I need to retrieve the first object which matches that from the collection.
For ex, given a list like this and a category Facial Tissue, I need to retrieve the Facial Tissue category object from the collection.
Category - Pharmacy
|-Product - Aspirin
|-Product - Tylenol
|-Category - Tooth Paste
| |-Product - Crest
| |-Product - Colgate
|-Category - Paper Products
|-Category - Toilet Paper
| |-Product - NoName
| |-Product - Charmin
|-Category - Facial Tissue
|-Product - Kleenex
Category - Household
|-Product - Pinesol Cleaner
|-Product - Garbage Bags
I have tried this, but it throws an Object reference not set to an instance of an object exception when I search level >2 in the hierarchy.
return Categories.FirstOrDefault(n => n.Name == name) ??
Categories.SelectMany(node => node.Categories).Where(lx => lx.Name == name).FirstOrDefault();
NOTE: At times the categories could be null deep down the hierarchy.i.e if there are no categories, then the collection is set to null.Also the solution necessarily need not be using LINQ.
You can use either of the following methods to traverse the tree structure recursively:
public static IEnumerable<T> Traverse<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> childSelector)
{
var queue = new Queue<T>(source);
while (queue.Any())
{
var item = queue.Dequeue();
yield return item;
foreach (var child in childSelector(item))
{
queue.Enqueue(child);
}
}
}
public static IEnumerable<T> Traverse<T>(T root, Func<T, IEnumerable<T>> childSelector)
{
return Traverse(new[] { root }, childSelector);
}
There is an overload for a single root item, and another that takes a sequence of items.
You could implement them using actual recursion if you prefer, but I prefer an explicit data structure. If you would like a depth first search instead of a breath first search just change the Queue to a Stack and update the methods accordingly.
To use it you can do something like this:
Category root = new Category();
var searchResult = Traverse(root, item => item.Categories)
.Where(category => category.Name == "testValue")
.FirstOrDefault();
It also appears that you're getting null errors because you have Categories that are null. If at all possible, I would highly encourage you to fix that problem, rather than dealing with it. If an Entity has no Categories it should have an empty list, not a null list. Having said that, you can adjust the Traverse calls as follows if you have any null items:
Traverse(root, item => item.Categories ?? Enumerable.Empty<Category>())
LINQ by itself does not have a dedicated operator for a depth-first search (which is what you need in this case). However, given your requirements, there is a fairly easy solution using a simple recursive function:
// Returns the first category with the given name or null, if none is found
Category findCategory(Category start, String name) {
if (start.name == name) {
return start;
}
if (start.Categories == null) {
return null;
}
return (from c in start.Categories
let found = findCategory(c, name)
where found != null
select found).FirstOrDefault()
}
You might consider setting the Categories property of categories with no subcategories to an empty list instead of null. This allows you skip the null check here (and probably also in a lot of other places).
Here is a simple solution although it doesn't only use Linq:
public Category GetCategory(string name, List<Category> Categories)
{
Category found = Categories.FirstOrDefault(cat => cat.Name == name);
return found ?? Categories.Select(cat => GetCategory(name,cat.Categories))
.FirstOrDefault(cat => cat != null);
}
I have a service call that returns to me an IEnumerable of CustomObject, this is a third party call that I don't have the liberty to modify. CustomObject can be assumed to have a definition like below:
public class CustomObject
{
public int Id { get; set; }
public string Name { get; set; }
...
...
...
public int Points { get; set; }
public bool IsPrivate { get; set; }
}
Among the list of objects returned, I could have special CustomObject objects. I need to implement some special rules such as:
If elements with Ids 1 and 3 both exist in the list, only render one of them based on rules
a. If either one of them has IsPrivate flagged to true, display the one that has IsPrivate set to false
b. If neither have IsPrivate set to true, display the one with higher points
... and so on
What would be the best place to implement these rules. I thought about implementing an IEqualityComparer and do a .Distinct() on my service call, doesn't seem like what IEqualityComparer is meant to do.
Suggestions?
Since you cannot modify the class 'CustomObject' I would add the business logic into the 'render' pipeline and just call some function such as below where you have a specific business rule that applies. Then just remove id1 and id3 from your list of objects to render and only render the object that is returned from the following function.
CustomObject BizRule3293(IEnumerable<CustomObject> objects)
{
CustomObject id1 = objects.SingleOrDefault(t => t.Id == 1);
CustomObject id3 = objects.SingleOrDefault(t => t.Id == 3);
if (id1 != null && id3 !=null)
{
if (!id1.IsPrivate && !id3.IsPrivate)
return id1.Points > id3.Points ? id1 : id3;
return id1.IsPrivate ? id3 : id1;
// No logic stated if both are private
}
return id1 ?? id3;
}