I have a People table, I want a method that return one Person by id, with numbers of Cars, number of houses, etc.
I need to Load this tables together? I see SQL generated by EF, is a monster.
Ex:
public Person Get()
{
return context.People.Include("Cars").Include("Houses").Where(x=> x.Id = 1).First();
}
My view use this:
Name: <%= people.Name%>
Cars: <%= people.Cars.Count%>
Houses: <%= people.Houses.Count%>
You can do this
var result = (
from p in ctx.People
where p.Id == 1
select new {
Person = p,
Cars = p.Cars.Count(),
Houses = p.Houses.Count()
}).FirstOrDefault();
Which will just bring back the counts.
Related
enter image description hereI have a code. And there you need to make a grouping by name.
//<date,<partid,amount>>
Dictionary<string, Dictionary<int, double>> emSpending = new Dictionary<string, Dictionary<int, double>>();
foreach (Orders order in db.Orders.ToList())
{
foreach (OrderItems orderitem in order.OrderItems.ToList())
{
if (!emSpending.ContainsKey(order.Date.ToString("yyyy-MM"))) emSpending.Add(order.Date.ToString("yyyy-MM"), new Dictionary<int, double>());
if (!emSpending[order.Date.ToString("yyyy-MM")].ContainsKey(Convert.ToInt32(orderitem.PartID))) emSpending[order.Date.ToString("yyyy-MM")].Add(Convert.ToInt32(orderitem.PartID), 0);
emSpending[order.Date.ToString("yyyy-MM")][Convert.ToInt32(orderitem.PartID)] += Convert.ToDouble(orderitem.Amount);
}
}
DataGridViewColumn col1 = new DataGridViewColumn();
col1.CellTemplate = new DataGridViewTextBoxCell();
col1.Name = "Department";
col1.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col1.HeaderText = "Department";
dgvEMSpending.Columns.Add(col1);
foreach (string date in emSpending.Keys)
{
DataGridViewColumn col = new DataGridViewColumn();
col.Name = date;
col.HeaderText = date;
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
col.CellTemplate = new DataGridViewTextBoxCell();
dgvEMSpending.Columns.Add(col);
}
List<string> allKey = emSpending.Keys.ToList();
foreach (string date in allKey)
if (date == "Department") continue;
else
{
dgvEMSpending.Rows.Add();
foreach (int partid in emSpending[date].Keys)
{
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts.Where(x => x.ID == partid).SingleOrDefault().Name.GroupBy(Name);
for (int i = 1; i < dgvEMSpending.Columns.Count; i++)
{
if (!emSpending.ContainsKey(dgvEMSpending.Columns[i].Name)) emSpending.Add(dgvEMSpending.Columns[i].Name, new Dictionary<int, double>());
if (!emSpending[dgvEMSpending.Columns[i].Name].ContainsKey(partid)) emSpending[dgvEMSpending.Columns[i].Name].Add(partid, 0);
double val = emSpending[dgvEMSpending.Columns[i].Name][partid];
dgvEMSpending.Rows[dgvEMSpending.RowCount - 1].Cells[i].Value = val;
}
}
}
I tried to use group by myself, but something doesn't work. It just outputs the same names, and I want to group them so that there is a grouping. Pls helped to me.
Ok, a few issues to help you out first. This code:
foreach (Orders order in db.Orders.ToList())
{
foreach (OrderItems orderitem in order.OrderItems.ToList())
{
if (!emSpending.ContainsKey(order.Date.ToString("yyyy-MM"))) emSpending.Add(order.Date.ToString("yyyy-MM"), new Dictionary<int, double>());
if (!emSpending[order.Date.ToString("yyyy-MM")].ContainsKey(Convert.ToInt32(orderitem.PartID))) emSpending[order.Date.ToString("yyyy-MM")].Add(Convert.ToInt32(orderitem.PartID), 0);
emSpending[order.Date.ToString("yyyy-MM")][Convert.ToInt32(orderitem.PartID)] += Convert.ToDouble(orderitem.Amount);
}
}
Right off the bat this is going to trip lazy loading on OrderItems. If you have 10 orders 1-10 you're going to be running 11 queries against the database:
SELECT * FROM Orders;
SELECT * FROM OrderItems WHERE OrderId = 1;
SELECT * FROM OrderItems WHERE OrderId = 2;
// ...
SELECT * FROM OrderItems WHERE OrderId = 10;
Now if you have 100 orders or 1000 orders, you should see the problem. At a minimum ensure that if you are touching a collection or reference on entities you are loading, eager load it with Include:
foreach (Orders order in db.Orders.Include(x => x.OrderItems).ToList())
This will run a single query that fetches the Orders and their OrderItems. However, if you have a LOT of rows this is going to take a while and consume a LOT of memory.
The next tip is "only load what you need". You need 1 field from Order and 2 fields from OrderItem. So why load everything from both tables??
var orderItemDetails = db.Orders
.SelectMany(o => o.OrderItems.Select(oi => new { o.Date, oi.PartId, oi.Amount })
.ToList();
This would give us just the Order date, and each Part ID and Amount. Now that this data is in memory we can group it to populate your desired dictionary structure without having to iterate over it row by row.
var emSpending = orderItemDetails.GroupBy(x => x.Date.ToString("yyyy-MM"))
.ToDictionary(g => g.Key,
g => g.GroupBy(y => y.PartId)
.ToDictionary(g2 => g2.Key, g2 => g2.Sum(z => z.Amount)));
Depending on the Types in your entities you may need to insert casts. This first groups the outer dictionary of the yyyy-MM of the order dates, then it groups the remaining data for each date by part ID, and sums the Amount.
Now relating to your question, from your code example I'm guessing the problem area you are facing is this line:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts
.Where(x => x.ID == partid)
.SingleOrDefault().Name.GroupBy(Name);
Now the question would be to explain what exactly you are expecting from this? You are fetching a single Part by ID. How would you expect this to be "grouped"?
If you want to display the Part name instead of the PartId then I believe you would just want to Select the Part Name:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = db.Parts
.Where(x => x.ID == partid)
.Select(x => x.Name)
.SingleOrDefault();
We can go one better to fetch the Part names for each used product in one hit using our loaded order details:
var partIds = orderItemDetails
.Select(x=> x.PartId)
.Distinct()
.ToList();
var partDetails = db.Parts
.Where(x => partIds.Contains(x.ID))
.ToDictionary(x => x.ID, x => x.Name);
This fetches us a dictionary set indexed by ID for the part names, it would be done outside of the loop after we had loaded the orderItemDetails. Now we don't have to go to the DB with every row:
dgvEMSpending.Rows[dgvEMSpending.Rows.Count - 1].Cells[0].Value = partDetails[partId];
A newbie asks...
Part 1
Suppose I have 3 classes (and their equivalent SQL tables) :
Product
{
int Id;
List<Keyword> Keywords;
List<Serial> Serials;
}
Keyword
{
int Id;
int ProductId; // FK to Product
string Name;
}
Serial
{
int Id;
int ProductId; // FK to Product
string SerialNumber;
}
When loading PRODUCT == 123, the we could do this:
item = db.Products.FirstOrDefault(p => p.Id == 123);
item.Keywords = db.Keywords.Where(p => p.ProductId == 123).ToList();
item.Serials = db.Serials.Where(p => p.ProductId == 123).ToList();
which is 3 SQL statements.
Or we could do this:
from product in db.Products.AsNoTracking()
join link1 in Db.Keywords.AsNoTracking()
on product.Id equals link1.ProductId into kwJoin
from keyword in kwJoin.DefaultIfEmpty()
join link2 in Db.Serials.AsNoTracking()
on product.Id equals link2.ProductId into serJoin
from serial in serJoin.DefaultIfEmpty()
where product.Id == 123
select new { product, keyword, serial };
which gives 1 SQL statement but produces far too many rows (number of keywords x number of serials) that need to be coalesced together
Both seem less than efficient. Is there a better way?
Part 2
As another question, but using the same example, when we have a join like so:
from product in db.Products.AsNoTracking()
join link1 in Db.Keywords.AsNoTracking()
on product.Id equals link1.ProductId into kwJoin
from keyword in kwJoin.DefaultIfEmpty()
select new { product, keyword };
Is there a way to assign the keywords directly in the product, in select statement?
select new { product, product.Keywords = keyword };
Thanks for any help!
If the FKs exist, depending on how you have setup your DB context, the properties will automatically be fetched. No joins required. Part 1 query is simple as it has a filter. Part 2 might have issues depending on how many records needs to be fetched from the database. You can map the fields to anonymous objects(or DTOs) after the fact that you have keyword objects for each product in the list.
Part 1
item = db.Products
.Include(p=>p.Keywords)
.Include(s=>s.Serials)
.Where(p => p.Id == 123)
.FirstOrDefault();
Part 2
products = db.Products.Include(p=>p.Keywords).ToList();
Given a table of players (users) with several fields. One of this is their rating with respect other players.
I'd like to implement via LINQ following SQL query:
SELECT p.*,
(select COUNT(*) from Users where (Rating > p.Rating)) as Rank
FROM Users as p
ORDER BY p.Rating DESC
In other words, last field (RANK) should give the rank of each user with respect the others:
Id Username ... Rating Rank
43 player41 ... 1002,333 0
99 player97 ... 1002 1
202 player200 ... 1002 1
53 player51 ... 1000,667 2
168 player166 ... 1000,667 2
56 player54 ... 1000 3
32 player30 ... 999,342 4
This attempt does not work:
var q = from u in Users
orderby u.Rating descending
group u by u.Id into g
select new
{
MyKey = g.Key,
User = g.First(),
cnt = Users.Count(uu => uu.Rating > g.First().Rating) + 1
};
Just for your knowledge, note that the table Users is mapped to a EF entity named User with a 'NotMapped' int? field named Rank where I'd like to manually copy the rank:
class User {
...
[NotMapped]
public virtual int? Rank { get; internal set; }
}
You'll want something like:
var rankedUsers = db.Users
.Select(user => new
{
User = user,
Rank = db.Users.Count(innerQueryUser => innerQueryUser.Rating > user.Rating)
})
.ToList();
Which will give you a list of users and their Rank as an anonymous type.
You'll then be able to do something like:
List<User> users = rankedUsers.Select(rankedUser=>
{
rankedUser.User.Rank = rankedUser.Rank;
return rankedUser.User;
})
.ToList();
Try this:
var q = (from u in Users
select new
{
UserId = u.Id,
UserObj = u,
Rank = (from u1 in Users where u1.Rating>u.Rating select u1).Count()
}).ToList();
we have 4 entities:
Bills
BillDetails
Products
ProductGroups
what I want to do is something like this
var qry = from bill in ctx.Bills
join billDetail in ctx.BillDetails on bill.Id equal billDetail.Bill_Id
join product in ctx.Products on billDetail.Product_Id equals product.Id
join productGroup in ctx.ProductGroups
on product.ProductGroup_Id equals productGroup.Id
where
productGroup.Id == 113
select bill;
The problem is if we disable lazy loading the returned bill dos not contains BillDetail entity,
we have to return it explicitly as anonymous object.
Is there any way to convert it to something like this ?
var qry = ctx.Bills
.Include("BillDetails.Products.ProductGroup")
.where(s=>s.BillDetails.Products.ProductGroup.Id == 113);
Not 100% what you need, but something like this may work:
var qry = ctx.Bills
.Include("BillDetails.Products.ProductGroup")
.SelectMany(b => b.BillDetails)
.Where(bd => bd.Product.ProductGroup.Id == 113);
Let say I have the following classes:
Product { ID, Name }
Meta { ID, Object, Key, Value }
Category { ID, Name }
Relation {ID, ChildID, ParentID } (Child = Product, Parent = Category)
and some sample data:
Product:
ID Name
1 Chair
2 Table
Meta
ID Object Key Value
1 1 Color "Red"
2 1 Size "Large"
3 2 Color "Blue"
4 2 Size "Small"
Category
ID Name
1 Indoor
2 Outdoor
Relation
ID ChildID ParentID
1 1 1
2 1 2
3 2 1
Can we use Distinct and Group by to produce the following format (ProductDetail)
ID=1,
Name=Chair,
Parent=
{
{ ID=1, Name="Indoor" },
{ ID=2, Name="Outdoor" }
},
Properties { Color="Red", Size="Large" }
ID=2,
Name=Table,
Parent=
{
{ ID=1, Name="Indoor"}
},
Properties { Color = "Blue", Size = "Small" }
which we can get the "Color" value of the first item by using
ProductDetails[0].Properties.Color
Any helps would be appreciated!
No, you can't do this based on what you've said - because "Color" and "Size" are part of the data, rather than part of the model. They're only known at execution time, so unless you use dynamic typing, you're not going to be able to access it by Properties.Color.
You could, however, use Properties["Color"] potentially:
var query = from product in db.Products
join meta in db.Meta
on product.ID equals meta.Object
into properties
select new { Product = product,
Properties = properties.ToDictionary(m => m.Key,
m => m.Value) };
So for each product, you'll have a dictionary of properties. That works logically, but you may need to tweak it to get it to work in the entity framework - I don't know how well that supports ToDictionary.
EDIT: Okay, I'll leave the above up as the "ideal" solution, but if EF doesn't support ToDictionary, you'd have to do that part in-process:
var query = (from product in db.Products
join meta in db.Meta
on product.ID equals meta.Object
into properties
select new { product, properties })
.AsEnumerable()
.Select(p => new {
Product = p.product,
Properties = p.properties.ToDictionary(m => m.Key,
m => m.Value) });
I just came across this question while learning LINQ, but I wasn't satisfied that Jon's output matched the question (sorry Jon). The following code returns a List of anonymously-typed objects that better match the structure of your question:
var ProductDetails = (from p in Product
let Parents = from r in Relation
where r.ChildID == p.ID
join c in Category on r.ParentID equals c.ID
into RelationCategory
from rc in RelationCategory
select new
{
rc.ID,
rc.Name
}
join m in Meta on p.ID equals m.Object into ProductMeta
select new
{
p.ID,
p.Name,
Parent = Parents.ToList(),
ProductMeta
})
.AsEnumerable()
.Select(p => new
{
p.ID,
p.Name,
p.Parent,
Properties = p.ProductMeta
.ToDictionary(e => e.Key, e => e.Value)
}).ToList();
Credit goes mostly to Jon Skeet and the Visual Studio debugger ;)
I realise that you've probably moved on by now but hopefully this might help someone else looking to learn LINQ, as I was.