Prioritizing fields when matching multiple fields with linq - 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?

Related

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

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

Select from multiple tables based upon search term but good in performance

I have a query in which I pass the search term to filter the list of Companies, either by Email or company Title which is stored in another table (TranslationTexts) as Text column for multiple locales.
The query runs fine but it is very heavy and takes time. How can I make it more efficient?
See Table Diagram Image
The query:
gm.ListData = context.Companies.ToList()
.Where(a => a.AspNetUser.Email.NullableContains(searchTerm) ||
a.TitleTranslation.TranslationTexts
.Where(b => b.Text.NullableContains(searchTerm)).Any()
).Select(c => new ListCompany
{
CompanyID = c.CompanyID,
EmailID = c.AspNetUser.Email,
Title = c.TitleTranslation.TranslationTexts.FirstOrDefault(d => d.Locale == Locale).Text
}).ToList();

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).

LINQTOSQL Help needed

I'm trying to add a column to the following LINQ expression. I want the column to contain a string concatenation of a text value in a many table called WasteItems. The join would be on "Waste.WasteId = WasteItem.WasteId". My problem is I need to display in a single dynamic column a string such as "EW (5); EX (3)" if there was 8 records in WasteItem and the column containing the 2 character string was called WasteItem.EWC. Hope that makes sense, there must be an efficient way since I realise LINQ is very powerfull. I'm new to it and not sure how to start or go about this:
return from waste in this._db.Wastes
where (from u in _db.UsersToSites.Where(p => p.UserId == userId && p.SystemTypeId == SystemType.W)
select u.SiteId)
.Contains(waste.SiteId)
orderby waste.Entered descending select waste;
THANKS IN ADVANCE
Something like this should do:
wastes.GroupJoin(db.WasteItems, w => w.WastId, wi => wi.WasteId, (w,wi) => new { w, wi })
.AsEnumerable()
.Select(x => new
{
x.w.Name,
Items = string.Join(", ", x.wi.GroupBy(wi => wi.EWC).Select(g => string.Format("{0} ({1})", g.Key, g.Count())))
})
Where wastes is the result from your query. The AsEnumerable() is necessary because Entity Framework can not handle string.Join, so that part must be dealt with in memory.
I could not check the syntax, obviously, but at least it may show you the way to go.

Entity Framework 4 search on combined fields

How do I search on two combined fields. The search should happen on the SQL end if possible.
So say I have a customer table with first name and last name. I would like users to be able to search on both of those columns using a single search box.
My query currently looks like this:
var query = DbContext.Customers
.Where(c => c.FirstName.Contains(search) || c.LastName.Contains(search));
but it should be something like
var query = DbContext.Customers
.Where(c => c.FullName.Contains(search));
It is not possible unless you have FullName column also mapped. The way around this problem can be String.Concat which is allowed in Linq-to-entities:
var query = DbContext.Customers
.Where(p => String.Concat(p.FirstName, " ", p.LastName)
.Contains(search));
You could use a computed column in the database and map that
e.g.
alter table Customer add FullName AS FirstName + ' ' + LastName
(Not pretty I know)

Resources