I have 3 tables and I'm trying to get a combined result with a sum of one field of them.
I'm working with C#, .NET, Entity Framework 7 and SQL Server.
I need to get the city's Name of each result, but I store the idCity
Brand table:
public byte IdBrand { get; set; }
public string Name { get; set; } = null!;
Bundles table:
public int IdBundle { get; set; }
public short IdCity{ get; set; }
public short IdBrand { get; set; }
public decimal? Volume { get; set; }
Cities:
public short IdCity { get; set; }
public string Name { get; set; } = null!;
I've tried this linq query and got almost the result I want but the city field is failing and I got stuck...
var volume = context.Bundles
.GroupBy(city => city.IdCity)
.Select(cad => new
{
CITY = context.Cities.Local.ToList().ElementAt(cad.Key)!.Name,
BRAND1 = cad.Where(c => c.IdBrand == 1).Sum(c => c.Volume),
BRAND2 = cad.Where(c => c.IdBrand == 19).Sum(c => c.Volume)
}).ToList();
I get this result that I expect but the CITY is not correct, I think because the cad.Key is not the same than Cities Index
I also tried:
context.Cities.ToList()
.Where(i => context.Bundles.Any(a=> i.IdCity == a.IdCity))
.Select(x=> x.Name)
CITY
BRAND1
BRAND2
LONDON
10.2
12
MOSCOU
11.4
1
PARIS
9.1
0.4
I guess that the cad.Key is not what I need to use to get the ElementAt Cities but how can I get the city .Name from another table in the Select? Or what is the best way to perform this query?
Try the following query, it should have better performance:
var query =
from b in context.Bundles
group b by b.IdCity into g
select new
{
IdCity = g.Key,
BRAND1 = g.Sum(c => c.IdBrand == 1 ? c.Volume : 0),
BRAND2 = g.Sum(c => c.IdBrand == 19 ? c.Volume : 0)
} into agg
join city in context.Cities on agg.IdCity equals city.Id
select new
{
CITY = city.Name,
BRAND1 = agg.BRAND1,
BRAND2 = agg.BRAND2
};
I have four tables joined to produce data something like below:
Name Grade CardID Date Class Listen Read Write
Jane Doe A 1001 2020-10-01 Period 1 - Spanish 500 500 500
John Doe B+ 1002 2010-10-02 Pereiod 2 - English 1000 1000 1000
Jane Doe A 1001 2020-10-01 Period 3 - Englsih 500 1000 1000
How do I convert the above data into a nested form like below using LINQ group by? This is a .NET CORE WEB API project and uses DTO objects projections from the LINQ query data.
[
{
"cardId": 1001,
"studentName": "Jane Doe",
"grade": "A",
"evaluationDate": "2020-10-01T00:00:00",
"Period 1 - Spanish": {
"Listen": 1000,
"Read": 500,
"Write": 500
},
"Period 3 - English": {
"Listen": 1000,
"Read": 500,
"Write": 1000
}
},
{
"cardId": 1002,
"studentName": "John Doe",
"grade": "B+",
"evaluationDate": "2010-10-01T00:00:00",
"Period 2 - English": {
"Listen": 500,
"Read": 500,
"Write": 1000
}
}
]
Below I have two viewModel classes which I am using to generate the nested POCO data stracture to be returned from the query. If I don't use GroupBy, I can generate a simple unnested POCO but I don't want to repeat the response data as separate object. This is for a .NET core web api project .
I feel like I am close, but the group by in LINQ is throwing me off...
public class PointCardViewModel
{
public int CardId { get; set; }
public string StudentName { get; set; }
public string Grade { get; set; }
public DateTime EvaluationDate { get; set; }
public IEnumerable<LineItemViewModel> LineItems { get; set; }
}
public class LineItemViewModel
{
public string ClassPeriod { get; set; }
public int Listen { get; set; }
public int Read { get; set; }
public int Write { get; set; }
}
((from s in db.Students
join dc in db.DailyCards on s.StudentId equals dc.StudentId
join dcli in db.DailyCardLineItems on dc.CardId equals dcli.CardId
join dcob in db.DailyCardOtherBehaviors on dc.CardId equals dcob.CardId
select new
{
s.StudentName,
s.StudentGrade,
dc.CardId,
dc.CardDate,
dcli.ClassParticipationPoints,
dcli.AssignmentCompletionPoints,
dcli.BonusHomeworkPoints,
dcli.ClassPeriod
})
.GroupBy(x => x.CardId)
.Select(g => new PointCardViewModel()
{
CardId = g.Key,
StudentName = g.Select(c => c.StudentName).First(),
Grade = g.Select(c => c.StudentGrade).First(),
EvaluationDate = x.CardDate,
LineItems = g.Select(y => new LineItemViewModel()
{
//Class
//Read
//Listen
//Write
})
}).toList()
Update:
After understanding multiple group By in lINQ, my .NET Core WEB API is still complaining about bad request and doesn't return the nested JSON. I did update the LineItems prop to be IDictionary type with the decorator. Interestingly, if I comment out the DTO portion of LineItems and set it to null, the response comes back fine. Can you help what the issue is here?
public async Task<List<PointCardViewModel>> GetPointCards()
{
var queryPointCards =
((from s in db.Students
join dc in db.DailyCards on s.StudentId equals dc.StudentId
join dcli in db.DailyCardLineItems on dc.CardId equals dcli.CardId
join dcob in db.DailyCardOtherBehaviors on dc.CardId equals dcob.CardId
select new
{
s.StudentName,
s.StudentGrade,
dc.CardId,
dc.CardDate,
dcli.ClassParticipationPoints,
dcli.AssignmentCompletionPoints,
dcli.BonusHomeworkPoints,
dcli.ClassPeriod,
dcob.PersonalAppearancePoints,
dcob.LunchPoints,
dcob.RecessOtherPoints,
dcob.AmHomeroomPoints,
dcob.PmHomeroomPoints
})
.GroupBy(x => new {
x.CardId,
x.StudentGrade,
x.StudentName,
x.CardDate,
x.PersonalAppearancePoints,
x.LunchPoints,
x.RecessOtherPoints,
x.AmHomeroomPoints,
x.PmHomeroomPoints
})
.Select(x => new PointCardViewModel
{
CardId = x.Key.CardId,
StudentName = x.Key.StudentName,
Grade = x.Key.StudentGrade,
EvaluationDate = x.Key.CardDate,
PersonalAppearancePoints = x.Key.PersonalAppearancePoints,
LunchPoints = x.Key.LunchPoints,
RecessOtherPoints = x.Key.RecessOtherPoints,
AMHomeRoomPoints = x.Key.AmHomeroomPoints,
PMHomeRoomPoints = x.Key.PmHomeroomPoints,
LineItems = null
//x.Select(c => new LineItemViewModel
//{
// ClassPeriod = c.ClassPeriod,
// ClassParticipationPoints = c.ClassParticipationPoints,
// AssignmentCompletionPoints = c.AssignmentCompletionPoints,
// BonusHomeworkPoints = c.BonusHomeworkPoints
//}).ToDictionary(key => key.ClassPeriod, value => (object)value)
}
)
).ToListAsync();
if (db != null)
{
return await queryPointCards;
}
return null;
}
You could achieve this with a slight change in your query and resultant Data structure. For example
Changing your Data Structures as
public class PointCardViewModel
{
public int CardId { get; set; }
public string StudentName { get; set; }
public string Grade { get; set; }
public DateTime EvaluationDate { get; set; }
[JsonExtensionData]
public IDictionary<string, object> LineItems { get; set; } //Change Here
}
public class LineItemViewModel
{
public string ClassPeriod { get; set; }
public int Listen { get; set; }
public int Read { get; set; }
public int Write { get; set; }
}
Note that the LineItems has been converted to a Dictionary and decorated with JsonExtensionDataAttribute.
And now you could Change your Group By Query as
.GroupBy(x=> new {x.Name,x.Grade,x.CardID,x.Date})
.Select(x=> new PointCardViewModel
{
CardId=x.Key.CardID,
StudentName = x.Key.Name,
Grade = x.Key.Grade,
EvaluationDate = x.Key.Date,
LineItems = x.Select(c=> new LineItemViewModel
{
ClassPeriod = c.Class,
Listen = c.Listen,
Read = c.Read,
Write = c.Write
}).ToDictionary(key=>key.ClassPeriod,value=>(object)value)
});
Serializing the resultant data would give the required Json
Demo Code
Change the Group by and Select as below:
var result=((from s in db.Students
join dc in db.DailyCards on s.StudentId equals dc.StudentId
join dcli in db.DailyCardLineItems on dc.CardId equals dcli.CardId
join dcob in db.DailyCardOtherBehaviors on dc.CardId equals dcob.CardId
select new
{
s.StudentName,
s.StudentGrade,
dc.CardId,
dc.CardDate,
dcli.ClassParticipationPoints,
dcli.AssignmentCompletionPoints,
dcli.BonusHomeworkPoints,
dcli.ClassPeriod
})
.GroupBy(x => new { x.StudentName, x.CardId, x.StudentGrade, x.CardDate})
.Select(g => new PointCardViewModel()
{
CardId =g.Key.CardId,
StudentName = g.Key.StudentName,
Grade = g.Key.StudentGrade,
EvaluationDate = g.Key.CardDate,
LineItems = g.Select(y => new LineItemViewModel
{
Class=y.Class,
Read=y.ClassParticipationPoints,
Listen=y.AssignmentCompletionPoints,
Write=y.BonusHomeworkPoints
})
}).toList()
My RowMultiplevaluw table is
public class RowMultipleValues
{
public int ID { get; set; }
public String Year{ get; set; }
public string country { get; set; }
public decial Admin { get; set; }
public decimal Finance { get; set; }
public virtual ICollection<UsedAmount> UsedAmount { get; set; }
}
My used amount table is
public class UsedAmount
{
public int ID { get; set; }
public string Year{ get; set; }
public string country { get; set; }
public decial UsedAmount { get; set; }
public int RowMultipleValues ID { get; set; }
Public virtual RowMultibleValue RowMultibleValue { get; set; }
}
My query is
var query = from mtv in context.multiplerowvaluetable
join usd in dbcontext.usedtsble on mtv.year equal usd.year group g by mtv.country into g
select new { country =g.key,sumadmincolumn =g.sum(Admin),sumfinancecolumn = g.sum(finance) }).tolist();
Result which I want is
ID Year Country Admin. UsedAdmin Finance UsedFinance
1. 2017 USA 100 50 200 300
2. 2017 China 300 300 500 400
Total. 400 350 700 700
Please help me my model design and query for result.Thank.
So you want to join every MultipleValue with the UsedAmount on equal year value. Then group the result into groups of joined items with same country. Finally from every group create one object with the country, the sum of all Admin values and the sum of all finance values.
// first join the two collections on same year.
// we only need properties Country, Admin, Finance:
var result = myDbContext.MultipleRowValueTable.Join(myDbContext.UsedAmountTable,
multipleRow => multipleRow.Year, // from every multipleRow take the year
usedAmount => usedAmount.Year, // from every usedAmount take the year
(multipleRow, usedAmount) => new // when they match make a new object
{
Country = multipleRow.Country,
Admin = multipleRow.Admin,
UsedAdmin = usedAmount.Admin,
Finance = multipleRow.Finance,
UsedFinance = usedAmount.Finance,
})
// group the elements from this join table into groups with same Country
.GroupBy(joinedItem => joinedItem.Country, // all items in the group have this Country
joinedItem => new // the elements of the group
{
Admin = joinedItem.Admin,
UsedAdmin = joinedItem.UsedAdmin,
Finance = joinedItem.Finance,
UsedFinance = joinedItem.UsedFinance,
})
// finally: from every group take the Key (which is the Country)
// and the sum of the Admins and Finances in the group
.Select(group => new
{
Country = group.Key,
SumAdminColumn = group
.Select(groupElement => groupElement.Admin)
.Sum(),
... // others are similar
});
// from every group take the elements and sum the properties
.Select(group => new
{
Id = multipleRowValue.Id,
Year = multipleRowValue.Year,
Country = multipleRowValue.Country,
}
I have a following list after joining 2 tables
Id Name IsActive FeeName Amount
1 Test 1 Fee1 100
1 Test 1 Fee2 200
1 Test 1 Fee3 300
I have a class called products
public class Products
{
public int Id { get; set; }
public string ProductName { get; set; }
public bool IsActive { get; set; }
public List<ProductFee> ProductFunding { get; set; }
}
Product fee class has FeeName and amount properties,
I need to create a object of above class something like
1,test,1,List of ProductFee
var products = results.GroupBy(r => new { r.Id, r.Name, r.IsActive }).Select(g => new Products(){ Id = g.Key.Id, ProductName = g.Key.Name, IsActive = g.Key.IsActive, ProductFunding = g.Select(p => new ProductFee(){ FeeName = p.FeeName, Amount = p.Amount }).ToList() });
I have two entities:
public class Category
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
// Navigation properties
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
// Navigation properties
public virtual ICollection<Category> Categories { get; set; }
}
Based on a set of Address Id's, I need to Group By all the addresses by Category, including the Address Count for each Address.
Example:
Category: {1,"WORKSHOP",{1,2}},{2,"DEALER",{1,3}}
Address: {1,"Paul Workshop and Dealer",{1,2}}
{2,"Joe Workshop",{1}}
{3,"Peter Dealer",{2}}
If I have the Address Id's 1 and 3, I want to get:
Categories - "WORKSHOP - Count: 1"
"DEALER - Count: 2"
If I have the Address Id's 1 and 2, I want to get: Category -
Categories - "WORKSHOP - Count: 2"
"DEALER - Count: 1"
So far I get this, but the group by is not working:
var groupedAddresses = from add in addressQuery
where addressIds.Contains(add.Id)
group add by new { Category_Id = add.Categories, Address_Id = add.Id };
var result = from add in groupedAddresses
group add by add.Id into final
join c in categoryQuery on final.Key equals c.Id
select new CategoryGetAllBySearchDto
{
Id = final.Key,
Name = c.Name,
SearchCount = final.Count()
};
Any idea?
Thanks.
int[] addressIds = { 1, 3 };
var query = from c in categoryQuery
let searchCount = c.Addresses.Count(a => addressIds.Contains(a.Id))
where searchCount > 0
select new CategoryGetAllBySearchDto{
Id = c.Id,
Name = c.Name,
SearchCount = searchCount
};