Linq - Include and filter - nested collections - asp.net-mvc

I'm working on a mvc application where people can search for train routes. I made my database code-first with the entity framework but i cant seem to figure out how to ask some more complex queries.
I have a collection of routes which i use in my viewmodel. So the first step is asking which routes have a certain start and end station. Then i want to include the schedule where a certain day is set on true (and where the start and end date of that schedule match). This is linked to a collection of trips (i am using this table cause routes run multiple times on a day). From here i can find all the matching stations with the arrive and depart hours from the table routeHasStations.
So i was thinking something like:
public IEnumerable<Route> Search(DateTime date, int? departure, int? arrival)
{
var day = date.DayOfWeek.ToString();
return db.Routes.Where(r => r.DepartureStationID == departure && r.ArrivalStationID == arrival)
.Include(s => s.Train)
//using linq.dynamic here
.Include(r => r.Schedule.where(day + "==" + true)
.Include(sch => sch.trip.where(date > sch.DepartureTime)
.Include(route => route.RouteHaseStations)
.Include(st => st.Stations)
}
But this is offcourse not working. Here are my models:
public class Route
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int RouteID { get; set; }
public String RouteName { get; set; }
[ForeignKey("Train")]
public int TrainID { get; set; }
[ForeignKey("Station")]
public int DepartureStationID { get; set; }
[ForeignKey("Station")]
public int ArrivalStationID { get; set; }
public virtual ICollection<Schedule> Schedule { get; set; }
public virtual Train Train { get; set; }
public virtual Station DepartureStation { get; set; }
public virtual Station ArrivalStation { get; set; }
}
public class Station
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int StationID { get; set; }
[Display(Name = "Station")]
public String Name { get; set; }
public int Platforms { get; set; }
public float Latitude { get; set; }
public float Longitude { get; set; }
public virtual ICollection<RouteHasStation> RouteHasStation { get; set; }
}
public class Train
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int TrainID { get; set; }
public String Name { get; set; }
public virtual ICollection<Route> Route { get; set; }
}
public class Schedule
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ScheduleID { get; set; }
[ForeignKey("Route")]
public int RouteID { get; set; }
public Boolean Monday { get; set; }
public Boolean Tuesday { get; set; }
public Boolean Wednesday { get; set; }
public Boolean Thursday { get; set; }
public Boolean Friday { get; set; }
public Boolean Saturday { get; set; }
public Boolean Sunday { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual ICollection<Trip> Trip { get; set; }
public virtual Route Route { get; set; }
}
public class Trip
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int TripID { get; set; }
[ForeignKey("Schedule")]
public int ScheduleID { get; set; }
public DateTime DepartureTime { get; set; }
public virtual ICollection<RouteHasStation> RouteHasStation { get; set; }
}
public class RouteHasStation
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int RouteHasStationID { get; set; }
[ForeignKey("Station")]
public int StationID { get; set; }
public virtual Station Station { get; set; }
[ForeignKey("Trip")]
public int TripID { get; set; }
public virtual Trip Trip { get; set; }
[DataType(DataType.Time)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:HH:mm}")]
public DateTime? Arrival { get; set; }
[DataType(DataType.Time)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:HH:mm}")]
public DateTime? Departure { get; set; }
public int Platform { get; set; }
public float Distance { get; set; }
}

Apart from your typing errors, your program has several problems.
The statement
.Include(r => r.Schedule.where(day + "==" + true)
does not have a correct predicate. Even if you'd be able to change it into a predicate LINQ to SQL doesn't know how to handle this as a query, because you need to call a different procedure from your Schedule class depending on variable day.
Because of the combination with the include, I'm not sure what you want. From your description I'd say you want all routes (with a certain departure station and arrival station) that are scheduled to run on the given search date.
I'm not sure if you are using an existing database, or are still developing it. In the latter case, consider changing the Schedule class such that Schedule is a flagged enum like this
[Flags]
public enum MyDayOfWeek
{
Sunday = 1 << System.DayOfWeek.Sunday, // 0x01
Monday = 1 << System.DayOfWeek.Monday, // 0x02
Tuesday = 1 << System.DayOfWeek.Tuesday, // 0x04
Wednesday 1 << System.DayOfWeek.Wednesday, // 0x08
Thursday 1<< System.DayOfWeek.Thursday, // 0x10
Friday = 1 << System.DayOfWeek.Friday, // 0x20
Saturday = 1 << System.DayOfWeek.Saturday, // 0x40
}
public class Schedule
{
public int ScheduleID { get; set; }
public int RouteID { get; set; }
public MyDayOfWeek RunDays { get; set; }
public virtual ICollection<Trip> Trips {get; set;}
...
RunDays is an or-flagged variable of all days the route is scheduled to be run, for instance, if scheduled to run in weekends only:
RunDays = DayOfWeek.Saturday | DayOfWeek.Sunday;
The query to get all routes that are scheduled to run on a given DateTime will be like:
System.DayOfWeek dayOfWeek = day.DayOfWeek;
MyDayOfWeek myDayOfWeek = (MyDayOfWeek)( 1 << (int)dayOfWeek);
var routesRunningOnDate = db
.Where(route => route.DepartureStation etc
&& (route.RunDays & myDayOfWeek) == myDayOfWeek)
Linq to SQL will know how to handle it.
If you can't change the model of your database and you are stuck with this definition of Schedule, you can't perform it AsQueryable, however it will be possible to perform it AsEnumerable, in your local memory.
If you want to check if your route is scheduled on a certain day of week using the functions Schedule.Monday, Schedule.Tuesday, etc, I'd advise you define and extension for your Schedule class:
public static class ScheduleExtensions
{
// returns whether schedule scheduled on date:
public static bool IsScheduled(this Schedule schedule, DateTime date)
{
switch (date.DayOfWeek)
{
case DayOfWeek.Sunday:
return schedule.Sunday;
case DayOfWeek.Monday:
return chedule.Monday;
...
}
}
}
Usage:
db.Routes.AsEnumerable().Where (route => route.Schedule.IsScheduled(date));
Furthermore, it seems you haven't bracketed all your includes correctly. all your paramertes r, s, sch, route, st are all expected to be routes, while in fact you mean your sch to be Schedules, etc.
Be aware, that as long as you don't really enumerate over your sequence (and you don't dispose your DbContext), you don't need the Include statements. Include statements are needed when you don't specifically ask for a value, but you want them in local memory.
If you have an IQueryable, that does not use a certain field of your table and late you use this field, before you enumerate it, the field is automatically added to the query and thus to your SQL query:
var query1 = routes.Where(r.DepartureStationID == departure && r.ArrivalStationID == arrival);
var query2 = query1.Where(route => route.Schedule.Trips...)
Without the extra include LINQ to SQL knows that a join with the Schedule table is needed.
I guess you want the following routes:
(1) All routes that start on DepartureStation and arrive on ArrivalStation
(2) AND that are scheduled to run on day
(3) AND that are scheduled after (not on?) Schedule.DepartureTime
(4) Make sure you have all stations from route.RouteHasStations
Not really sure if Schedule.DepartureTime means the first day that this schedule is valid (so wouldn't ActivationDate be a better name?) or whether DepartureTime is the time that the train departs on the Scheduled dates.
In the latter case the statement
.Include(sch => sch.trip.where(date > sch.DepartureTime)
doesn't do what you want: it returns all schedules that were schedule on dates after your date, even if the departuretime is later than the time in your date
db.Routes.Where(route => route.DepartureStaionId == departure
&& route.ArrivalStationId == arrival
&& route.Schedule.IsScheduled(date)
&& date > route.Trips.DepartureTime)
// you don't need to include Schedule and Trips explicitly
// unless you need their fields outside this query
.Include(route => route.Train)
.Include(route => route.HasTrips)
// only include these if you need their fields outside this query
// if the only thing from train you want is Train.Name,
// use route.Train.Name in a select after the Where

Related

How to make a ViewModel for an anonymous type from a linq query

I have joined and grouped 2 databases using linq in my controller, and now I need to parse it to the view. I know, I gonna need a ViewModel to do it, but I have no clue what to put in it, to make it work.
My Linq query in the controller looks like this:
var energyUsageViewModel =
from e in applicationDbContext
join u in _context.UsagesPerMonth on e.Id equals u.EnergyUsageId into u2
group u2 by new { Year = e.Year }
into u3
select u3.ToList();
return View(energyUsageViewModel);
In short what the linq query does is taking the "year" from table "e" joining it with table "u" which contains the energy usage per month, which I am gonna use to make a table in my view, which displays the year and usage per month in one row.
And currently my ViewModel looks like this (obviously not working):
public class EnergyUsageViewModel
{
public IEnumerable<UsagePerMonth> UsagePerMonthVm { get; set; }
}
The view takes a model of:
#model IEnumerable<Jullerup.Models.EnergyUsageViewModel>
I have tried to modify the ViewModel to take u3, but haven't succeeded.
I get the following invalid operation exception:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Linq.Enumerable+SelectEnumerableIterator2[System.Linq.IGrouping2[<>f__AnonymousType101[System.String],System.Collections.Generic.IEnumerable1[Jullerup.Models.UsagePerMonth]],System.Collections.Generic.List1[System.Collections.Generic.IEnumerable1[Jullerup.Models.UsagePerMonth]]]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IEnumerable`1[Jullerup.Models.EnergyUsageViewModel]'.
How do I edit my ViewModel to handle u3?
Update:
I'm working with 3 classes.
Customer class:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
//Navigation Prop
public ICollection<EnergyUsage>? energyUsage { get; set; }
}
EnergyUsage class:
public class EnergyUsage
{
public int Id { get; set; }
public int CustomerID { get; set; }
public DateTime Date { get; set; }
public int YearlyHeatUsage { get; private set; }
public List<UsagePerMonth> UsagePerYear { get; set; }
//Navigation Prop
public Customer? Customer { get; set; }
}
UsagePerMonth class:
public class UsagePerMonth
{
public int Id { get; set; }
public MonthEnum Month { get; set; }
public int Usage { get; set; }
public int HeatUsage { get; private set; }
public string EnergyType { get; set; }
private EnergyMeasurement energyType { get; set; }
public int EnergyUsageId { get; set; }
}
In the database Customer.Id (PK) has a one to many relationship to EnergyUsage.CustomerId (FK) and EnergyUsage.Id (PK) has a one to many relationship to UsagePerMonth.EnergyUsageId (FK).
What I am trying to do, is to get an object/list something I can use in the view to display all instances of UsagePerMonth grouped by Customer.Year for a certain Customer.Id.

Group and Get top(n) records by count times out in linq

I'm trying to get the top 10 most visited organisations using Linq and GroupBy. When an organisation is accessed their information is logged using the following class. There are up to 1000 records added per day.
public class LogOrgAccess
{
public int OrganisationID { get; set; }
public DateTime AccessDate { get; set; }
[ForeignKey("OrganisationID")]
public virtual Organisation Organisation { get; set; }
}
My Organisation class look like:
public partial class Organisation
{
public int OrganisationID { get; set; }
public string OrgName { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
}
I want to pull back the top (n) visited organisations within a timeframe so I've created a class to map the results to:
public class OrgAccessDTO
{
public int OrganisationID { get; set; }
public string OrgName { get; set; }
public int Counter { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
}
I've written the following query to try and return the required data:
public IQueryable<OrgAccessDTO> GetOrgAccessByDateRange(DateTime fromDate, DateTime toDate)
{
return _UoW.LogOrgAccess.All.Where(s => s.AccessDate >= fromDate && s.AccessDate <= toDate)
.GroupBy(s => s.OrganisationID)
.Select(g => new OrgAccessDTO
{
OrganisationID = g.FirstOrDefault().OrganisationID,
OrgName = g.FirstOrDefault().Organisation.OrgName,
Latitude = g.FirstOrDefault().Organisation.Latitude,
Longitude = g.FirstOrDefault().Organisation.Longitude,
Counter = g.Count(),
}
).OrderByDescending(g=>g.Counter).Take(10);
}
Im returning the Iqueryable to my controller like so:
IList<OrgAccessDTO> logs = _logService.GetOrgAccessByDateRange(start, end).ToList();
The query keeps timing out with the exception "Win32Exception: The wait operation timed out".
I've extended the timeout property on my connection but this does not seem to make a difference.
I thought there may be an issue with the query itself, however after adding an index to the OrgAccess table in the database, the query is now pulling out the required data. Even though its still a little slow.
There also seems to be an issue with RAM on the development machine. (Its running at full capacity). When uploaded to the web server the query executes much faster.

How to include a child object's child object in Entity Framework 5 Not working

I saw this How to include a child object's child object in Entity Framework 5
However, I'm using MVC 5 and that doesn't seem to be working.
I have a typical Tournament, Games, Teams structure, and am trying to include Teams when I query the tournament.
I am trying to run
db.Tournaments.Include(t => t.Games.Select(g => g.Team1)).ToList();
However Team1 is coming back null. Here are my classes:
public class Tournament
{
public int TournamentId { get; set; }
public string Name { get; set; }
public virtual List<Game> Games { get; set; }
public virtual List<Standing> Standings { get; set; }
}
public class Game
{
public int GameId { get; set; }
public Location Location { get; set; }
public int LocationId { get; set; }
public virtual Team Team1 { get; set; }
public int Team1Id { get; set; }
public virtual Team Team2 { get; set; }
public int Team2Id { get; set; }
public int Team1Score { get; set; }
public int Team2Score { get; set; }
public DateTime Time { get; set; }
public Tournament Tournament { get; set; }
public int TournamentId { get; set; }
}
public class Team
{
public int TeamId { get; set; }
public string Name { get; set; }
public string Coach { get; set; }
}
Does anyone have any suggestions?
You are trying to load a single cell from the Games table in the Db, I don't think you can do this the way you are trying too.
You have your Navigation properties set to Virtual, so you should lazy load automatically when you call the property that you need. There is no need for an include statement.
All you normally need to do is iterate through the list of games in tournaments
var Tournaments = Db.Tournaments.ToList();
foreach (var game in Tournaments.Games)
{
game.Team1.TeamId;
game.Team1.Coach;
game.Team1.Name;
}
db.Tournaments.Include(t => t.Games.Team1).ToList();
This is the correct way of Including single child entities in EF5.

Need help trying to create a one-to-many relationship using EF7, Asp.NET and SQLite

I am new to Entity Framework and Asp.NET, and therefore, struggling with creating database relationships within the Entity Framework.
I have two SQLite tables (Ticket and User) and have setup my entity models as follows:
public class Users
{
[ForeignKey("id")]
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public virtual ICollection<Tickets> Tickets { get; set; }
}
public class Tickets
{
public int id { get; set; }
public string summary { get; set; }
public string description { get; set; }
public string c_location { get; set; }
public string c_store_device { get; set; }
public string category { get; set; }
public DateTime? created_at { get; set; }
public DateTime? closed_at { get; set; }
public int priority { get; set; }
public int? assigned_to { get; set; }
public DateTime? due_at { get; set; }
public DateTime? updated_at { get; set; }
public string status { get; set; }
public virtual Users Users { get; set; }
}
I am trying to use Entity Framework 7 to export an IEnumerable<Tickets> that includes the User assigned to each Ticket.
I have tried to create my model relationship in MyDBContext as a single User can have multiple Tickets, and also has a foreign key associated in my Sqlite database (Tickets.assigned_to = User.id):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Users - > many Tickets
modelBuilder.Entity<Users>()
.HasMany(p => p.Tickets)
.WithOne(e => e.Users)
.HasForeignKey(p => p.assigned_to);
}
My result ends up with Ticket data being exported, but against every ticket I see a null value for User:
[{"id":10002,...,"Users":null}]
When I use .Include() within my Repository to include each User like this:
public IEnumerable<Tickets> GetAll()
{
return _db.Tickets.Include(t => t.Users).ToList();
}
It results in the error
HTTP Error 502.3 - Bad Gateway
The specified CGI application encountered an error and the server terminated the process.
What I'm trying to retrieve is data that looks like:
{"Ticket";[{"id":10002,..."status":"closed"}],"Users":[{"id":"1"..."email":"johndoe#someplace.com"}]}
I know it probably has something to do with my relationship model, but I cannot work out what I am doing wrong.
First you should really derive your Users from IdentityUser. It helps when trying to wire up the relationship, but I will give you the answer based on your current models. Your ForeignKey property should be on the child entity. By naming conventions, which is what EF uses by default, your public Users Users works better if you put a public int UsersId. Then essentially what EF will do is from your public Users Users it will go to the Users table. Then it looks for the ForeignKey which is set to Id, so now we are in the Users Table looking at the id property. Then it looks for the naming convention UsersId and if it sees it, it will set that property to the value that it saw from the Users Table Id column.
Try using this
public class Users
{
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public virtual ICollection<Tickets> Tickets { get; set; }
}
public class Tickets
{
public int id { get; set; }
public string summary { get; set; }
public string description { get; set; }
public string c_location { get; set; }
public string c_store_device { get; set; }
public string category { get; set; }
public DateTime? created_at { get; set; }
public DateTime? closed_at { get; set; }
public int priority { get; set; }
public DateTime? due_at { get; set; }
public DateTime? updated_at { get; set; }
public string status { get; set; }
[ForeignKey("Id")]
public int UsersId { get; set; }
public virtual Users Users { get; set; }
}
and for your Fluent API configuring
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Users - > many Tickets
modelBuilder.Entity<Users>()
.HasMany(p => p.Tickets)
.WithOne();
}
Now all that does is create the relationship. In order to view the specific items you want to view, use a ViewModel. So, pull the two lists you want from where you want. Then use logic to separate the list how you want them to display.
public class UsersViewModel()
{
public UsersViewModel(Users user, List<Tickets> tickets)
{
this.first_name = user.first_name;
this.last_name = user.last_name;
this.email = user.email;
this.Tickets = new List<Tickets>();
foreach(var ticket in tickets)
{
if(ticket.UserId == user.Id)
{
this.Tickets.Add(ticket)
}
}
}
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public List<Tickets> Tickets { get; set;}
}
then in your controller make your list
public IActionResult Index()
{
var usersList = _repository.Users.ToList();
var ticketsList = _repository.Tickets.ToList();
var model = new List<UsersViewModel>();
foreach(var user in usersList)
{
var listItem = new UsersViewModel(user, ticketsList);
model.Add(listItem);
}
return View(model);
}
or use a Linq query
public IActionResult Index()
{
var usersList = _repository.Users.ToList();
var model = new List<UsersViewModel>();
foreach(var user in usersList)
{
var ticketsList = from x in _repository.Tickets where x.UserId.Equals(user.Id) select x;
var listItem = new UsersViewModel(user, ticketsList);
model.Add(listItem);
}
return View(model);
}
then at the top of your view you should have
#model IEnumerable<UsersViewModel>

Viewmodel and LINQ query to represent 4 linked tables in Asp.Net MVC

I'm using a typical invoice system as a test for developing MVC and ViewModel knowledge, before tackling migrating my employers inhouse systems from asp to asp.net MVC.
I know ViewModels are the recommended way to display info in the view - so I was hoping for some help "flattening" the viewmodel for the following:
tables: Invoice, InvoiceItem, Payment, PaymentInvoice
Invoice and InvoiceItem are linked, and Payment (which records an overall payment), and PaymentInvoice (which lists which invoices the Payment covers) are also linked.
I would like a ViewModel to show me:
InvoiceId
CustomerName
Total of Invoice (quantity X UnitPrice plus VAT)
AmountAllocated (from the PaymentInvoice table)
Outstanding (TotalofInvoice - AmountAllocated)
So I think my ViewModel should be:
public class InvoiceViewModel
{
public Int InvoiceId { get; set; }
public string CustomerName { get; set; }
public decimal TotalofInvoice { get; set; }
public decimal AmountAllocated { get; set; }
public decimal Outstanding { get; set; }
}
My domain models are:
public class Invoice
{
public int InvoiceId { get; set; }
public int CustomerId { get; set; }
public string CustomerName { get; set; }
public string Email { get; set; }
public DateTime InvDate { get; set; }
public IList<InvoiceItem> InvoiceItems { get; set; }
}
public class InvoiceItem
{
public int InvoiceItemId { get; set; }
public int InvoiceId { get; set; }
public string Item { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal VAT { get; set; }
public virtual Invoice Invoice { get; set; }
// calculated fields
public decimal Total
{
get { return Quantity * UnitPrice; }
}
public decimal VATAmount
{
get { return TotalPlusVAT - Total; }
}
public decimal TotalPlusVAT
{
get { return Total * (1 + VAT / 100); }
}
}
public class Payment
{
public int PaymentId { get; set; }
public int CustomerId { get; set; }
public DateTime DateReceived { get; set; }
public decimal TotalReceived { get; set; }
public IList<PaymentInvoice> PaymentInvoices { get; set; }
}
public class PaymentInvoice
{
public int PaymentInvoiceId { get; set; }
public int PaymentId { get; set; }
public decimal AmountAllocated { get; set; }
public int InvoiceId { get; set; }
public virtual Payment Payment { get; set; }
}
My problem is in how to link the Payment and PaymentInvoice table to the Invoice and InvoiceItem table, so I can use a LINQ query in my controller to populate the viewmodel with the "flattened data".
I'm lost with the LINQ query too - in LinqPad I've got:
from c in Invoices
join i in InvoiceItems on c.InvoiceId equals i.InvoiceId
join pi in PaymentInvoices on c.InvoiceId equals pi.InvoiceId
select new {...into ViewModel????...}
...but not sure where to go after that.
EDIT - The closest I've got is the Sql to do this is:
SELECT Invoices.InvoiceId,
Invoices.CustomerName,
(SUM(InvoiceItems.Quantity * InvoiceItems.UnitPrice)) AS TotalOfInvoice,
(SELECT SUM(AmountAllocated) AS Expr1
FROM PaymentInvoices
WHERE (InvoiceId = Invoices.InvoiceId)) AS AmountAllocated,
SUM(InvoiceItems.Quantity * InvoiceItems.UnitPrice)
- (SELECT SUM(AmountAllocated) AS Expr1
FROM PaymentInvoices
WHERE (InvoiceId = Invoices.InvoiceId)) AS Outstanding
FROM Invoices LEFT OUTER JOIN
InvoiceItems ON Invoices.InvoiceId = InvoiceItems.InvoiceId
GROUP BY Invoices.InvoiceId, Invoices.CustomerName
Thank you,
Mark
I think your Linq query is almost perfect you just need to select new ViewModels:
from c in Invoices
join i in InvoiceItems on c.InvoiceId equals i.InvoiceId
join pi in PaymentInvoices on c.InvoiceId equals pi.InvoiceId
select new InvoiceViewModel {
InvoiceId = c.InvoiceId,
CustomerName = c.CustomerName,
TotalofInvoice = c.InvoiceItems.Sum(invoiceitem => invoiceitem.Total(),
AmountAllocated = ...
Outstanding = ...
};

Resources