Which Linq join will work for my scenario, confused - c#

I have a web api project. In database I have two tables of comments and pictures. I want to use join to merge these two tables in such a way that every picture should have all the comments related to it. Both tables have picture id. Which join should I use? I need to use linq. Can someone tell me the linq query I should use?
I have tried cross join, in this way
var combo = from p in db.picturedetails
from c in db.comments
select new CommentAndPictureDetails
{
IdUser = p.iduser,
IdPictures = p.idpictures,
Likes = p.likes,
NudityLevel = p.nuditylevel,
PicTitle = p.picTitle,
PicTime = p.pictime,
FakesLevel = p.fakeslevel,
Comment1 c.comment1,
CTime = c.ctime,
IdComments = c.idcomments,
SpamLevel = c.spamlevel,
TargetPictureId = c.targetpictureid
};
But I am getting all the pictures with all the comments so a very big json. So which join should i use?

What you're looking for a group join:
var query = from p in db.picturedetails
join c in db.comments
on p.PictureId equals c.PictureId into comments
select new
{
ID = p.PictureId,
Comments = comments,
//...
};

My understanding of LINQ is limited at best but I will try to answer what I assume you are asking.
As far as my understanding of your question goes you are wanting the following to work :
Table1:
PictureID
PictureName
Table2:
PictureID
Comments
And the result to have 1 picture with multiple comments. Is this a correct assumption?
If so I do not believe that is possible with 1 single query as the query will return a picture for every comment, the best way would be to find the 1 picture object, then find the multiple comment objects seperately and return them in some fashion to make it appear as one object.

Related

Linq Select Results Generated Used Multiple Times

I have a query that is pulling data from the database and also manufacturing data with other methods that is included in the result set.
var results = from mr in db.Material_Reqs
join j in db.Jobs on mr.Job equals j.Job1
where j.Top_Lvl_Job == topLevelJob
select new
{
mr.Material,
mr.Material_Req1,
mr.Job,
mr.Description,
Lot = GetLotNumber(mr.Material),
UnitCost = GetUnitCost(mr.Material, GetLotNumber(mr.Material))
};
The Lot field is added by calling a function GetLotNumber and passing it the material field from the query. The UnitCost needs the generated field Lot to be calculated. Is there a technique to use the Lot filed in a method to generate the next field?
What I've shown works but is wasteful of time because it regenerated the same data twice for the Lot number.
How can I use the generated Lot field to pass to a method?
Ralf has the answer, I never know about "let" before today, thank you!
var results = from mr in db.Material_Reqs
join j in db.Jobs on mr.Job equals j.Job1
where j.Top_Lvl_Job == topLevelJob
let tmpLot = mr.Material
select new
{
mr.Material,
mr.Material_Req1,
mr.Job,
mr.Description,
Lot = tmpLot,
UnitCost = GetUnitCost(mr.Material, tmpLot)
};
This opens a bunch of interesting combinations. Thank you to everyone for the assist!

How to Get Top 5 Rated User in 2 Tables with LINQ using C#

I have 2 tables, one is Posts another is Comments. These tables contain "RatedPoint" field.
I want to take 5 users who have the highest point.
For example, user ID =1 and its total point 50 in Post table
and it's total point is 25 in Comment table, so its total point is 75
so, i have to look whole members and after choose 5 highest point
It seems a bit complicated, i hope its clear..
I tried something like that
var abc= csEntity.Users.Where(u => csEntity.Posts.Any(p => u.Id == p.UserId)).
Take(userCount).OrderByDescending(u => u.Posts.Count).ToList();
or..
var xyz = csEntity.Posts.Where(p => csEntity.Comments.Any(c => c.UserId == p.UserId));
I dont want to use 2 different list if possible.. is it possible to do it in one query?
I could do it with 2 for loops, but i think its a bad idea..
Post TABLE
Comments TABLE
As you see, these two tables contain userID and each user has RatedPoint...
I think now its clear
EDIT: Maybe a user never write a comment or never write a post just write a comment.. then i think we musnt make equal posts.userId=comments.UserId
Here is a LINQ expression that does what you seem to be asking for:
var result = from p in posts
join c in comments on p.Id equals c.Id
select new { Id = p.Id, Total = p.Points + c.Points };
That provides the actual joined data. Then you can pick the top 5 like this:
result.OrderByDescending(item => item.Total).Take(5)
Note that the above does assume that both tables always have each user, even if they didn't post or comment. I.e. they would simply have a point count of 0. Your updated question clarifies that in your case, you have potentially disjoint tables, i.e. a user can be in one table but not the other.
In that case, the following should work for you:
var leftOuter = from p in posts
join c in comments on p.Id equals c.Id into groupJoin
let c = groupJoin.SingleOrDefault()
select new { Id = p.Id, Total = p.Points + (c == null ? 0 : c.Points) };
var rightAnti = from c in comments
join p in posts on c.Id equals p.Id into groupJoin
let p = groupJoin.SingleOrDefault()
where p == null
select new { Id = c.Id, Total = c.Points };
var result = leftOuter.Concat(rightAnti);
The first LINQ expression does a left outer join. The second LINQ expression does a left anti-join (but I call it "right" because it's effectively the right-join of the original data :) ). I'm using SingleToDefault() to ensure that each user is in each table once at most. The code will throw an exception if it turns out they are present more than once (which otherwise would result in that user being represented in the final result more than once).
I admit, I don't know whether the above is the most efficient approach. I think it should be pretty close, since the joins should be optimized (in objects or SQL) and that's the most expensive part of the whole operation. But I make no promises regarding performance. :)

Linq and RESTful services: how to best merge data from multiple tables in a resultset

I'm experimenting with pulling data from multiple datasets using RESTful services. I'm hooking up to the Cloud version of Northwind, and attempting to use Linq to get the equivalent of this:
SELECT TOP 20 p.ProductName, p.ProductID, s.SupplierID, s.CompanyName AS Supplier,
s.ContactName, s.ContactTitle, s.Phone
FROM Products p
JOIN Suppliers s on p.SupplierID = s.SupplierID
ORDER BY ProductName
So, I define a class to hold my data:
public class ProductSuppliers
{
public string ProductName;
public int ProductID;
public string SupplierName;
public string ContactName;
public string ContactPosition;
public string ContactPhone;
}
And hook into the Northwind service:
NorthwindEntities dc = new NorthwindEntities (new
Uri("http://services.odata.org/Northwind/Northwind.svc/"));
After trying to set up a join, not being able to get it to work, and wandering around in the back corridors of MSDN for a while, I find that Linq joins aren't supported by the OData spec. Which seems obvious once you think about it, given the limitations of URI syntax.
Of course, the usual thing to do is stored procs and views on the server side anyway, handling any sort of joins there. However, I wanted to work out some sort of solution for a situation like this one, where you don't have the capability of creating stored procs or views.
My naive solution has all the elegance of medieval battlefield surgery, and it has to scale horribly. I pulled the two tables as two separate List objects, then iterated one, used Find to locate the matching ID in the other, and Added a combined record into my Product. Here's the code:
public List<ProductSuppliers> GetProductSuppliers()
{
var result = new List<ProductSuppliers>();
ProductSuppliers ps;
var prods =
(
from p in dc.Products
orderby p.ProductName
select p
).ToList();
var sups =
(
from s in dc.Suppliers
select s
).ToList();
foreach (var p in prods)
{
int cIndex = sups.IndexOf(sups.Find(x => x.SupplierID == p.SupplierID));
ps = new ProductSuppliers()
{
ProductName = p.ProductName,
ProductID = p.ProductID,
SupplierName = sups[cIndex].CompanyName,
ContactName = sups[cIndex].ContactName,
ContactPosition = sups[cIndex].ContactTitle,
ContactPhone = sups[cIndex].Phone
};
result.Add(ps);
}
return result;
}
There has to be something better than this, doesn't there? Is there something obvious I'm missing?
[Edit] I've looked at the link someone gave me on the Expand method, and that works...sort of. Here's the code change:
var sups =
(
from s in dc.Suppliers.Expand("Products")
select s
).ToList();
This gives me a list of Suppliers with Products for each in a sublist (dc.Suppliers[0].Products[0], etc.). While I could get what I want from there, I'd still have to iterate the entire list to invert the values (wouldn't I?), so it doesn't look like a more scaleable solution. Also, I can't apply Expand to the Products table to include Suppliers (Changing the from clause in prods to from p in dc.Products.Expand("Suppliers") results in a helpful "An Error occurred while processing this request."). So, it doesn't look like I can expand products to include lookup values from Suppliers, since it looks like expanding is expanding parents to include children, not looking up parent values in a list of children. Is there a way to use Expand (or is there some other mechanism besides client-side manipulation of the two tables) to include lookup values from a foreign key table?
The best you can do is described in this SO answer to a similar question. Not what you expected either, since you're required to make multiple roundtrips to the service.
If you don't control the server-side of things (or you don't want to use SPs/views/joins there) you are forced to use one of these mechanisms.
Anyway, at the very least you can improve the products-suppliers matching in your code to this:
var results = from p in prods
join s in sups on s.SupplierId equals p.SupplierId
select new ProductSuppliers()
{
ProductName = p.ProductName,
ProductID = p.ProductID,
SupplierName = s.CompanyName,
ContactName = s.ContactName,
ContactPosition = s.ContactTitle,
ContactPhone = s.Phone
};
You still need to retrieve all records and join in-memory, though.

C# Linq eqiuvalent of SQL Count()

I have a fairly complicated join query that I use with my database. Upon running it I end up with results that contain an baseID and a bunch of other fields. I then want to take this baseID and determine how many times it occurs in a table like this:
TableToBeCounted (Many to Many)
{
baseID,
childID
}
How do I perform a linq query that still uses the query I already have and then JOINs the count() with the baseID?
Something like this in untested linq code:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
select new {c.ClassID, c.Name};
I then want to take that code and count how many orders are nested within each class. I then want to append a column using linq so that my final select looks like this:
select new {c.ClassID, c.Name, o.Count()}//Or something like that.
The entire example is based upon the Biological Classification system.
Update:
Assume for the example that I have multiple tables:
Kingdom
|--Phylum
|--Class
|--Order
Each Phylum has a Phylum ID and a Kingdom ID. Meaning that all phylum are a subset of a kingdom. All Orders are subsets of a Class ID. I want to count how many Orders below to each class.
I hope this is clear now.
Normally this is done with a group. For example:
from k in db.Kingdom
join p in db.Phylum on k.KingdomID equals p.KingdomID
where p.PhylumID == "Something"
join c in db.Class on p.PhylumID equals c.PhylumID
group c by new { c.ClassID, c.Name } into g
select new { Count = g.Count(), g.Key.ClassID, g.Key.Name };
That will basically count how many entries you have for each ClassID/Name pair. However, as Winston says in the comments, you're possibly interested in another table (Order) that you haven't told us about. We can't really give much more information until we know what you're doing here. Do you already have a relationship set up for this in LINQ to SQL? Please tell us about the Order table and how it relates to your other tables.
EDIT: Okay, with the modified question, I suspect we can ignore phylum and kingdom completely, unless I'm missing something. (I also can't see how this relates to a many-to-many mapping...)
I think this would work:
from o in db.Order
group o by o.ClassID into g
join c in db.Class on g.Key.ClassID equals c.ClassID
select new { c.ClassID, c.Name, g.Count() };

LINQ to SQL join when there aren't results

Given the following database structure
alt text http://dl.dropbox.com/u/26791/tables.png
I'm trying to write a LINQ query that will return images grouped by tags it's associated with. So far I've got this:
var images = from img in db.Images
join imgTags in db.ImageTags on img.idImage equals imgTags.idImage
join t in db.Tags on imgTags.idTag equals t.idTag
where img.OCRData.Contains(searchText.Text)
group img by new { t.TagName } into aGroup
select new
{
GroupName = aGroup.Key.TagName,
Items = from x in aGroup
select new ImageFragment()
{
ImageID = x.idImage,
ScanDate = x.ScanTime
}
};
Which works great. However, I also want to return Images that do not have any tags associated with them in a group of "(Untagged)" or something. I can't wrap my head around how I would do this without inserting a default tag for every image and that seems like generally not a very good solution.
If you want image records when there are no corresponding tag records, you need to perform an outer join on the image tags table.
It's a little tricky, but you can do it in one big query if you have the ability to instantiate new ImageTag and Tag instances for linq to work with. Essentially, when you're doing an outer join, you have to use the into keyword with the DefaultIfEmpty(...) method to deal with the "outer join gaps" (e.g., when the right side of the joined key is null in a typical SQL left outer join).
var images = from img in db.Images
join imgTags in db.ImageTags on img.idImage equals imgTags.idImage
into outerImageRef
from outerIR in outerImageRef.DefaultIfEmpty(new ImageTag() { idImage = img.idImage, idTag = -1 })
join t in db.Tags on imgTags.idTag equals t.idTag
into outerRefTags
from outerRT in outerRefTags.DefaultIfEmpty(new Tag(){ idTag=-1, TagName ="untagged"})
group img by outerRT.TagName into aGroup
select new {
GroupName = aGroup.Key,
Items = from x in aGroup
select new ImageFragment() {
ImageID = x.idImage,
ScanDate = x.ScanTime
}
};
Hopefully the above compiles since I don't have your exact environment, I built my solution using my own data types and then converted it to your question's description. Basically the key parts are the extra into and DefaultIfEmpty lines that essentially help add the extra "rows" into the massively joined table that's in memory if you're thinking about it in the traditional sql sense.
However, there's a more readable solution that doesn't require the in memory instantiation of linq entities (you'll have to convert this one yourself to your environment):
//this first query will return a collection of anonymous types with TagName and ImageId,
// essentially a relation from joining your ImageTags x-ref table and Tags so that
// each row is the tag and image id (as Robert Harvey mentioned in his comment to your Q)
var tagNamesWithImageIds = from tag in Tags
join refer in ImageTags on tag.IdTag equals refer.IdTag
select new {
TagName = tag.Name,
ImageId = refer.IdImage
};
//Now we can get your solution by outer joining the images to the above relation
// and filling in the "outer join gaps" with the anonymous type again of "untagged"
// and then joining that with the Images table one last time to get your grouping and projection.
var images = from img in Images
join t in tagNamesWithImageIds on img.IdImage equals t.ImageId
into outerJoin
from o in outerJoin.DefaultIfEmpty(new { TagName = "untagged", ImageId = img.IdImage })
join img2 in Images on o.ImageId equals img2.IdImage
group img2 by o.TagName into aGroup
select new {
TagName = aGroup.Key,
Images = aGroup.Select(i => i.Data).ToList() //you'll definitely need to replace this with your code's logic. I just had a much simpler data type in my workspace.
};
Hope that makes sense.
Of course, you can always just set your application to tag everything by default w/ "untagged" or do some much simpler LINQ queries to create a list of image id's that are not present in your ImageTag table, and then union or something.
Here's what I ended up doing. I haven't actually checked what kind of SQL this is generating yet, I'm guessing that it's probably not exactly pretty. I think I'd be better off doing a couple queries and aggregating the stuff myself, but in any case this works:
var images = from img in db.Images
join imgTags in db.ImageTags on img.idImage equals imgTags.idImage into g
from imgTags in g.DefaultIfEmpty()
join t in db.Tags on imgTags.idTag equals t.idTag into g1
from t in g1.DefaultIfEmpty()
where img.OCRData.Contains(searchText.Text)
group img by t == null ? "(No Tags)" : t.TagName into aGroup
select new
{
GroupName = aGroup.Key,
Items = from x in aGroup
select new ImageFragment()
{
ImageID = x.idImage,
ScanDate = x.ScanTime
}
};

Categories

Resources