Linq select a value based on where condition from other table - c#

I have this query:
var result = (from game in db.Games
join gameevent in db.Events
on game.GameId equals gameevent.GameId into events
from _event in events
join _targetObjects in db.TargetObjects
on _event.TargetObject equals _targetObjects.TargetObjectId into targetss
where game.userId == userId
select new ProfileViewModel
{
record = events.Where(s => s.TargetObjectId == _event.TargetObject && _event.EventType == 35).Select(/* Here I want to select a value from targetss, field called TargetName */).ToList()
}).First();
As you can see, I want to get value based on where clause from other table. Is that possible in the select new part?
I want to get the name of the targetObject based on the targetObjectId which matches the targetObjectId in events table and also the event type should be 35.

If the query starts to become a too complex one then it is worth splitting it into parts.
In my example below I use the extension method syntax of LINQ instead of query keywords just because it is easier for me to use.
// First we collect the relevant games.
var games =
db
.Games
.Where(game => game.UserId == userId);
// Then we collect the events of the collected games that have the specified event type.
var events =
db
.Events
.Join(
games,
gameEvent => gameEvent.GameId,
game => game.GameId,
(gameEvent, game) => gameEvent
)
.Where(gameEvent => gameEvent.EventType == 35);
// Then we collect the target objects based on the collected events.
var targetObjects =
db
.TargetObjects
.Join(
events,
targetObject => targetObject.TargetObjectId,
gameEvent => gameEvent.TargetObjectId,
(targetObject, gameEvent) => targetObject
);
// Last we select the target name from the collected target objects.
var records =
targetObjects
.Select(targetObject => targetObject.TargetName)
.ToList(); // The query will be executed at this point.
If this is not what you are looking for, please clarify what data the ProfileViewModel should have exactly and from which set should it be selected as I am not very familiar with this syntax.

Related

EF Linq group by ICollection of objects

All,
I have a Linq query which fetches a list of events which works great. The problem I'm facing is that Events contains a ICollection of Artists called headliners and in the list I only want 1 event per,set of, Artist(s).
The query underneath works fine but: I require a top 10 of Events but only one Event per, set of, artist(s) for sorting the popularity of the artist with highest popularity can be used - not what i want.
Context.Events
.Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified)
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity))
.Take(10)
.ToList();
How can I adjust the query above that I only get one Event per Artist. I would need to do some sort of grouping to see if the event is performed by same (set of) Artist(s).
I'm looking into using the Artist's primary key but because it is an collection i cannot get it to work. I already tried the String.Join to get a single unique key for the headliners. This is however not support in entity framework.
Is this something that can (gracefully) be supported by Linq to EF?
The following SQL query does almost what i want expect that it won't work with multiple artist for the same event
SELECT MAX(E.EventId), MAX(E.Name)
FROM [dbo].[Events] E
INNER JOIN [dbo].[Stages] S ON E.StageId = S.StageId
INNER JOIN [dbo].[Venues] V ON S.VenueId = V.VenueId
INNER JOIN [dbo].[Areas] A ON V.AreaId = A.AreaId
INNER JOIN [dbo].[Headliners] H ON E.EventId = H.EventId
INNER JOIN [dbo].[Artists] A2 ON A2.ArtistId = H.ArtistId
WHERE E.IsVerified = 1 AND E.StartDateTimeUtc>GETDATE() AND A.AreaId = 1
GROUP BY A2.ArtistId, A2.Name, A2.EchoNestHotttnesss
ORDER BY A2.EchoNestHotttnesss desc
Challenging task, but here it is:
var availableEvents = db.MusicEvents.Where(e =>
e.Stage.Venue.AreaId == 1 && e.StartDateTimeUtc > DateTime.UtcNow && e.IsVerified);
var topEvents =
(from e1 in availableEvents
where e1.Headliners.Any() &&
!availableEvents.Any(e2 => e2.StartDateTimeUtc < e1.StartDateTimeUtc &&
!e2.Headliners.Any(a2 => !e1.Headliners.Any(a1 => a1.Id == a2.Id)) &&
!e1.Headliners.Any(a1 => !e2.Headliners.Any(a2 => a2.Id == a1.Id)))
orderby e1.Headliners.Max(a => a.Popularity) descending
select e1)
.Take(10)
.ToList();
The first subquery (availableEvents) is just for reusing the "availability" filter inside the main query. It does not execute separately.
The critical part is the condition
!availableEvents.Any(e2 => e2.StartDateTimeUtc < e1.StartDateTimeUtc &&
!e2.Headliners.Any(a2 => !e1.Headliners.Any(a1 => a1.Id == a2.Id)) &&
!e1.Headliners.Any(a1 => !e2.Headliners.Any(a2 => a2.Id == a1.Id)))
The idea is to exclude the later events for the same set of headliners. It should be read this way:
Exclude the event if there is another available event starting earlier and there is no at least one artist from either event that is not headliner of the other event (i.e. they have the same headliner set).
Edit:
A pretty decent partial-LINQ lazily executed solution could be done in this way:
Firstly, get your query up to the ordered events based on popularity:
var evArtists = Context.Events
.Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified)
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity));
Secondly, since a ICollection<Artist> can be unordered yet forming equal set, creates an intermediate function to check if two ICollection<Artist> are of identical members:
private bool areArtistsEqual(ICollection<Artist> arts1, ICollection<Artist> arts2) {
return arts1.Count == arts2.Count && //have the same amount of artists
arts1.Select(x => x.ArtistId)
.Except(arts2.Select(y => y.ArtistId))
.ToList().Count == 0; //when excepted, returns 0
}
Thirdly, use the above method to get the unique artists set in the query results, put the results in a List, and fill the List with the number of elements you need (say, 10 elements):
List<Events> topEvList = new List<Events>();
foreach (var ev in evArtists) {
if (topEvList.Count == 0 || !topEvList.Any(te => areArtistsEqual(te.Headliners, ev.Headliners)))
topEvList.Add(ev);
if (topEvList.Count >= 10) //you have had enough events
break;
}
Your result is in the topEvList.
Benefits:
The solution above is lazily executed and is also pretty decent in the sense that you can really break down the logic and check your execution piece by piece without breaking the performance.
Note that using the method above you do not need to refer to the evArtists (which is your large query) other than by its individual element ev. Using full-LINQ solution is possible, yet you may need to refer to evArtists.Any to find the duplicates set of artists (as you do have have memory of what sets has been chosen before) from the original ordered query itself (rather than by simply using its element (ev) one by one).
This is possible because you create a temporary memory topEvList which records what sets have been chosen before and only need to check if the next element (ev) is not among the already selected set of artists. Thus, you do not impair your performance by checking you set of artists against the whole ordered query every time.
Original:
You are almost there actually. What you further need are LINQ GroupBy and First, and put your Take(10) the last:
var query = Context.Events
.Where(x => x.Stage.Venue.AreaId == 1 && x.StartDateTimeUtc > DateTime.UtcNow && x.IsVerified)
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity))
.GroupBy(a => a.ArtistId)
.Select(e => e.First())
.Take(10);
Since in by this query you have sorted your headliner artist:
.OrderByDescending(x => x.Headliners.Max(y => y.Popularity))
Then you only need to group your headliners by ArtistId:
.GroupBy(a => a.ArtistId)
Thus each artist would be having one group. Then next, you only want the first element in the group (supposedly the most popular Event per Artist):
.Select(e => e.First())
And thus you will get all the most popular events per artist. And lastly, among these most popular events per artist, you only want to take 10 of them, thus:
.Take(10);
And you are done!

C# Linq multiple GroupBy and Select

I have two objects that are linked, States and Cities, so each State has his Cities and each Citie is linked to an State. I also have some Units that have stateID and citieID but they are not linked since i have them only in Json.
What i need is to get only the States and Cities that have Units. I managed to get the first two but was wondering if there was any faster way to do it since i will have to make an update on those datas everyday:
//unitsData have a List of Units objects, this only have stateID, citieID and the unit data
var unitsData = objUnidade.BuscaUnidades();
//unitsState have all units grouped by State, here i also only have stateID and citieID, same data as above
var unitsState = unitsData.GroupBy(x => x.codigoEstado);
//Here is where i make my search inside the unidadesEstados and select only the Estados that i need
var activeStates = unitsState.Select(est => db.States.FirstOrDefault(x => x.ID == est.Key)).Where(state => state != null).ToList();
To do the Cities search i'm doing the same but using an extra foreach, is there a way to make this better ?
You are querying the database multiple times. It's better to use a SELECT ... IN query, which in LINQ looks like:
var units = objUnidad.BuscaUnidades();
var stateIds = units.Select(u => u.codigoEstado).ToList();
var activeStates = db.States.Where(s => stateIds.Contains(s.Id)).ToList();
EDIT: you asked about cities as well. It's more of the same:
var cityIds = units.Select(u => u.codigoCuidad).ToList()
var activeCities = db.Cities.Where(c => cityIds.Contains(c.Id)).ToList();
This solution gives you every city whose ID is referred to by a unit. #StriplingWarrior 's solution will give you every city in (the states that have a unit).
If db.States queries the database, then for each group in unitsState the query will get executed. If the number of states isn't extremely large, you can store them in a list.
var dbStates = db.States.ToList();
var activeStates = unitsState.Select(est => dbStates.FirstOrDefault(x => x.ID == est.Key)).Where(state => state != null).ToList();

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.

Updating a property of an object using LINQ when value needed is from db

How do you suppose I tackle this? Basically, I have this inital query:
var orders = (from order in _dbContext.Orders
join orderDetail in _dbContext.OrderDetails on order.ID equals orderDetail.OrderID
where order.StoreID == storeID
select new Order
{
ID = order.ID,
No = order.ID,
Type = "", // Notice that this is empty; this one needs updating
Quantity = order.Quantity,
// more properties here
}).AsQueryable();
After this query, I need to loop through the result and update the Type property based on different criteria like this:
string type = "";
foreach (OrderDetailDto order in orders)
{
if (order.UserID != null)
type = "UserOrder";
else if (order.UserID == null)
type = "NonUserOrder";
else if (order.Cook == null && (order.Option == "fiery"))
type = "FieryCook";
else if (check if this has corresponding records in another table) // this part I don't know how to effectively tackle
type = "XXX";
// Update.
order.Type = type;
}
The problem is one of my criteria needs me to check if there are existing record in the database. I would use JOIN but if I have to loop thru several hundreds or thousands of records and then JOIN each one of them then check on db just to get one value, I think that would be very slow.
I can't do the JOIN on the initial query because I might do a different JOIN based on a different criterion. Any ideas?
You could just join all the lookup tables you might possibly need in left join type way:
from o in Orders
from c in Cooks.Where(x => x.OrderId == m.OrderId).DefaultIfEmpty()
from u in Users.Where(x => x.OrderId == o.OrderId).DefaultIfEmpty()
select new
{
Order = m,
Cook = c,
User = u
}
or depending on your usage patterns you could build the required tables into local Lookups or Dictionaries for linear time searching thereafter:
var userDict = Users.ToDictionary(x => x.UserId);
var userIdDict = Users.Select(x => x.UserId).ToDictionary(x => x);
var cooksLookup = Cooks.ToLookup(x => x.Salary);

using linq to order tickets by lowest date where ticket is not closed?

I have an object called Ticket with that contains a list of objects called TicketActions. The Ticket object has a field called Date_Closed and the Actions object has a field called Action_Date:
Ticket
Date_Closed
TicketActions
-Action_Date
What I'm trying to do is order a List of tickets (List) based on the latest date of each Action in ascending order where the Ticket does not have a value for Date_Closed. The goal is to load this list into a listview and show tickets in a way that displays tickets in order on the page, placing the ones that have gone the longest without an action at the top. Does that make sense?
Here is what I ended up with so far that isn't working:
protected List<FullTicket> BuildTickets(int ticketsToShow)
{
using (var db = new SupportLogDBDataContext())
{
var result =
(from ticket in db.Support_Tickets
join status in db.Ticket_Statuses on ticket.Status_ID equals status.ID
select new FullTicket
{
TicketID = ticket.ID,
DateOpened = (DateTime)ticket.Date_Opened,
DateClosed = (DateTime)ticket.Date_Closed,
Subject = ticket.Subject,
Status = new KeyPair { Key = status.Status, Value = status.ID },
CreatedBy = new GuidPair { Key = ticket.Reported_By, Value = (Guid)ticket.AD_GUID },
TicketActions =
(from a in db.Ticket_Actions
where a.Ticket_ID == ticket.ID
select a).ToList()
}).Take(ticketsToShow).ToList();
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max()); //error on this line (invalid arguments)
return result;
}
}
People reply quick here!
Try this:
var result = (from ticket in tickets
where !ticket.DateClosed.HasValue
select ticket).OrderByDescending(t => (from a in t.TicketActions
select a.ActionDate).Max());
From here you can take as many as you need.
David B's analysis is slightly off. The line...
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
... will not compile because the argument to the Where method is not a lambda expression or delegate.
I would suggest this solution (assuming that the relevant property of the TicketAction type is ActionDate):
return result.Where(i => i.DateClosed == null)
.OrderBy(i => i.TicketActions.Max(a => a.ActionDate));
Or, in query comprehension syntax:
return from i in result
where i.DateClosed == null
orderby i.TicketActions.Max(a => a.ActionDate)
select i;
Here is some simple code.
var sorted = tickets.Where(t => t.DateClosed == null)
.OrderBy(t => t.TicketActions.Max(ta => ta.Action_Date.Ticks));
Sorry, I prefer LINQ function syntax, but if you want it in query syntax, it shouldn't be too hard to convert.
result.OrderBy(i => i.TicketActions.Where(i.DateClosed == null).Max());
This line generates an error because TicketActions.Max() is not defined.
You need to project TicketAction into something that can be Max'd. For example:
result.OrderBy(i =>
i.TicketActions
.Where(ta => i.DateClosed == null)
.Select(ta => ta.Id)
.Max()
);
Also note:
OrderBy does not modify its source. OrderBy returns an ordered IEnumerable, which you didn't assign anywhere.
OrderBy's enumerable is deferred, and you want a List result instead, so you should call ToList.
You are accessing Ticket.TicketActions outside of the query. This will cause one database round trip per ticket to load that property.
Here is a modification to your query that avoids the problems mentioned above by ordering and using navigational properties within the query.
from ticket in db.Support_Tickets
where ticket.DateClosed == null
let lastDate = ticket.TicketActions
.Select(ta => ta.ActionDate)
.OrderByDescending(date => date)
.FirstOrDefault()
let ticketStatus = ticket.TicketStatus
order by lastDate
select new FullTicket
{
...
}

Categories

Resources