Help with LINQ query - c#

I currently a list of a Supplier class, within that supplier class is a list of orders.
Each order has a userID and an empty string variable for username.
I then have a list of users which contains userID and username.
The way I am doing this now is:
foreach(supplier s in SupplierList)
{
foreach (order o in s.childorders)
{
user u = _users.First(p => p.userid == o.userid);
o.username = u.username;
}
}
I feel this might be a little inefficient and I was wondering if it is possible to compact it down into one linq query?
The logic should be
set supplierslist.childorders.username to the value in _users where supplierslist.childorders.userid == _users.userid.
Im fairly new to Linq so any advice for this would be apreciated, or also if its a bad idea and to leave it as it is / reasons why would be good too.
Thanks

What you want to do here is iterate over a collection (many collections, really, but it doesn't make a difference) and mutate its members. LINQ is not really targeted at performing mutating operations but rather at querying. You can do it with LINQ, but it's against the spirit of the tool.
If you are constructing the SupplierList yourself, it might be possible to fetch the data appropriately with LINQ so that it comes pre-populated as you want it to be.
Otherwise, I 'd leave the foreach as it is. You can make a dictionary that maps ids to users to make the inner loop faster, but that's your call and it depends on your data size.

var orderUserPairs = SupplierList
.SelectMany(s => s.ChildOrders)
.Join(_users, o => o.UserId, u => u.userId, (Order, User) => new {Order, User});
foreach (var orderUserPair in orderUserPairs)
orderUserPair.Order.username = orderUserPair.User.username;
Though having both username and userId as part of order looks suspicious.

First a question...
It looks like you are operating on every order. Why do you need to cycle through the supplierlist first since you don't seem to be using it inside the loop? Unless there are orders that don't belong to any supplierlist, you might be able to skip that step.
If that isn't the case, then I think you can use a join. If you aren't familiar with the syntax for joins in linq, this is one (simplified) way to approach it:
var x = from S in SupplierList
join C in childorders on C.supplierlistID equals S.ID
where [whatever you need here if anything]
select new { field1, field2};
foreach var y in x
{
}
Note I assumed a foreign key in childorders to supplierlist. If that isn't the case you will have to modify accordingly.
Hope that helps.

You need to use SelectMany or join depending on weather you are using linq-to-sql or linq with local collections. If you are using local collections the better way is to use join, else use SelectMany.
Like this...join:
var selection = (from s in SupplierList
join o in s.childholders on s.userid equals o.userid
select new { username = o.username);
or, in case of linq-to-sql:
var selection = (from s in SupplierList
from o in s.childholders
select { username = o.username);
You can then use the anonymous type you projected the way you want.

I agree with Jon, but you could say:
var orders = (from s in supplier
from o in s.childorders
select new
{
Order = o,
User = _users.First(p => p.userid == o.userid)
}).ToList();
foreach(var order in orders) {
order.Order.username = order.User.username;
}
Untested of course :)

If users list contains many elements, it can be really slow so I'd use a temporary dictionary:
var userById = users.GroupBy(x => x.userid)
.ToDictionary(x => x.Key, x => x.First());
foreach(var order in supplier.SelectMany(x => x.childorders))
{
order.username = userById[order.userid].username;
}

Related

How do I filter duplicates in Entity Framework minimize performance loss?

How can I make the performance better of the code below?
I'm loading the carItems from an external webservice in a list.
carItem is checked whether it exists in EF.
If carItem is new, then it's mapped to carsCol and added to the database. What are some easy ways to improve the performance of this using code?
carItems = carItems.Where(x => x.Name == "Tesla");
// Filter existing cars
List<Car> carsCol = new List<Car>();
foreach (var item in carItems)
{
if (GetById(item.Id) == null)
{
carsCol.Add(item);
}
}
Entities.AddRange(carsCol);
Depending on the situation, you can try to find out which ids already exist in the database by making a single query before the foreach.
var newCarItemIds = carItems.Select(x => x.Id);
var alreadyExistentCarItemIds = Entities.CarItems.Where(x => newCarItemIds.Contains(x.Id)).Select(x=>x.Id);
foreach(var item in carItems)
{
if(!alreadyExistentCarItemIds.Contains(x))
{
carsCol.Add(item);
}
}
You can use Distinct Linq functions: example
You must to implement IEquatable interface
I would propose this. You can do a left outter join with the cars that you get from your API and the existing one. Than you get the new cars that will be added.
var newCars = (from c in carItems
join e in Entities.CarItems on c.Id equals e.Id into g
from x in g.DefaultIfEmpty()
select new { c, IsNew = x == null }).Where(x = x.IsNew).ToList()
With that you do only one access to the database. Also when working with IEnumerable is always good to convert it to either a list or array, that way each time that you iterates through that object you don't run a query in your database.

Multiple joins with multiple on statements using Linq Lambda expressions [duplicate]

Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for

LINQ: Is there a way to combine these queries into one?

I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.

How do I select an object by a sub-property

i've got a List of objects, lets call them Product, which each of them contains a bunch of properties and also a List of Version (which are also objects).
Version also has a bunch of properties and does contain a List of Customer (which again are objects).
Customer again has properties, one of them is its ID (=Guid).
What i try to do is to make a List of Product, selected by a certain ID of its Product.VersionList.Version.ID.
I would prefere a join query, but every efficient way is welcome. I tried so far this, but because i have only a single ID to compare with, i don't know how to construct the join.
lp = List<Entity.Product>;
g = GetGuid();
var query = from product in Entity.ProductCollection
join g in g
on product.Version.Where(x => x.id == g)
select product;
lp.AddRange(query);
I'm guessing you mean:
var query = from product in Entity.ProductCollection
where product.Version.Any(x => x.id == g)
select product;
i.e. select all the products that have a version where the id matches the guid you were thinking of.
Note that joining to the versions would cause product duplication if any product has multiple matching versions.
Try this .... May be you wants more deep digging on it..
var query = from Product product in pc
from varsion in product.Version
let v= varsion as Entity.Version
where v.id == g
select product;
var query = Entity.ProductCollection.Where(p => p.Version.Any(v => v.Id == g));
You can use Any rather than having to do a self join.

query list with linq lambda expressions

How would I get participants that are in a list of counties? I get the counties in var counties, then I want to get all of the participants that have a CountyOfParticipationId that is in the list of counties.
if (collaborationId != null)
{
var counties = (from c in db.CountyCollaborations
where c.CollaborationId == collaborationId
select c).ToList();
participants = participants.Where(p => p.CountyOfParticipationId in counties);
}
.Where(p => counties.Contains(p.CountyOfParticipationId))
Now if there's a lot of data be careful with the complexity of this. Contains in a list is O(n), so overall the algorithm is O(n*m) with n,m being the # of participants and the # of counties.
For better performance you could store the counties in a HashSet, which has O(1) Contains, meaning O(n) overall.
Of course if the number of counties is small it doesn't matter, really.
EDIT: Just noted that your list doesn't contain the ids but full objects. For the code above to work you also need to change your linq query from select c to select c.Id or something like that (don't know the name of the field).
participants = participants
.Where(p => counties.Any(c=> c.CountyId == p.CountyOfParticipationId) )
Or
participants.Where(p => p.County.CollaborationId == collaborationId)
should also work if you have set up relations properly
This might be better in some situations since you won't have to store counties separately if the linq method is translating the expression to sql behind the scences.
participants = (from p in participants
join c in
db.CountyCollaborations
.Where(cty=>cty.CollaborationId == collaborationId)
on p.CountyOfParticipationId equals c.CountyId
select p);
Assuming each county has a CountyId:
participants = participants.Where( p =>
counties.Select(c=> c.CountyId ).Contains( p.CountyOfParticipationId) );

Categories

Resources