What does it mean and why (if at all) is it important?
It means you can add additional "operators" to a query. It's important because you can do it extremely efficiently.
For example, let's say you have a method that returns a list (enumerable) of employees:
var employees = GetEmployees();
and another method that uses that one to return all managers:
IEnumerable<Employee> GetManagers()
{
return GetEmployees().Where(e => e.IsManager);
}
You can call that function to get managers that are approaching retirement and send them an email like this:
foreach (var manager in GetManagers().Where(m => m.Age >= 65) )
{
SendPreRetirementMessage(manager);
}
Pop quiz: How many times will that iterate over your employees list? The answer is exactly once; the entire operation is still just O(n)!
Also, I don't need to have separate methods for this. I could compose a query with these steps all in one place:
var retiringManagers = GetEmployees();
retiringManagers = retiringManagers.Where(e => e.IsManager);
retiringManagers = retiringManagers.Where(m => m.Age >= 65);
foreach (var manager in retiringMangers)
{
SendPreRetirementMessage();
}
One cool thing about this is that I can change is at run time, such that I can include or not include one part of the composition inside an if block, such that the decision to use a specific filter is made at run time, and everything still comes out nice and pretty.
I think it means that you can daisy chain your queries, like this
var peterJacksonsTotalBoxOffice
= movies.Where(movie => movie.Director == "Peter Jackson")
.Sum(movie => movie.BoxOffice);
Related
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).
I want to return a list of objects stored in a database using LINQ queries.
I tried the following
public BO.Hotel getHotels()
{
TripBagEntities db = new TripBagEntities();
var hotels = (from m in db.HotelEntities
where m.id < 10
select m).ToList().First();
return Mapper.ToHotelObject(hotels);
}
This returns only the first item in the list. How can I return the entire list?
Thanks in advance
Firstly, as per the comment, you should understand exactly what each line of your existing code does first. (You should also try to follow .NET naming conventions.) If you're guessing around which bit of your code does what, it would be a good idea to read a good tutorial geared towards the LINQ provider you're using (Entity Framework?).
We don't really know what Mapper.ToHotelObject does, or whether there's already a method for converting a whole sequence. This should work though:
public List<BO.Hotel> GetHotels()
{
// Note: you may want a using statement here...
TripBagEntities db = new TripBagEntities();
var hotels = db.HotelEntities
.Where(m => m.id < 10)
.AsEnumerable()
.Select(Mapper.ToHotelObject)
.ToList();
}
Or if the method group conversion doesn't work:
public List<BO.Hotel> GetHotels()
{
// Note: you may want a using statement here...
TripBagEntities db = new TripBagEntities();
var hotels = db.HotelEntities
.Where(m => m.id < 10)
.AsEnumerable()
.Select(m => Mapper.ToHotelObject(m))
.ToList();
}
Note that I've used "dot notation" for the whole query, as it makes life simpler when you're using things like AsEnumerable and ToList, and your query expression wasn't complicated anyway.
The AsEnumerable call "shifts" the query into LINQ to Objects, so that the query-to-SQL translation part doesn't need to try to convert Mapper.ToHotelObject into SQL, which I assume would fail.
You need to make your function return List<BO.Hotel> instead of a Hotel, and adjust your query and Mapper function accordingly.
public List<BO.Hotel> getHotels()
{
TripBagEntities db = new TripBagEntities();
return (from m in db.HotelEntities
where m.id < 10
select Mapper.ToHotelObject(m)).ToList();
}
Can anyone explain what the difference is between:
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.OrderBy(sort2 => sort2.InvoiceOwner.FirstName)
.OrderBy(sort3 => sort3.InvoiceID);
and
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.ThenBy(sort2 => sort2.InvoiceOwner.FirstName)
.ThenBy(sort3 => sort3.InvoiceID);
Which is the correct approach if I wish to order by 3 items of data?
You should definitely use ThenBy rather than multiple OrderBy calls.
I would suggest this:
tmp = invoices.InvoiceCollection
.OrderBy(o => o.InvoiceOwner.LastName)
.ThenBy(o => o.InvoiceOwner.FirstName)
.ThenBy(o => o.InvoiceID);
Note how you can use the same name each time. This is also equivalent to:
tmp = from o in invoices.InvoiceCollection
orderby o.InvoiceOwner.LastName,
o.InvoiceOwner.FirstName,
o.InvoiceID
select o;
If you call OrderBy multiple times, it will effectively reorder the sequence completely three times... so the final call will effectively be the dominant one. You can (in LINQ to Objects) write
foo.OrderBy(x).OrderBy(y).OrderBy(z)
which would be equivalent to
foo.OrderBy(z).ThenBy(y).ThenBy(x)
as the sort order is stable, but you absolutely shouldn't:
It's hard to read
It doesn't perform well (because it reorders the whole sequence)
It may well not work in other providers (e.g. LINQ to SQL)
It's basically not how OrderBy was designed to be used.
The point of OrderBy is to provide the "most important" ordering projection; then use ThenBy (repeatedly) to specify secondary, tertiary etc ordering projections.
Effectively, think of it this way: OrderBy(...).ThenBy(...).ThenBy(...) allows you to build a single composite comparison for any two objects, and then sort the sequence once using that composite comparison. That's almost certainly what you want.
I found this distinction annoying in trying to build queries in a generic manner, so I made a little helper to produce OrderBy/ThenBy in the proper order, for as many sorts as you like.
public class EFSortHelper
{
public static EFSortHelper<TModel> Create<TModel>(IQueryable<T> query)
{
return new EFSortHelper<TModel>(query);
}
}
public class EFSortHelper<TModel> : EFSortHelper
{
protected IQueryable<TModel> unsorted;
protected IOrderedQueryable<TModel> sorted;
public EFSortHelper(IQueryable<TModel> unsorted)
{
this.unsorted = unsorted;
}
public void SortBy<TCol>(Expression<Func<TModel, TCol>> sort, bool isDesc = false)
{
if (sorted == null)
{
sorted = isDesc ? unsorted.OrderByDescending(sort) : unsorted.OrderBy(sort);
unsorted = null;
}
else
{
sorted = isDesc ? sorted.ThenByDescending(sort) : sorted.ThenBy(sort)
}
}
public IOrderedQueryable<TModel> Sorted
{
get
{
return sorted;
}
}
}
There are a lot of ways you might use this depending on your use case, but if you were for example passed a list of sort columns and directions as strings and bools, you could loop over them and use them in a switch like:
var query = db.People.AsNoTracking();
var sortHelper = EFSortHelper.Create(query);
foreach(var sort in sorts)
{
switch(sort.ColumnName)
{
case "Id":
sortHelper.SortBy(p => p.Id, sort.IsDesc);
break;
case "Name":
sortHelper.SortBy(p => p.Name, sort.IsDesc);
break;
// etc
}
}
var sortedQuery = sortHelper.Sorted;
The result in sortedQuery is sorted in the desired order, instead of resorting over and over as the other answer here cautions.
if you want to sort more than one field then go for ThenBy:
like this
list.OrderBy(personLast => person.LastName)
.ThenBy(personFirst => person.FirstName)
Yes, you should never use multiple OrderBy if you are playing with multiple keys.
ThenBy is safer bet since it will perform after OrderBy.
I want to do something like DELETE FROM TABLE WHERE ID NOT IN (1,2,3) AND PAGEID = 9
I have a List of IDS but that could be changed if needs be. I can't work out how to get a boolean result for the LINQ parser.
Here is what Subsonic expects I think.
db.Delete(content => content.PageID == ID).Execute();
I can't work out how to do the NOT IN statement. I've tried the List.Contains method but something not quite right.
UPDATE: One alternative is to do:
var items = TABLE.Find(x => x.PageID == ID)'
foreach(var item in items)
{
item.Delete();
}
This hits the database a lot more though
When you say "something not quite right" what exactly do you mean?
I'd expect to write:
List<int> excluded = new List<int> { 1, 2, 3 };
db.Delete(content => !excluded.Contains(content.PageID)).Execute();
Note that you need to call Contains on the array of excluded values, not on your candidate. In other words, instead of saying "item not in collection" you're saying "collection doesn't contain item."
Try .Contains:
db.Delete(content => content.PageID.Contains(<Array containing ID's>).Execute();
(the above is just an example, might need some polishing for your specific situation)
I have found that this works but its not via LINQ
var table = new WebPageContentTable(_db.DataProvider);
var g = new SubSonic.Query.Delete<WebPageContent(_db.DataProvider)
.From(table)
.Where(table.ID)
.NotIn(usedID)
.Execute();
I have found that this does work and via LINQ - however it hits the database multiple times.
var f = WebPageContent.Find(x => !usedID.Any(e => e == x.ID));
if (f.Count > 0)
{
var repo = WebPageContent.GetRepo();
repo.Delete(f);
}
This I imagine would work in one hit to the database but I get an exception thrown in QueryVisitor::VisitUnary
WebPageContent.Delete(x => !usedID.Any(e => e == x.ID));
I felt like the following should be possible I'm just not sure what approach to take.
What I'd like to do is use the include method to shape my results, ie define how far along the object graph to traverse. but... I'd like that traversal to be conditional.
something like...
dealerships
.include( d => d.parts.where(p => p.price < 100.00))
.include( d => d.parts.suppliers.where(s => s.country == "brazil"));
I understand that this is not valid linq, in fact, that it is horribly wrong, but essentially I'm looking for some way to build an expression tree that will return shaped results, equivalent to...
select *
from dealerships as d
outer join parts as p on d.dealerid = p.dealerid
and p.price < 100.00
outer join suppliers as s on p.partid = s.partid
and s.country = 'brazil'
with an emphasis on the join conditions.
I feel like this would be fairly straight forward with esql but my preference would be to build expression trees on the fly.
as always, grateful for any advice or guidance
This should do the trick:
using (TestEntities db = new TestEntities())
{
var query = from d in db.Dealership
select new
{
Dealer = d,
Parts = d.Part.Where
(
p => p.Price < 100.0
&& p.Supplier.Country == "Brazil"
),
Suppliers = d.Part.Select(p => p.Supplier)
};
var dealers = query.ToArray().Select(o => o.Dealer);
foreach (var dealer in dealers)
{
Console.WriteLine(dealer.Name);
foreach (var part in dealer.Part)
{
Console.WriteLine(" " + part.PartId + ", " + part.Price);
Console.WriteLine
(
" "
+ part.Supplier.Name
+ ", "
+ part.Supplier.Country
);
}
}
}
This code will give you a list of Dealerships each containing a filtered list of parts. Each part references a Supplier. The interesting part is that you have to create the anonymous types in the select in the way shown. Otherwise the Part property of the Dealership objects will be empty.
Also, you have to execute the SQL statement before selecting the dealers from the query. Otherwise the Part property of the dealers will again be empty. That is why I put the ToArray() call in the following line:
var dealers = query.ToArray().Select(o => o.Dealer);
But I agree with Darren that this may not be what the users of your library are expecting.
Are you sure this is what you want? The only reason I ask is, once you add the filter on Parts off of Dealerships, your results are no longer Dealerships. You're dealing in special objects that are, for the most part, very close to Dealerships (with the same properties), but the meaning of the "Parts" property is different. Instead of being a relationship between Dealerships and Parts, it's a filtered relationship.
Or to put it another way, if I pull a dealership out of your results and passed to a method I wrote, and then in my method I call:
var count = dealership.Parts.Count();
I'm expecting to get the parts, not the filtered parts from Brazil where the price is less than $100.
If you don't use the dealership object to pass the filtered data, it becomes very easy. It becomes as simple as:
var query = from d in dealerships
select new { DealershipName = d.Name,
CheapBrazilProducts = dealership.Parts.Where(d => d.parts.Any(p => p.price < 100.00) || d.parts.suppliers.Any(s => s.country == "brazil")) };
If I just had to get the filtered sets like you asked, I'd probably use the technique I mentioned above, and then use a tool like Automapper to copy the filtered results from my anonymous class to the real class. It's not incredibly elegant, but it should work.
I hope that helps! It was an interesting problem.
I know this can work with one single Include. Never test with two includes, but worth the try:
dealerships
.Include( d => d.parts)
.Include( d => d.parts.suppliers)
.Where(d => d.parts.All(p => p.price < 100.00) && d.parts.suppliers.All(s => s.country == "brazil"))
Am I missing something, or aren't you just looking for the Any keyword?
var query = dealerships.Where(d => d.parts.Any(p => p.price < 100.00) ||
d.parts.suppliers.Any(s => s.country == "brazil"));
Yes that's what I wanted to do I think the next realease of Data Services will have the possiblity to do just that LINQ to REST queries that would be great in the mean time I just switched to load the inverse and Include the related entity that will be loaded multiple times but in theory it just have to load once in the first Include like in this code
return this.Context.SearchHistories.Include("Handle")
.Where(sh => sh.SearchTerm.Contains(searchTerm) && sh.Timestamp > minDate && sh.Timestamp < maxDate);
before I tried to load for any Handle the searchHistories that matched the logic but don't know how using the Include logic you posted so in the mean time I think a reverse lookup would be a not so dirty solution