How does one make .Include work with a segmented query? - linq

I have a search form where the user can search for car parts and I need to filter on whether or not they provided a value for the PartNumber field:
var query = db.Car.Include(u => u.CarPart);
if (!string.IsNullOrWhiteSpace(model.PartNumber))
{
query = query.Where(u => u.CarPart.PartNumber == model.PartNumber);
}
But this error is given:
ICollection< CarPart> does not contain a definition for 'PartNumber' and no extension method 'PartNumber' accepting a first argument of type 'ICollection< CarPart>' could be found.
Any ideas?

Apparently you have a class Car, where every Car has a property PartNumber of type ICollection<CarPart>. Probably a one-to-many or a many-to-many relationship.
I'm not sure, but I think that every CarPart has a property PartNumber.
If you had written your type declarations instead of using var and used proper identifiers for your variables, you would have seen something like:
IQueryable<Car> cars= db.Cars.Include(car => car.CarParts);
if (...)
{
cars = cars.Where(car => car.CarParts...
}
Now what kind of thing is CarParts? It is an ICollection<CarPart>. Surely you wouldn't expect that a collection would have a property PartNumber?
I'm not sure which cars you want if model.PartNumber has a non-empty value.
I want the cars that have at least one part that has this PartNumber
cars = cars.Where(car => car.CarParts
.Where(carPart => carPart.PartNumber == model.PartNumber
.Any()
In words: give me all Cars, with all their CarParts, that have at least one CarPart in its collection of CarParts that has a PartNumber equal to model.PartNumber

Related

How can I use func in a C# EF dbquery statement for sorting?

I have to do multi-part sorts and want to do it dynamically.
I found this question but do not know how to use func in a dbquery statement.
No generic method 'ThenBy' on type 'System.Linq.Queryable'
If I could get the code in the thread to work it would be nirvana.
All the examples I have seen use then within a where statement, but I need to use the function to do sorting.
I have written extensions using IQueryable, including ones for orderby and orderbydescending. The problem is thenby and thenbydescending use iorderedqueryable.
The error I get when using ThenByProperty is
Object of type 'System.Data.Entity.Infrastructure.DbQuery1[ORMModel.v_Brand]' cannot be converted to type 'System.Linq.IOrderedEnumerable1[ORMModel.v_Brand]'.
Do not get such an error when I use a comparable OrderByProperty extension.
what a mess, obviously I do not post often here. Anyway I am stumped and clueless so any tips are very appreciated.
Tried to post code but kept getting format errors so gave up. But help me anyways :)
If you use method syntax, you'll see func quite often, for instance in Where, GroupBy, Join, etc
Every method with some input parameters and one return value can be translated to a Func<...> as follows
MyReturnType DoSomething(ParameterType1 p1, ParameterType2, p2) {...}
Func<ParameterType1, ParameterType2, MyReturnType> myFunc = (x, y) => DoSomething(x, y);
The part Func<ParameterType1, ParameterType2, MyReturnType> means: a function with two input parameters and one return value. The input parameters are of type ParameterType1 and ParameterType2, in this order. The return value is of MyReturnType.
You instantiate an object of Func<ParameterType1, ParameterType2, MyReturnType> using a lambda expression. Before the => you type a declaration for the input parameters, after the => you call the function with these input parameters. If you have more than one input parameter you make them comma separated surrounded by brackets.
For a Where you need a Func<TSource, bool>. So a function that has as input one source element, and as result a bool:
Where(x => x.Name == "John Doe")
For a GroupJoin you need a resultSelector of type Func<TOuter,System.Collections.Generic.IEnumerable<TInner>,TResult> resultSelector
So this is a function with as input one element of the outer sequence, and a sequence of elements of the inner sequence. For example, to query Teachers with their Students:
var result = Teachers.GroupJoin(Students,
teacher => teacher.Id, // from every Teacher take the Id,
student => student.TeacherId, // from every Student take the TeacherId,
(teacher, students) => new
{
Id = teacher.Id,
Name = teacher.Name,
Students = students.Select(student => new
{
Id = student.Id,
Name = student.Name,
})
.ToList(),
});
Here you see several Funcs. TOuter is Teacher, TInner is Student, TKey is int
OuterKeySelector: Func<TOuter, TKey>: teacher => teacher.Id
InnerKeySelector: Func<TInner, TKey>: student => student.TeacherId
ResultSelector: Func<Touter, IEnumerable<TInner>, TResult>
The resultSelector is a function that takes one TOuter (a Teacher), and a sequence of TInner (all students of this Teacher) and creates one object using the input parameters
(teacher, students) => new {... use teacher and students }
When creating the lambda expression it is often helpful if you use plurals to refer to collections (teachers, students) and singulars if you refer one element of a collection (student).
Use the => to start defining the func. You can use input parameters that were defined before the => to define the result after the =>

Convert string value to entity in linq where query

I am using jqgrid in MVC 4. I have written a method for getting a list in linq.
In my function I am getting all values of jqgrid with search criteria i.e. Operator AND/OR
, operations Equals to, not equals to etc. Here I am also getting the column name like Name, City, State etc.
My problem is I can't pass the column name directly in linq query i.e. I have to use the column name as x => x.Name
switch (rule.field)
{
case "Name":
query = query.Where(x => x.Name.StartsWith(rule.data, StringComparison.OrdinalIgnoreCase));
break;
case "Course":
query = query.Where(x => x.Course.StartsWith(rule.data, StringComparison.OrdinalIgnoreCase));
break;
}
In rule.field I am getting column Name i.e. Name, city, state etc. I want to pass the column name which I am getting in rule.filed in LINQ query instead of x =>x.Name.
Is there any way to do it so I can avoid writing switch cases?
You can use System.Linq.Dynamic, which can be installed as a Nuget package along with string.Format. The syntax would then look something like..
var newquery = query.AsQueryable()
.Where(
string.Format("{0}.ToUpper().StartsWith(#0)", rule.field)
,rule.data.ToUpper());
You could always use reflection:
query = query.ToList().Where(p => {
var field = p.GetType().GetProperty(rule.field);
var value = (String) field.GetValue(p);
return value.StartsWith(rule.data, StringComparison.OrdinalIgnoreCase);
});
Warning: Reflection is slow. Only use this for short lists. Since you're dealing with UI rendering, I'm assuming this won't be a problem.
edit: My example is assuming all properties are indeed properties (and not fields), and that all properties are Strings. You may need to alter the code for your specific case.

Prioritizing fields when matching multiple fields with linq

I have a database with fields like firstname lastname street and searchfield. Anything that match the search field will be in my search subset here is the linq logic :
if (!String.IsNullOrEmpty(searchString))
{
folders = folders.Where(p => p.SearchField.ToLower().Contains(searchString.ToLower()));
}
I can order it by name or firstname or whatever.
Now I would like to present the results so it prioritize the name field in relation to my search term.
For example if i look for Schmid i want to show first the people with the LastName that match Schmid then the firstname then the street ...etc
Any idea ?
I hope I understood it correctly
var res =
folders
.Where(item => item.FirstName == name)
.Union(folders.Where(item => item.LastName == name))
/* Add more Union-Where statements */
;
I think the best approach is to get the matching objects first and then proceed in memory:
var lower = searchString.ToLower();
folders = folders
.Where(p => p.SearchField.ToLower().Contains(lower))
.ToArray();
folders = folders
.OrderBy(f => !f.LastName.Contains(lower))
.ThenBy(f => !f.FistName.Contains(lower))
.ThenBy(f => !f. ...
If you do all the OrderBy's on the IQueryable the query will probably blow up, while the initial filter is the most important thing to use the database engine for.
Note that you cannot always show the items that match lower in LastName and then those with a match in FistName etc., because there may be items that have a match in both. I don't think you want to duplicate items, do you?

Any way to add a property using linq?

So I have this list, it returns an ID and a thumbnail. ex. List<PersonPicture>
and I have this list, List<Person> which has a property named "picture" in it.
Is there anyway that I can merge this two properties and add the List<PersonPicture> to the property named "picture" in it and base this via the ID since they have the same?
Any help would be appreciated.
You can use an anonymous object for this, below an example:
List<PersonPicture> pictures = LoadPictures();
List<Person> persons = LoadPersons();
var result = persons.Select(pers => new
{
Id = pers.Id,
Name = pers.Name,
Picture = pictures.Where(pic => pic.PersId == pers.Id)
.FirstOrDefault()
.Thumbnail
};
Another solution is to use a Join:
var result = persons.Join(pictures,
pers => pers.Id,
pic => pic.PersId,
(pers, pic) => {
return new
{
Id = pers.Id,
Name = pers.Name,
Picture = pic.Thumbnail
};
});
LINQ isn't quite designed for modifying existing collections like this, but you can do it:
foreach (tup in people
.Join(
picture,
person => person.ID,
picture => picture.ID,
Tuple.Create
))
{
tup.Item1.Picture = tup.Item2;
}
EDIT: Note that this will produce unpredictable results if a person has more than one picture. Is this a possibility, and how should it be dealt with?
You could either use a Join or the Zip operator in linq. These links will take you to questions about the syntax of using both of them. Basically the Join just adds the two lists together based on a key just like in SQL and the Zip merges the two lists by matching the position of each element in each list..
You want to join the two lists based on a shared key -- the ID.
Basically, you want to use the Join operator in LINQ to find pairs of Person and PersonPicture that match the same ID:
persons.Join(pictures, // join these two lists
person => person.Id, // extract key from person
personPicture => personPicture.PersonId, // extract key from picture
(person, personPicture) => ??? // do something with each matching pair
The question you now face is what to do with each matching pair; Join lets you supply a delegate that takes a matching pair and returns something else, and the result of the Join operation will be a list of those 'something else's produced from each of the matching pairs.
Your problem is that you want to take each pair and do something with it -- specifically, you want to copy the picture from the PersonPicture object to the Person object. Since LINQ is all about finding data but not modifying it, this is not trivial.
You can do this in two ways. One is to create a temporary object from each pair, and then iterate over that and do your thing:
var pairs = persons.Join(pictures,
person => person.Id,
personPicture => personPicture.PersonId,
(person, personPicture) => new { person, personPicture };
foreach (var pair in pairs)
pair.person.Picture = pair.personPicture.Thumbnail;
(You can use a Tuple instead of a temporary object, as was suggested in another answer).
This works, but seems clumsy because of the temporary object (be it an anonymous object or a tuple).
Alternatively, you can do the assignment right inside the delegate, and return the Person object itself, since you're done with the PersonPicture object:
var personsWithPicturesPopulated = persons.Join(pictures,
person => person.Id,
personPicture => personPicture.PersonId,
(person, personPicture) => {
person.Picture = personPicture.Thumbnail;
return person;
});
This has the added bonus of giving you the list of persons for which you found a match in the personPictures list, omitting the ones without a match; this is sometimes exactly what you need (and other times it isn't, in which case you can discard the result of the join).

LINQ / EF - Only return items based on a list of ID's

I have a list of type int, which contains ID's. For example it may contain 1,2,5,8,16 or 2,3,6,9,10,12 etc..
I then want to return all of my "Enquiries" based on the ID's stored in my list (called vehicles) and return them as a list, something like:
var enquiries = context.Enquiries.Where(x => x.EnquiryID == vehicles.Any()).ToList();
But obviously this doesn't work, is there something similar I can do?
You likely want to use Contains. Contains (in Linq2SQL or EF) will be transformed into a WHERE/IN clause.
enquiries = context.Enquiries
.Where( x => vehicles.Contains( x.EnquiryID ) )
.ToList();

Resources