LINQ Left Outer Join only the first record - linq

I'm working on a LINQ query that joins three tables. For the Orders and OrderInfo table I expect a single record in each table for a given order id. However for the ShipRate table, there could be 0, 1 or more records for a given order id. So for this table I am using a left outer join. The query shown below is working if 0 or 1 records exist in the ShipRate table, but for instances where the number of records is > 1, I need to select only the most recent ShipRate record. I tried to do this by replacing the line:
from shipRate in sr.DefaultIfEmpty()
with this:
from shipRate in sr.OrderByDescending(r => r.CreateDate).Take(1).DefaultIfEmpty()
but the query takes forever, as if it is loading the entire ShipRate table. Where have I gone wrong?
var query = (from order in db.Orders
join info in db.OrderInfo
on order.OrderId equals info.OrderId
join shipRate in db.ShipRate
on info.OrderId equals shipRate.OrderId
into sr
from shipRate in sr.DefaultIfEmpty()
where order.OrderId == orderId
select new
{
OrderId = order.OrderId,
OrderDetail = info.OrderDetail,
Carrier = shipRate.Carrier
}).SingleOrDefault();

With a proper model definition your query would be like:
var query = (from order in db.Orders
where order.OrderId == orderId
select new
{
OrderId = order.OrderId,
OrderDetail = order.OrderInfo.OrderDetail,
Carrier = order.OrderInfo.ShipRates.OrderBy(sr =>sr.CreateDate).FirstOrDefault()
}).SingleOrDefault();
I can't be sure though, because you didn't supply sample data and model.

Cetin Basoz's answer is a good one: ideally you'd set up your model in a way that allows you to use navigation properties. If you're using a model generated from your database schema, that typically means setting up foreign and primary keys properly.
If you can't do that, you should still be able to get a similar effect by writing SQL like this:
var query = (from order in db.Orders
where order.OrderId == orderId
let orderInfo = db.OrderInfo.FirstOrDefault(info => order.OrderId == info.OrderId)
let currentShipRate = db.ShipRate
.Where(shipRate => info.OrderId == shipRate.OrderId)
.OrderByDescending(shipRate => shipRate.CreateDate)
.FirstOrDefault()
select new
{
OrderId = order.OrderId,
OrderDetail = orderInfo.OrderDetail,
Carrier = currentShipRate.Carrier
}).SingleOrDefault();
However, LINQ to SQL isn't nearly as good at building advanced queries as Entity Framework, and the symptoms you're describing might be an indication that it's actually doing multiple database round-trips instead of a join. I'd recommend logging the query that you're producing (prior to the .SingleOrDefault()) either by calling .ToString() on the query or by executing your query in LINQPad and clicking on the SQL tab. That might give you a clue as to why the query is misbehaving.

There seems to be a one-to-one relation between Orders and OrderInfos: every Order has exactly one OrderInfo, and every OrderInfo is the info of exactly one Order, namely the Order that the foreign key OrderId refers to.
On the other hand, there seems to be a one-to-many relation between Orders and ShipRates. Every Order has zero or more ShipRates, every ShipRate is a ShipRate of exactly one Order, namely the Order that the foreign key OrderId refers to.
You want several properties of "Orders, each Order with its one and only OrderInfo and its zero or more ShipRates"
Whenever you have a one-to-many relation, and you want "items with their zero or more sub-items", like Schools with their Students, Customers with their Orders, or in your case: Orders with their ShipRates, consider to use one of the overloads of Queryable.GroupJoin
In the other direction: if you want an item with its one and only other item that the foreign key refers to, like Student with the School he attends, Order with the Customer who created the Order, or Order with its one and only OrderInfo, use Queryable.Join.
I mostly use the overload of GroupJoin that has a parameter resultSelector, so I can select exactly what properties I want.
int orderId = ...
var ordersWithShipRates = dbContext.Orders.GroupJoin(dbContext.ShipRates,
order => order.Id, // from every Order take the primary key
shipRate => shipRate.OrderId, // from every ShipRate take the foreign key to Order
// parameter resultSelector: from every Order, with its zero or more ShipRates
// make one new
(order, shipRatesOfThisOrder) => new
{
// Select the Order properties that you plan to use:
Id = order.Id,
Date = order.Date,
...
ShipRates = shipRatesOfThisOrder.Select(shipRate => new
{
// Select the ShipRate properties that you plan to use:
Id = shipRate.Id,
Value = shipRate.Value,
...
})
.ToList(),
// A simple join to get the one and only OrderInfo
OrderInfo = dbContext.OrderInfos.Where(orderInfo => orderInfo.Id == order.Id)
.Select(orderInfo => new
{
// Select the orderInfo properties that you plan to use
Name = orderInfo.Name,
...
})
.FirstOrDefault(),
});

Related

LINQ Equivalent of and SQL query with two inner joins and a left join

I would be grateful for help with a LINQ equivalent of the following SQL Query (which works). Below this SQL query I give some description of my simple data base and the problem I want to solve.
Select a.Name, a.OrderID,
b.ProductIDFirst, c1.productName ProductNameFirst,
b.ProductIDSecond , c2.productName ProductNameSecond
from Customers a
INNER JOIN ORDERS b ON a.OrderID = b.OrderID
left join products c1 on b.productidfirst = c1.productid
left join products c2 on b.ProductIDSecond = c2.productid
Background information on the database structure:
I have a simple SQL Server Database with three Tables named Products, Orders and Customers.
The business model is such that each order can have only two products (not more).
The Orders table has two foreign keys, though they both come from the Products table. These Foreign Key field Names in the Orders Table are ProductIDFirst and ProductIDSecond. These two Foreign Keys in the orders table correspond to two products that each order can have. Customers table has one Foreign Key which comes from the Orders Table.
Now I need help with an LINQ query that will return me all customers such that I get five fields - CustomerName, OrderID and Names of each of the two products that match the OrderID in the customer product.
Your links are non-existent, so here's a best attempt without seeing anything.
Assuming you have the following containers (you'll need to change them for your scenario):
var customers = new List<Customer>();
var orders = new List<Order>();
var products = new List<Product>();
You can do the following:
var query =
from a in customers
join b in orders
on a.OrderId equals b.OrderId
join c1 in products
on b.ProductIdFirst equals c1.ProductId into c1a
join c2 in products
on b.ProductIdSecond equals c2.ProductId into c2a
from p1 in c1a.DefaultIfEmpty()
from p2 in c2a.DefaultIfEmpty()
select new
{
Name = a.Name,
OrderId = a.OrderId,
ProductIdFirst = p1 == null ? null : p1.ProductIdFirst,
ProductNameFirst = p1 == null ? null : p1.ProductNameFirst,
ProductIdSecond = p2 == null ? null : p1.ProductIdSecond,
ProductNameSecond = p2 == null ? null : p1.ProductNameSecond,
};
In short, where you want a left join, project the join into something else (e.g. c1a, c2a) then call from on them using DefaultIfEmpty() which will set null when no matching item exists on the right-hand-side.

How to return values from query when joining multiple tables in Linq?

I have a Linq queries that have tables join and couple of tables inner join together. Sometimes I got an error from the query when table is empty. What I trying to do is I am tryting to get a value from table even if other table is empty.
Thanks in Advance.
You need to do left join
Assuming left join between customer and order table.
var query =
from customer in dc.Customers
from order
in dc.Orders
.Where(o => customer.CustomerId == o.CustomerId)
.DefaultIfEmpty()
select new { Customer = customer, Order = order }
Also refer below link
http://forums.asp.net/t/1792428.aspx/1

Linq query, return distinct on single field & returning subset of data

I have a Linq query that returns three data elements.
var billingDateResults = from s in Subscriptions
.Where(s => (s.ProductCode.Contains("myCode")))
select { g.ID, BillingDate =s.BILL_THRU, s.ProductCode};
I would like to do distinct type of query on this to limit the results to one record per ID.
var billingDateResults = from s in Subscriptions
.Where(s => (s.ProductCode.Contains("myCode")))
group s by s.ID into g
select g.FirstOrDefault();
This works but now returns all of the fields in the records and I would like to minimize the amount of data by limiting the results to only the 3 fields in the first example.
What is a good way to do this?
TIA
Group by those three fields then.
var billingDateResults =
from s in Subscriptions
where s.ProductCode.Contains("myCode")
group new
{
g.ID,
BillingDate = s.BILL_THRU,
s.ProductCode
} by s.ID into g
select g.First(); // FirstOrDefault is not necessary, the groups will be non-empty

Linq query that results in one field in parent entity being a collection of child entities

I am trying to write a Linq query that results in a parent entity with one of the fields being a collection of entities of its related children. For example, I have a collection of all customers entities and a collection of all orders entities. The orders entities have a field called customerPK which contains the link to the related parent customer entity. I want to create a Linq query that joins the two collections and results in all the fields of the customer entity plus an additional field which is the collection object of all the related order entities for that specific customer entity.
Hopefully this should do the trick;
EDIT: Code updated to perform a Left Outer Join, based on this example; http://smehrozalam.wordpress.com/2009/06/10/c-left-outer-joins-with-linq/. This now includes Customers who have no orders.
var query = from c in customers
join o in orders on c.ID equals o.CustomerPK into joined
from j in joined.DefaultIfEmpty()
group j by c into g
select new { Customer = g.Key, Orders = g.Where(x => x != null) };
Note, the use of Where on the selection of the Orders grouping is so that null orders are filtered out at this point, instead of ending up with a grouping containing a single null Order for customers who don't have orders.
Then some example usage;
foreach (var result in query)
{
Console.WriteLine("{0} (ID={1})", result.Customer.Name, result.Customer.ID);
foreach (var order in result.Orders)
{
Console.WriteLine(order.Description);
}
}
This example results in an object with two fields, the Customer and then a group of related Orders, but there's no reason why you can't select the individual fields of your customer object in the query as you specified in your post.

LINQ: Inner join to the First row in a sub query?

I have two classes, basically one holds Members and the other Sessions.
They are joined together with a common field called "name". There is one member but can be many Sessions.
So if I do a standard join I get back 1 member and many sessions. I just want to get back the first row of sessions.
The session has field called SessioEndTime. So I need to order by DESC on this to pick out the first record.
This is my linq; I have returns too many. I think I need a subquery but I am a little confused.
var sessions = from m in this.members
join s in this.sessions
on m.Name equals s.Name
select new { MemberName = m.Name, SessionTime = s.SessioEndTime};
Edit
To make it clear, imagine I have five members, each member has NUMEROUS sessions. I just wish to receive my five members but with only one session each, that session is the LATEST session which can be got from the SessioEndTime.
Try this:
var sessions =
from m in this.members
join s in
(
from se in this.sessions
group se by se.Name into g
select new {Name = g.Key, SessioEndTime = g.Max(a=>a.SessioEndTime)}
)
on m.Name equals s.Name
select new { MemberName = m.Name, SessionTime = s.SessioEndTime}

Resources