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() };
Related
Thanks in advance for any help.
Not an expert in Linq to Sql by any means.
I have 4 tables.
The main lb_item table which defines, unsurprisingly, an item.
Many fields but holds 3 ID fields.
itemID (key)
categoryID (not null)
patternID (can be null)
lb_pattern table which is keyed off the lb_item patternID.
lb_category table which is keyed off the lb_item categoryID.
lb_animal table which is keyed off the lb_item item ID.
So I need a select from the lb_item table joining to these other 3 tables to bring back varchar fields as I'm building a DTO.
A single left outer join works fine thus:
from lbi in lbContext.lb_item
join lbp in lbContext.lb_pattern on lbi.patternID equals lbp.patternID into g1
from j1 in g1.DefaultIfEmpty()
join lbc in lbContext.lb_category on lbi.categoryID equals lbc.categoryID
where lbi.itemID == id
select new lb_itemDTO..........
I now need to add a 2nd left outer join for the lb_animal table.
So I started to do this:
from lbi in lbContext.lb_item
join lbp in lbContext.lb_pattern on lbi.patternID equals lbp.patternID into g1
from j1 in g1.DefaultIfEmpty()
join lba in lbContext.lb_animal on j1.
But the options in VS for j1 give me only the fields within the lb_pattern table.
I need the join to read:
join lba in lbContext.lb_animal on j1.itemID equals lba.itemID
or
join lba in lbContext.lb_animal on lbi.itemID equals lba.itemID
Neither works and gives me an exception along the lines of "'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core".
So how can I add a left outer join to the lb_animal table?
I've spent the last hour looking at various SO posts to suss it out but I just cannot seem to get my head around the solution for some reason. Feel like a newbie. And I'm sure the solution is going to be obvious!
Any help or pointers to a solution would be much appreciated.
This should work:
var ans = from lbi in lbContext.lb_item
where lbi.itemID == id
join lbp in lbContext.lb_pattern on lbi.patternID equals lbp.patternID into lbpj
from lbp in lbpj.DefaultIfEmpty()
join lba in lbContext.lb_animal on lbi.itemID equals lba.itemID into lbaj
from lba in lbaj.DefaultIfEmpty()
join lbc in lbContext.lb_category on lbi.categoryID equals lbc.categoryID
select new {
};
Persvered and finally came across a SO post which approached it in a different way and it worked.
Original SO is here:
My working code is now thus:
from lbi in lbContext.lb_item
from lbc in lbContext.lb_category
.Where(c => c.categoryID == lbi.categoryID)
from lbp in lbContext.lb_pattern
.Where(p => p.patternID == lbi.patternID)
.DefaultIfEmpty()
from lba in lbContext.lb_animal
.Where(a => a.itemID == lbi.itemID)
.DefaultIfEmpty()
where lbi.itemID == id
select new lb_itemDTO
Would still be interested in comments on this solution as it's not breaking the outer joins into grouped segments. So is this solution I found any less efficient in terms of the SQL it generates compared to how I was originally proposing to do it?
Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for
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. :)
There are a lot of questions on SO already about Left joins in Linq, and the ones I've looked at all use the join keyword to achieve the desired end.
This does not make sense to me. Let's say I have the tables Customer and Invoice, linked by a foreign key CustomerID on Invoice. Now I want to run a report containing customer info, plus any invoices. SQL would be:
select c.*, i.*
from Customer c
left join Invoice i on c.ID = i.CustomerID
From what I've seen of the answers on SO, people are mostly suggesting:
var q = from c in Customers
join i in Invoices.DefaultIfEmpty() on c.ID equals i.CustomerID
select new { c, i };
I really don't understand how this can be the only way. The relationship between Customer and Invoice is already defined by the LinqToSQL classes; why should I have to repeat it for the join clause? If I wanted an inner join it would simply have been:
var q = from c in Customers
from i in c.Invoices
select new { c, i };
without specifying the joined fields!
I tried:
var q = from c in Customers
from i in c.Invoices.DefaultIfEmpty()
select new { c, i };
but that just gave me the same result as if it were an inner join.
Is there not a better way of doing this?
While the relationship is already defined (both in the database and in the .dbml markup) the runtime cannot automatically determine if it should use that relationship.
What if there are two relationships in the object model (Person has Parents and Children, both relationships to other Person instances). While cases could be special cased, this would make the system more complex (so more bugs). Remember in SQL you would repeat the specification of the relationship.
Remember indexes and keys are an implementation detail and not part of the relational algebra that underlies the relation model.
If you want a LEFT OUTER JOIN then you need to use "into":
from c in Customers
join i in Invoices on i.CustomerId equals c.CustomerId into inv
...
and inv will have type IEnumerable<Invoivce>, possibly with no instances.
What are you talking about? That from i in c.Invoice.DefaultIfEmpty() is exactly a left join.
List<string> strings = new List<string>() { "Foo", "" };
var q = from s in strings
from c in s.DefaultIfEmpty()
select new { s, c };
foreach (var x in q)
{
Console.WriteLine("ValueOfStringIs|{0}| ValueOfCharIs|{1}|",
x.s,
(int)x.c);
}
This test produces:
ValueOfStringIs|Foo| ValueOfCharIs|70|
ValueOfStringIs|Foo| ValueOfCharIs|111|
ValueOfStringIs|Foo| ValueOfCharIs|111|
ValueOfStringIs|| ValueOfCharIs|0|
You may probably want to use the 'into' keyword.
Example
I want to convert the following query into LINQ syntax. I am having a great deal of trouble managing to get it to work. I actually tried starting from LINQ, but found that I might have better luck if I wrote it the other way around.
SELECT
pmt.guid,
pmt.sku,
pmt.name,
opt.color,
opt.size,
SUM(opt.qty) AS qtySold,
SUM(opt.qty * opt.itemprice) AS totalSales,
COUNT(omt.guid) AS betweenOrders
FROM
products_mainTable pmt
LEFT OUTER JOIN
orders_productsTable opt ON opt.products_mainTableGUID = pmt.guid
LEFT OUTER JOIN orders_mainTable omt ON omt.guid = opt.orders_mainTableGUID AND
(omt.flags & 1) = 1
GROUP BY
pmt.sku, opt.color, opt.size, pmt.guid, pmt.name
ORDER BY
pmt.sku
The end result is a table that shows me information about a product as you can see above.
How do I write this query, in LINQ form, using comprehension syntax ?
Additionally, I may want to add additional filters (to the orders_mainTable, for instance).
Here is one example that I tried to make work, and was fairly close but am not sure if it's the "correct" way, and was not able to group it by size and color from the orders_productsTable.
from pmt in products_mainTable
let Purchases =
from opt in pmt.orders_productsTable
where ((opt.orders_mainTable.flags & 1) == 1)
where ((opt.orders_mainTable.date_completedon > Convert.ToDateTime("01/01/2009 00:00:00")))
select opt
orderby pmt.sku
select new {
pmt.guid,
pmt.sku,
pmt.name,
pmt.price,
AvgPerOrder = Purchases.Average(p => p.qty).GetValueOrDefault(0),
QtySold = Purchases.Sum(p => p.qty).GetValueOrDefault(),
SoldFor = Purchases.Sum(p => p.itemprice * p.qty).GetValueOrDefault()
}
*Edit:
To be a little more explicit so you can understand what I am trying to do, here is some more explanation.
Products are stored in products_mainTable
Orders are stored in orders_mainTable
Products That Have Been Ordered are stored in orders_productsTable
I want to create several reports based on products, orders, etc. drilling into the data and finding meaningful bits to display to the end user.
In this instance, I am trying to show which products have been purchased over a period of time, and are the most popular. How many sold, for what price, and what is the breakout per order. Maybe not the best order, but I'm just experimenting and picked this one.
All of the tables have relationships to other tables. So from the product table, I can get to what orders ordered that product, etc.
The largest problem I am having, is understanding how LINQ works, especially with grouping, aggregate data, extensions, subqueries, etc. It's been fun, but it's starting to get frustrating because I am having difficulty finding detailed explanations on how to do this.
I'm also a beginner in LINQ. I don't know if this is the right way of grouping by several fields but I think you have to transform these grouping fields into a representing key. So, assuming that all your grouping fields are strings or ints you can make a key as follows:
var qry = from pmt in products_mainTable
join opt in orders_productsTable on pmt.guid equals opt.products_mainTableGUID
join omt in orders_mainTable on opt.orders_mainTableGUID equals omt.guid
where (opt.orders_mainTable.flags & 1) == 1
group omt by pmt.sku + opt.price + opt.size + pmt.guid + pmt.name into g
orderby g.sku
select new
{
g.FirstOrDefault().guid,
g.FirstOrDefault().sku,
g.FirstOrDefault().name,
g.FirstOrDefault().color,
g.FirstOrDefault().price,
AvgPerOrder = g.Average(p => p.qty).GetValueOrDefault(0),
QtySold = g.Sum(p => p.qty).GetValueOrDefault(),
SoldFor = g.Sum(p => p.itemprice * p.qty).GetValueOrDefault()
};
I didn't test this so please see if this helps you in any way.
Bruno, thank you so much for your assistance! The FirstOrDefault() was probably the largest help. Following some of what you did, and another resource I came up with the following that seems to work beautifully! This LINQ query below gave me nearly an exact replication of the SQL I posted above.
Here's the other resource I found on doing a LEFT OUTER JOIN in LINQ: Blog Post
Final Answer:
from pmt in products_mainTable
join opt in orders_productsTable on pmt.guid equals opt.products_mainTableGUID into tempProducts
from orderedProducts in tempProducts.DefaultIfEmpty()
join omt in orders_mainTable on orderedProducts.orders_mainTableGUID equals omt.guid into tempOrders
from ordersMain in tempOrders.DefaultIfEmpty()
group pmt by new { pmt.sku, orderedProducts.color, orderedProducts.size } into g
orderby g.FirstOrDefault().sku
select new {
g.FirstOrDefault().guid,
g.Key.sku,
g.Key.size,
QTY = g.FirstOrDefault().orders_productsTable.Sum(c => c.qty),
SUM = g.FirstOrDefault().orders_productsTable.Sum(c => c.itemprice * c.qty),
AVG = g.FirstOrDefault().orders_productsTable.Average(c => c.itemprice * c.qty),
Some = g.FirstOrDefault().orders_productsTable.Average(p => p.qty).GetValueOrDefault(0),
}
This was very helpful to me thanks. I had a similar issue I was trying to sort through only my case was much simpler as I didn't have any joins in it. I was simply trying to group one field, get the min of another, and the count. (min and count in the same query)
Here is the SQL I wanted to recreate in Linq syntax:
select t.Field1, min(t.Field2), COUNT(*)
from SomeTable t
group by t.Field1
order by t.Field1
Thanks to your post I eventually managed to come up with this:
from t in SomeTable
group t by new { t.Field1 } into g
orderby g.Key.Field1
select new
{
g.Key.Field1,
code = g.Min(c => c.Field2),
qty = g.Count()
}
Which creates the following SQL behind the scenes:
SELECT [t1].[Field1], [t1].[value] AS [code], [t1].[value2] AS [qty]
FROM (
SELECT MIN([t0].[Field2]) AS [value], COUNT(*) AS [value2], [t0].[Field1]
FROM [SomeTable] AS [t0]
GROUP BY [t0].[Field1]
) AS [t1]
ORDER BY [t1].[Field1]
Perfect, exactly what I was looking to do. The key for me was that you showed it possible to do this inside the new {} which is something I had never considered trying. This is huge, I now feel like I have a significantly better understanding going forward.