Given the following model, I want to be able to retrieve the number of points gained for a single player and the average number of point gained for all players given a time period. This should be done in a single db query (I also want other stats e.g. avg points per team which will come later but the concept should be the same).
Having a bad day and getting nowhere. Can someone help me out?
public class Player
{
public int Id { get; set; }
public ICollection<PlayerGame> PlayerGames { get; set; }
...
}
public class PlayerGame
{
public int Id { get; set; }
public int Points { get; set; }
public int PlayerId { get; set; }
public Player Player { get; set; }
public int GameId { get; set; }
public Game Game { get; set; }
...
}
public class Game
{
public int Id { get; set; }
...
}
Edit:
OK. Have taken the game entity out of the equation for now and changed your code to fit in with my repo. This is what I have now:
var query = from p in _playerRepository.Query()
from pg in p.PlayerGames
group new { p, pg } by 1 into ppg
select new
{
SinglePlayerPointsGained = (from x in ppg
where x.p.Id == playerId && x.pg.Date > startDateTime
select x.pg.Points).Sum(),
AveragePoints = (from x in ppg
where x.pg.Date > startDateTime
select x.pg.Points).Average(),
};
So Now I just need the AveragePoints calculation to take players that have not played in the period into account as mentioned in the comment.
I assumed the Game class had a DateTime property. The basic idea is using the group by 1 trick
DateTime startDateTime, endDateTime;
int playerId;
var query = from p in context.Players
join pg in context.Players on p.Id equals pg.PlayerId
join g in context.Games on pg.GameId equals g.Id
group new { p, pg, g } by 1 into ppgg
select new {
SinglePlayerPointsGained = (from x in ppgg
where x.p.PlayerId == playerId
where x.g.DateTime >= startDateTime && x.g.DateTime <= endDateTime
select x.pg.Points ).Sum(),
AveragePoints = (from x in ppgg
group x.pg.Points by x.p.PlayerId into g
select g.Key).Average()
};
Related
I have three tables, Organization, Department, and OrganizationDepartments. here is the relationship between them.
Now I would like to join these three tables and create another object for a DTO class. This DTO object has some properties and a list of other DTOs. Here is the DTO Class.
Organization DTO:
public class OrganizationDto
{
public string Id { get; set; }
public string OrganizationName { get; set; }
public string Logo { get; set; }
public bool? IsActive { get; set; }
public IList<OrganizationDepartmentDto> OrganizationDepartments { get; set; }
}
OrganizationDepartment DTO:
public class OrganizationDepartmentDto
{
public string OrganizationId { get; set; }
public string OrganizationName { get; set; }
public string DepartmentId { get; set; }
public string DepartmentName { get; set; }
}
Now I would like to write a LINQ query to get a Organization object along with all the departments related to that organization. The query is imcomplete because I don't know how can I get all the department information as list in a single query. The code is below:
var organizationInfo = (from org in _dbContext.Organizations
join orgDept in _dbContext.OrganizationDepartments on org.Id equals orgDept.OrganizationId
join dept in _dbContext.Departments on orgDept.DepartmentId equals dept.Id
where org.Id.ToUpper() == id.ToUpper()
orderby org.CreatedOn ascending
select new OrganizationDto
{
Id = org.Id,
OrganizationName = org.OrganizationName,
Logo = org.Logo,
IsActive = org.IsActive,
OrganizationDepartments = //TODO:..
}
);
Can anyone help me to get the department lists of that organization's object (see the TODO:)?
If your entities are mapped correctly, and the relationships are correctly configured.
you can use .Include("OrganizationDepartment") and .ThenInclude("Department")to ensure relations are included into the generated Query.
If you insist on using Query Syntax. e.g from org in context.Organization
you can write out the query like this.
var q = (from org in _dbContext.Organizations
where org.Id.ToUpper() == id.ToUpper()
orderby org.CreatedOn ascending
select new OrganizationDto
{
Id = org.Id,
OrganizationName = org.OrganizationName,
Logo = org.Logo,
IsActive = org.IsActive,
OrganizationDepartments = org.OrganizationDepartments.ToList()
}
Depending on your usecase. Sometimes you are not interested in actually showing the "many to many" table outside of the scope of your database.
so it might make more sense to actually flatten the Dto.
that query would look like
var q = (from org in _dbContext.Organizations
where org.Id.ToUpper() == id.ToUpper()
orderby org.CreatedOn ascending
select new OrganizationDto
{
Id = org.Id,
OrganizationName = org.OrganizationName,
Logo = org.Logo,
IsActive = org.IsActive,
Departments= org.OrganizationDepartments.Select(t => t.Departments).ToList()
}
I have a class called DashboardGroupData
public class DashboardGroupData
{
public int ConsignmentID {get;set;}
public DateTime ActualManifestDate {get;set;}
public DateTime PlannedLatestDeliveryDateTime {get;set;}
public DateTime PlannedEarliestDeliveryDateTime {get;set;}
public DateTime PlannedEarliestCollectionDateTime { get; set; }
public string Status {get;set;}
public int ServiceLevelID {get;set;}
public double PalletWeight {get;set;}
public int MaxMove {get;set;}
public int LastMove { get; set; }
public int PalletStatusID { get; set; }
public int Sequence { get; set; }
public int RequestDepotID { get; set; }
public int CollectionDepotID { get; set; }
public bool Collection { get; set; }
}
I have the following entity query:
groupConsignmentList = (from d in connectDB.vwDepotDashboards
where depotAndSubDepots.Contains(d.DeliveryDepotID)
&& !incomplete.Contains(d.Status)
&& (!depotAndSubDepots.Contains(d.CollectionDepotID) || d.CustDirectToHub)
&& d.Sequence < (int)PalletStatusSequence.ICC
&& d.ActualManifestDate > validWindow
&& d.TransitHubID == hubID
select new Models.DashboardGroupData()
{
ConsignmentID = d.ConsignmentID,
ActualManifestDate = d.ActualManifestDate,
PlannedLatestDeliveryDateTime = d.PlannedLatestDeliveryDateTime,
PlannedEarliestDeliveryDateTime = d.PlannedEarliestDeliveryDateTime,
Status = d.Status,
PlannedEarliestCollectionDateTime = d.PlannedEarliestCollectionDateTime,
ServiceLevelID = d.ServiceLevelID,
PalletWeight = d.PalletWeight,
PalletStatusID = d.PalletStatusID,
Sequence = d.Sequence,
Collection = d.Collection,
RequestDepotID = d.RequestDepotID,
MaxMove = connectDB.PalletMovements.Where(c => c.CreatedDateTime < manifestDayEnd && c.PalletID == d.PalletID && !(c.PalletMovementTypeID > 12 && c.PalletMovementTypeID < 19)).Max(c => (int?)c.PalletMovementTypeID) ?? 0
});
When I try to do a further query (below) I get the "The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported" error:
var test = groupConsignmentList.Where(c => c.Sequence > 650 && c.Sequence < 1000).Select(c => c.ConsignmentID).ToList();
The same query written directly as a SQL query works perfectly, as do the above queries in LINQPad 5, using the same connection and entity model as the application.
I have tried renaming the field Sequence in the db to something that's not a SQL reserved word - but it didn't make any difference.
One clue: if I take the first query, ("groupConsignmentList =") and convert it to a list, in every member of the list, the value of Sequence is zero - whereas in LINQPad 5 it produces correct values.
The .Net framework is 4.6
The problem was that I hadn't realised that there is more than one assignment of a query to groupConsignmentList in the code - and the one I had added Sequence to wasn't the one used in this case.
I use Breeze with Durandal (still 1.2) and I am facing a problem which I haven't found an easy solution for. I have 2 entities: Invoice & InvoiceLine like described below:
public class Invoice
{
[Key]
public int Id { get; set; }
public string InvoiceNumber { get; set; }
public string Comment { get; set; }
public double? TotalExclVAT { get; set; }
public double? TotalInclVAT { get; set; }
public double? TotalVAT { get; set; }
public bool? WithoutVAT { get; set; }
public virtual List<InvoiceLine> Lines { get; set; }
}
public class InvoiceLine
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
public double VatPercent { get; set; }
public double Amount { get; set; }
public int InvoiceId { get; set; }
public virtual Invoice Invoice { get; set; }
}
I need to compute the totals of the invoice (TotalExclVAT, TotalInclVAT, TotalVAT) in 2 cases:
Whenever someone adds/modifies an invoice line.
Whenever someone changes the flag WithoutVAT on the invoice.
I don't think this is a good idea to perform this compute client side. Performing this server side is better for security reasons mainly.
My first thought was to do the job in the BeforeSaveEntity of Invoice & InvoiceLine.
Here is what i did:
public bool BeforeSaveEntity(EntityState entityState, EntityInfo entityInfo)
{
var invoice = entityInfo.Entity as Invoice;
...
ComputeTotal(entityInfo, invoice);
}
private void ComputeTotal(EntityInfo entityInfo, Invoice invoice)
{
var query = Context.InvoiceLines.Where(x => x.invoiceId == invoice.Id).AsEnumerable();
double totalExclVAT = 0;
double totalVAT = 0;
int percent = 0;
foreach (var line in query.ToList())
{
totalExclVAT = ...
totalVAT = ...
}
entityInfo.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
entityInfo.OriginalValuesMap.Add("TotalInclVAT", invoice.TotalInclVAT);
entityInfo.OriginalValuesMap.Add("TotalVAT", invoice.TotalVAT);
accounting.TotalExclVAT = totalExclVAT;
accounting.TotalInclVAT = totalExclVAT + totalVAT;
accounting.TotalVAT = totalVAT;
}
The same kind of thing is done for the invoice line. As you can see in the ComputeTotal function, I perform a query to get invoice lines from DB then computing totals and saving results in the invoice.
It doesn't work quite well: in case of adding a new line on my invoice, performing a query on my DB doesn't get this added line! Because it is not already stored in DB.
It would have been easier to proceed client side but I don't think this is a good idea... is it?
So I am sure there is another way of doing but I don't find it myself.
Any help is greathly appreciated.
UPDATE
Below is my first shot with this problem.
public Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap)
{
List<EntityInfo> invoices;
List<EntityInfo> invoiceLines;
EntityInfo ei;
if (!saveMap.TryGetValue(typeof(InvoiceLine), out invoiceLines))
{
// if we fall here it means no invoice lines exists in the saveMap
}
if (!saveMap.TryGetValue(typeof(Invoice), out invoices))
{
// if we fall here it means no invoices exists in the saveMap
// >> getting the invoice from DB and add it to the map
using (var dc = new BreezeContext())
{
int invoiceId = ((InvoiceLine)invoiceLines[0].Entity).InvoiceId;
EFContextProvider<BreezeContext> cp = new EFContextProvider<BreezeContext>();
var acc = dc.Invoices.Where(x => x.Id == invoiceId).FirstOrDefault();
ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Modified);
invoices = new List<EntityInfo>();
saveMap.Add(typeof(Invoice), invoices);
invoices.Add(ei);
}
}
// There is only 1 invoice at a time in the saveMap
Invoice invoice = (Invoice)invoices[0].Entity;
ei = invoices[0];
Dictionary<int, InvoiceLine> hashset = new Dictionary<int, InvoiceLine>();
// Retrieving values of invoice lines from database (server side)
using (var dc = new BreezeContext())
{
var linesServerSide = dc.InvoiceLines.Where(x => x.InvoiceId == invoice.Id).AsEnumerable();
foreach (var elm in linesServerSide)
{
hashset.Add(elm.Id, elm);
}
}
// Retrieving values of invoice lines from modified lines (client side)
foreach (var entityInfo in invoiceLines)
{
InvoiceLine entity = (InvoiceLine)entityInfo.Entity;
switch (entityInfo.EntityState)
{
case Breeze.WebApi.EntityState.Added:
hashset.Add(entity.Id, entity);
break;
case Breeze.WebApi.EntityState.Deleted:
hashset.Remove(entity.Id);
break;
case Breeze.WebApi.EntityState.Modified:
hashset.Remove(entity.Id);
hashset.Add(entity.Id, entity);
break;
}
}
// Computing totals based on my hashset
double totalExclVAT = 0;
double totalInclVAT = 0;
double totalVAT = 0;
foreach (var elm in hashset)
{
InvoiceLine line = elm.Value;
totalExclVAT += line.Amount;
totalVAT += line.Amount * (int)line.VatPercent.Value / 100;
}
totalInclVAT = totalExclVAT + totalVAT;
// Adding keys if necessary
if (!ei.OriginalValuesMap.ContainsKey("TotalExclVAT"))
ei.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
if (!ei.OriginalValuesMap.ContainsKey("TotalInclVAT"))
ei.OriginalValuesMap.Add("TotalInclVAT", invoice.TotalInclVAT);
if (!ei.OriginalValuesMap.ContainsKey("TotalVAT"))
ei.OriginalValuesMap.Add("TotalVAT", invoice.TotalVAT);
// Modifying total values
invoice.TotalExclVAT = totalExclVAT;
invoice.TotalInclVAT = totalInclVAT;
invoice.TotalVAT = totalVAT;
return saveMap;
}
The solution above works well whenever the invoice & the invoiceLines are modified client side. I have a problem when no invoice is modified client side (only lines modified). In this case I need to add the related invoice to the saveMap by getting it from DB. That's what I do in my code as you can see. But I need to add keys to the OriginalValuesMap for properties I manually modified here and I cannot in this case because my dictionary object is null. Then when I do...
ei.OriginalValuesMap.Add("TotalExclVAT", invoice.TotalExclVAT);
... on a null object (OriginalValuesMap) it doesn't work.
So my new problem is now the next: how to add an entity to the saveMap which already exists on DB. So I don't want to mark this entity as ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Add); but rather ei = cp.CreateEntityInfo(acc, Breeze.WebApi.EntityState.Modified);. In this case my OriginalValuesMap is null and it seems to be a problem.
Hope you understand what I try to explain here.
Is there any reason not to use a triggered stored procedure for this? This would certainly be the simplest approach...
But... if there is, then the other approach would be to use 'BeforeSaveEntities' instead of 'BeforeSaveEntity' because this will give you access to the entire 'SaveMap'.
Then create a hashset of all of the invoiceLines for each modified invoice and construct this as the combination of your server side query of invoice lines per invoice overlayed with client side invoiceLines associated with this invoice (from the SaveMap). Next just total each hashSet and use this update your 'Totalxxx' properties.
A little terse but hopefully this makes sense.
I am using Entity Framework 5 in my project and I want to update a record. How do I do this?
Here is my base class.
using System;
namespace EF_Sample09.DomainClasses
{
public abstract class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedOn { set; get; }
public string CreatedBy { set; get; }
public DateTime ModifiedOn { set; get; }
public string ModifiedBy { set; get; }
}
}
Taking from the great ADO.NET Entity Framework Overview:
using(AdventureWorksDB aw = new
AdventureWorksDB(Settings.Default.AdventureWorks)) {
// find all people hired at least 5 years ago
Query<SalesPerson> oldSalesPeople = aw.GetQuery<SalesPerson>(
"SELECT VALUE sp " +
"FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
"WHERE sp.HireDate < #date",
new QueryParameter("#date", DateTime.Today.AddYears(-5)));
foreach(SalesPerson p in oldSalesPeople) {
// call the HR system through a webservice to see if this
// sales person has a promotion coming (note that this
// entity type is XML-serializable)
if(HRWebService.ReadyForPromotion(p)) {
p.Bonus += 10; // give a raise of 10% in the bonus
p.Title = "Senior Sales Representative"; // give a promotion
}
}
// push changes back to the database
aw.SaveChanges();
}
You basically just have to:
create your ObjectContext (or DbContext)
fetch some records
modify the objects
call the context's .SaveChanges() method to write those changes back to the database
That's it!
Say I had a class:
public class Post
{
public int PostId { get; set; }
public string Topic { get; set; }
public int UserId { get; set; }
[StringLength(5000)]
public string Body { get; set; }
public DateTime DateCreated { get; set; }
public string Name { get; set; }
public int Votes { get; set; }
}
And for each post, a user could input a topic. for example, if the topics were "Red" "Green" "Blue" and "Yellow", how could I create a list based on how many times those were used?
An example output:
Red | 70
Blue | 60
Green | 40
Yellow| 35
EDIT: How come this doesn't work and gives me an error where I cannot implicitly convert the type?
public List<string> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
EDIT 2:
So I tried your solution out Dustin, and I'm getting an error. This is what I used:
public IEnumerable<IGrouping<string,int>> GetPosts()
{
var posts = from p in db.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
return posts.ToList();
}
This is giving me an error under posts.ToList():
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable>'. An explicit conversion exists (are you missing a cast?)
To create the grouping you create an anonymous type such as:
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
Then to work with the date, lets say iterate over it:
foreach(var p in posts)
{
Response.Write(String.Format("{0} - {1}", p.Topic, p.Count));
}
You must create a new type if you do a projection and return it form method!
public class MyCounts
{
public string Topic { get; set; }
public int Count { get; set; }
}
public List<MyCounts> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new MyCounts
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
The problem is that you need to use an non anonymous type for your return value.
This query creates an IEnumerable of anonymous types.
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
It's the select new statement that creates the anonymous objects.
What you need to do is to create something that is non anonymous - an object that can be shared within and outside this method.
Like this:
public IEnumerable<TopicAndCount> GetPosts()
{
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new TopicAndCount
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
}
Note the select new TopicAndCount statement and the return value of the enclosing method.
That will solve your problem.