LINQ Group By Type for each user - asp.net

I have a list of objects with following structure
public class Jobs
{
public string EmployeeName { get; set; }
public string JobNumber { get; set; }
public string JobPriority { get; set; }
public string JobType { get; set; }
public string Description { get; set; }
public string Status { get; set; }
public string CreatedByUser { get; set; }
public DateTime DueDate { get; set; }
public DateTime UpdateDate { get; set; }
}
What I would like to do is to return a List of objects which contains
EmployeeName,
Total Count of Jobs assigned to him,
Job Type & Count of each job type assigned to an emploee.
So in essence, I would need list of records to appear like the attached image below. I will use the result from the LINQ query and bind it to a asp.net GridView
Andrew
Total Jobs: 12
Build Jobs: 3
Config Jobs: 4
Delivery Jobs: 3
How can I achieve a result using the linq from the initial list that i have. I guess for pros, this would be a very simple and straight-forwad LINQ query.
EDIT: Added for final structure that i want:
I would like to get the result from LINQ into following structure. i.e. List of UserJob
public class UserJob
{
public string EmployeeName { get; set; }
public int TotalJobs { get; set; }
public List<UserJobGroup> Tickets { get; set; }
}
public class UserJobGroup
{
public string JobType { get; set; }
public int JobCount { get; set; }
}

dasblinkenlight already explained it. With your specific classes, it's even easier - all you need is to follow the steps he described and translate them to LINQ. You should really be able to do that yourself, especially with query syntax:
var result =
(from job in jobList
group job by job.EmployeeName into employeeNameJobs
let jobTypeJobs =
(from job in employeeNameJobs
group job by job.JobType into jobTypeJobs
select new UserJobGroup
{
JobType = jobTypeJobs.Key,
JobCount = jobTypeJobs.Count()
})
.ToList()
select new UserJob
{
EmployeeName = employeeNameJobs.Key,
TotalJobs = jobTypeJobs.Sum(item => item.JobCount),
Tickets = jobTypeJobs
})
.ToList();

Let's walk through the structure of the query that you are trying to build:
At the top level you need groups by employee
At the employee level you need two related, but separate, things:
You need separate counts by job, and
You also need the total of these counts
Now let's build the query. Start with GroupBy for the top level:
var empGroups = jobList.GroupBy(j => j.EmployeeName);
Convert each group to a dictionary with EmployeeName serving as the key, and job group counts stored as values:
var jobCountsByEmployee = empGroups
.ToDictionary(
g => g.Key
, g => g.GroupBy(j => j.JobType)
.ToDictionary(jg => jg.Key, jg => jg.Count())
);
Finally, add totals to construct the result that you want:
var res = jobCountsByEmployee
.ToDictionary(
p => p.Key
, p => new {
Total = p.Value.Sum(jc => jc.Value)
, CountByJob = p.Value
}
);

Related

Entity Framework not tracking List

I'm using EF6 with ASP.Net. I'm trying to add items to the Jobs list in the following model:
EDIT:
My goal is to save the changes I make to the Timecards.Jobs list through a PUT method in such a way that I can retrieve them through a GET method.
public class Timecard
{
[Key]
public long TimecardID { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
[Required]
public long EmployeesID { get; set; }
[Required]
public decimal Hours { get; set; }
[Required]
public ICollection<int> Jobs { get; set; } = new List<int>();
public List<DateTime> Days { get; set; } = new List<DateTime>();
}
And I believe i'm doing so, i'm checking the states change in my PUT method:
// PUT: api/TimecardsAPI/5
[ResponseType(typeof(void))]
public IHttpActionResult PutTimecard(int id, Job job)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
Timecard card = db.Timecards.Where(x => x.TimecardID == id).First();
var state = db.Entry(card).State;
db.Timecards.Attach(card);
state = db.Entry(card).State;
card.Jobs.Add((int)job.JobID);
db.Entry(card).State = EntityState.Modified;
state = db.Entry(card).State;
var result = db.SaveChanges();
state = db.Entry(card).State;
var change = db.Timecards.Where(x => x.TimecardID == id).First().Jobs;
}
catch (DbUpdateConcurrencyException)
{
if (!TimecardExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
Before returning from the put method, i have a var change to check the results of the Jobs list once i'm done working on it. Before I leave the put method, the changes to the Jobs list are accurate. However, when I do a get, I get all the correct data EXCEPT the list. It comes back as a 0 length list. Here's my get method, which also has the jobs list in a variable. This is where the list comes back as size 0:
// GET: api/TimecardsAPI
public IQueryable<Timecard> GetTimecards()
{
var change = db.Timecards.Where(x => x.TimecardID == 6).First().Jobs;
//In this example, six is the id of the timecard in question. Only hardcoded here
//for debugging.
return db.Timecards;
}
and my dbcontext:
public class ClockedWebContext : DbContext
{
public ClockedWebContext() : base("name=ClockedWebContext")
{
}
public DbSet<Job> Jobs { get; set; }
public System.Data.Entity.DbSet<ClockedWeb.Models.PayPeriod> PayPeriods { get; set; }
public System.Data.Entity.DbSet<ClockedWeb.Models.Employee> Employees { get; set; }
public System.Data.Entity.DbSet<ClockedWeb.Models.Timecard> Timecards { get; set; }
}
There are many similar questions on SO but I have not found information yet that has helped me solve my issue. I have no idea what I'm doing wrong, but I've lost days on this and I could really use some help. thank you.
Generally storing multiples values in column is an indication of poor database design. Relational databases are designed specifically to store one value per row/column combination. In order to store more than one value, you must serialize your list into a single value for storage, then deserialize it upon retrieval or you can use many-to-one relationship then you should use an extra table with a foreign key constraint. There is no other way to do so in RDMS.
If you use serialize approach, then your model look like--
public class Timecard
{
[Key]
public long TimecardID { get; set; }
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
[Required]
public long EmployeesID { get; set; }
[Required]
public decimal Hours { get; set; }
[NotMapped]
public List<int> JobList { get; set; } = new List<int>();
[Required]
public string Jobs
{
get => string.Join(",", JobList);
set
{
if (string.IsNullOrEmpty(value)) JobList = new List<int>();
else
{
JobList = !string.IsNullOrWhiteSpace(value) && value.Contains(",")
? value.Split(',').Select(s => Convert.ToInt32(s.Trim())).ToList()
: !string.IsNullOrWhiteSpace(value) && !value.Contains(",")
? new List<int>()
: new List<int>();
}
}
}
//have to change also
public List<DateTime> Days { get; set; } = new List<DateTime>();//Follow previous technique
}
Then you can do your operation as you doing. just it's insert data as a coma separated string.
I am not getting you correctly but if you not getting the update after you changed your entity then can you please add below line
db.savechanges();

Display the sum of the orders of the month in chart

I don`t know how to display, at the beginning the sum of the orders of the month in the table in the asp.net MVC.
My data in table Order look like:
DateCreated | TotalPrice
2017-02-06 | 400
2017-02-06 | 300
2017-03-06 | 100
2017-03-06 | 50
And I want to get mountly sum of TotalPrice, like this:
DateCreated | TotalPrice
2017-02 | 700
2017-03 | 150
My model class Order:
public int orderId { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser User { get; set; }
[StringLength(100)]
public string firstName { get; set; }
[StringLength(150)]
public string lastName { get; set; }
[StringLength(150)]
public string address { get; set; }
[StringLength(20)]
public string phoneNumber { get; set; }
public string email { get; set; }
public string comment { get; set; }
public DateTime dateCreated { get; set; }
public OrderState orderState { get; set; }
public decimal totalPrice { get; set; }
public List<OrderIteam> OrderIteams { get; set; }
I try to write something like this to display the sum of the orders of the month in the table :
public ActionResult ListOrder()
{
var result =
from s in db.Orders
group s by new { date = new DateTime(s.dateCreated.Year, s.dateCreated.Month, 1) } into g
select new
{
dateCreated = g.Key.date,
sumTotalPrice = g.Sum(x => x.totalPrice)
};
return View(result);
}
My view looks like this
I am getting the error message:
The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType52[System.DateTime,System.Decimal]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Sklep_Internetowy.Models.Order]'.
At the end I would like to show that the sum of the orders of a given month as the chart below:
Chart
Currently the LINQ query below returns DbQuery instance with anonymous type instead of IEnumerable collection:
var result = from s in db.Orders
group s by new { date = new DateTime(s.dateCreated.Year, s.dateCreated.Month, 1) } into g
select new
{
dateCreated = g.Key.date,
sumTotalPrice = g.Sum(x => x.totalPrice)
}; // returns System.Data.Entity.Infrastructure.DbQuery<AnonymousTypeN>
The reason behind thrown InvalidOperationException here is simply because a different type of data has been passed into the view which requires IEnumerable<Sklep_Internetowy.Models.Order>.
Try change return type to Order instead using anonymous type one:
var result = from s in db.Orders
group s by new { date = new DateTime(s.dateCreated.Year, s.dateCreated.Month, 1) } into g
select new Order // return Sklep_Internetowy.Models.Order here
{
dateCreated = g.Key.date,
sumTotalPrice = g.Sum(x => x.totalPrice)
};
At this point, the return type of LINQ query above becomes IQueryable<Sklep_Internetowy.Models.Order> which implements IEnumerable, hence it doesn't require conversion (but you may still need to use collection aggregate methods to execute it, e.g. ToList()).
Related issues:
The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery', but this dictionary requires a model item of type B
The model item passed into the dictionary is 'System.Data.Entity.Infrastructure.DbQuery`, but requires 'System.Collections.Generic.IEnumerable

RavenDB Query on Datetime with value in collection offset

I am trying to query RavenDB on a Datetime which is being offset by a entry in a collection. As shown below, I have an AppointmentReminder object which contains many AppointmentReminderJobs. I'd like to query for AppointmentReminders where the AppointmentReminderJob is due to run.
My models are as follows:
public class AppointmentReminder
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public DateTime AppointmentDateTime { get; set; }
public ReminderStatus ReminderStatus { get; set; }
public List<AppointmentReminderJob> AppointmentReminderJobs { get; set; }
}
public class AppointmentReminderJob
{
public JobStatus JobStatus { get; set; }
public int DaysPrior { get; set; }
}
My controller and attempt to retrieve a list of AppointmentReminders which have current jobs to run (I know this Where clause isn't complete, but I've tried to simplify it with no luck):
public ActionResult GetJobsQueuedListCurrent()
{
var jobsqueuedlist = RavenSession.Query<AppointmentReminder>()
.Where(appointmentreminder => appointmentreminder.AppointmentReminderJobs.Any(x => appointmentreminder.AppointmentDateTime < DateTime.Now.AddDays(x.DaysPrior)))
.OrderBy(appointmentreminder => appointmentreminder.AppointmentDateTime)
.Take(20)
.ToList();
return View("List", jobsqueuedlist);
}
Calling the above yields a response of:
variable 'x' of type 'ProjectName.Models.AppointmentReminderJob' referenced from scope '', but it is not defined
I am trying to set up an index like so:
public class JobsQueuedListCurrent : AbstractIndexCreationTask<AppointmentReminder, JobsQueuedListCurrent.IndexResult>
{
public class IndexResult
{
public int Id { get; set; }
public DateTime JobDateTime { get; set; }
}
public JobsQueuedListCurrent()
{
Map = appointmentreminders => from appointmentreminder in appointmentreminders
from job in appointmentreminder.AppointmentReminderJobs
select new
{
Id = appointmentreminder.Id,
JobDateTime = appointmentreminder.AppointmentDateTime.AddDays(job.DaysPrior)
};
Store(x => x.Id, FieldStorage.Yes);
Store(x => x.JobDateTime, FieldStorage.Yes);
}
}
Now, I'm querying and getting expected results using:
var jobsqueuedlist = RavenSession.Query<JobsQueuedListCurrent.IndexResult, JobsQueuedListCurrent>()
.Where(x=>x.JobDateTime >= DateTime.Now)
.As<AppointmentReminder>()
.Take(20)
.ToList();
return View("List", jobsqueuedlist);
My last question regarding this would be, my map/index can definitely result in multiple entries of the same document id (appointmentreminder), but my resulting list contains only 1 instance of the document. I'm happy with the way that works, I'm just not sure if I should be performing a reduce or doing something else in my code or just let Raven handle it like it seems like it is doing?
You cannot create such a query. This would require RavenDB to perform computation during query, and that is not allowed.
RavenDB only allows queries on the data in the index.
What you can do it setup the computation in the index, and then query on that.

LinqPad Query to Visual Studio - how to use a nested query to populate a viewmodel

This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?

Selecting records from parent table depending on child table filter

This should be simple, but I'm getting confused.
I have a parent/child tables - and all I want to do, is select from the parent table, depending on filtering of the child table.
So the parent table, Rooms, is linked one to many to the clients table - I want to select rooms, where there are no linked records in the Clients table, where the clients.Departure date is before a specific date:
public class Room
{
public int RoomId { get; set; }
public string RoomName { get; set; }
public List<Client> Clients { get; set; }
}
public class Client
{
public int ClientId { get; set; }
public int RoomId { get; set; }
public string Name { get; set; }
public DateTime Arrival { get; set; }
public DateTime Departure { get; set; }
public Room Room { get; set; }
}
In my controller I have been trying:
public ActionResult Avail()
{
DateTime dteFrom = DateTime.Parse("2012-07-01"); //hard coded for testing
Room room = db.Rooms.Where(r => r.Clients.Any(c => c.Departure <= dteFrom));
But I get the error message:
Cannot implicitly convert type 'System.Linq.IQueryable<ttp.Models.Room>' to 'ttp.Models.Room'. An explicit conversion exists (are you missing a cast?)
Can anyone suggest do I need to change my model classes, or my Where statement?
Room room represents a single room whereas db.Rooms.Where(r => r.Clients.Any(c => c.Departure <= dteFrom)) returns a list of rooms.
If you expect your query will only return one result, you could do the following:
Room room = db.Rooms.SingleOrDefault(r => r.Clients.Any(c => c.Departure <= dteFrom));
Or if you would like to return all the rooms that match the query, you could do the following:
IQueryable<Room> rooms = db.Rooms.Where(r => r.Clients.Any(c => c.Departure <= dteFrom));

Resources