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();
Related
I'm using entity framework to connect to database from my application. I have table in SQL, named Orders. It contains such fields as: TransactionId, ParticipantId and is linked to Transactions table which has one to many connection to Participants table. I need to get data from it using List of classes with such properties: TransactionId, ParticipantId, OrganizationId. Linq must meet such conditions: (orders.TransactionId == TransactionId && orders.ParticipantId == ParticipantId && orders.Transaction.Participants.Any(x=> x.Id == OrganizationId)). This should be done by one query, not by multiple, so, please don't recommend foreach or smth like that.
Like #NetMage said, generally we need examples. Assuming that you've got a dbcontext set up, the ask is pretty simple:
public static void GetData(int transactionId, int participantId, int organizationId)
{
using (var db = new MyDbContext())
{
var query =
(
from t in db.Transactions
from o in db.Orders
.Where(w => w.TransactionId == t.TransactionId)
from p in db.Participants
.Where(w => w.TransactionId == t.TransactionId)
where t.TransactionId = transactionId &&
o.ParticipantId = participantId
select new { Order = o, Transaction = t, Participant = p}
);
}
}
Again since we don't have a lot of information here it's hard to do more. You should be able to take it from there. I know I didn't use the organizationId filter, but since I don't know the target shape of the data I'm not sure what the best path would be
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);
I'm trying to write a query for a database that will left join a table to a look up table and the results will be returned based on a case statement.
In normal SQL the query would look like this:
SELECT chis_id, chis_detail, cilt.mhcatID, cilt.mhtID, 'TheFileName' =
CASE
WHEN cilt.mhcatID IS NOT NULL AND cilt.mhtID IS NOT NULL THEN chis_linked_filename
END
FROM chis
LEFT JOIN cilt on cilt.mhcatID = chis.mhcat_id AND cilt.mhtID = chis.mht_id
WHERE cch_id = 50
chis is the table being queried, cilt is a look-up table and does not contain any foreign key relationships to chis as a result (chis has existing FK's to mht and mhcat tables by the mhtID and mhcatID respectively).
The query will be used to return a list of history updates for a record. If the join to the cilt lookup table is successful this means that the caller of the query will have permission to view the filename of any associated files for the history updates.
Whilst during my research I've found various posts on here relating on how to do case statements and left joins in Linq to Entity queries, I've not been able to work out how to join on two different fields. Is this possible?
You need to join on an anonymous type with matching field names like so:
var query = from x in context.Table1
join y in context.Table2
on new { x.Field1, x.Field2 } equals new { y.Field1, y.Field2 }
select {...};
A full working example using the an extra from instead of a join would look something like this:
var query = from chis in context.Chis
from clit in context.Clit
.Where(x => x.mhcatID = chis.mhcat_id)
.Where(x => x.mhtID = chis.mht_id)
.DefaultIfEmpty()
select new
{
chis.id,
chis.detail,
cilt.mhcatID,
cilt.mhtID,
TheFileName = (cilt.mhcatID != null && cilt.mhtID != null) ? chis.linked_filename : null
};
Based on what Aducci suggested, I used a group join and DefaultIsEmpty() to get the results I wanted. For some reason, I couldn't get DefaultIfEmpty() didn't work correctly on its own and the resulting SQL employed an inner join instead of a left.
Here's the final code I used to get the left join working:
var query = (from chis in context.chis
join cilt in context.cilts on new { MHT = chis.mht_id, MHTCAT = chis.mhcat_id } equals new { MHT = cilt.mhtID, MHTCAT = cilt.mhcatID } into tempCilts
from tempCilt in tempCilts.DefaultIfEmpty()
where chis.cch_id == 50
select new {
chisID = chis.chis_id,
detail = chis.chis_detail,
filename = chis.chis_linked_filename,
TheFileName = (tempCilt.mhcatID != null && tempCilt.mhtID != null ? chis.chis_linked_filename : null),
mhtID = chis.mht_id,
mhtcatID = chis.mhcat_id
}).ToList();
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.
I've got an EDM with two tables Product and ProductCategory with a many-to-many relationship between both.
What I'm currently trying to do is to build a dynamic query to select the products related to the categories the user has selected through the UI.
In short I should build a query like the following but based one or more categories that I don't know at compile time.
var productQuery = context.Product.Where
(p => p.ProductCategories.Any(c => c.CategoryId == id1 ||
c.CategoryId == id2 || ...));
I've read a lot of things and I'm actually very new to linq so I really don't know where to start.
What is the best approach to do such queries ?
Thank you for your time.
var ids = new [] { id1, id2, // ...
var productQuery = context.Product.Where(
p => p.ProductCategories.Any(c => ids.Contains(c.CategoryId)));