Join in Linq query not working - linq

Now I have to sum the results by grouping the customers.
I have made a query but It is not working at all. Can anybody help me? Here is my query:
![var query=from p in context.Delivery
join o in
(from o1 in context.OrderTables select o1)
on p.OrderID equals o.OrderID
into go from po in go.DefaultIfEmpty()
join d in (from d1 in context.Diagram select d1)
on po.DiagramID equals d.DiagramID into gd
from pd in gd.Distinct()
group pd by pd.CustomerID into groupCustomer
join cu in context.CustomerCompanyTables
on groupCustomer.Key equals cu.CustomerID
select new { cu.CompanyName, SumNoTax = p.Sum(t => t.OrderID!=0 ? p.Price:d.Price)};][2]

As I noted in the comments your query seems massively over complicated:
The use of
…
join x in (from z in context.Xx select z) on …
rather than just:
…
join x in context.Xx on …
Those checks for non-exitant data (go.DefaultIfEmpty()): the form
of join you are using is an inner join: data will be returned
only when matching objects exist for both sides of the condition.
But in the end, your problem is refering back to the original collections
in the final select clause rather than to the result of the group by.
And of course there is no SellingPrice in PdtDeliveryTable also
used in that final select clause.
My approach with queries like this is to build things up incrementally,
ensuring I understand what I am doing with each step.
So the first step is to do the join. For this purpose I've defined
a slightly simpler structure to try and keep things clear (see the
bottom of this answer for definitions) and some test data. I'm using
LINQ to Objects, but the synatax and semantics of the LINQ operators
is the same (and it saves creating a more complex project & database).
A customer has multiple orders, each with a single Sku (stock control
unit—a product). The order can optionally override the default price
of the sku (hence using a Nullable<decimal>). There is also some
test data.
The first step is to check I'm doing the join right, and check
I'm handling that price override:
var ctx = GetTestData();
var query = from c in ctx.Customers
join o in ctx.Orders on c.CustomerId equals o.CustomerId
join s in ctx.Skus on o.SkuId equals s.SkuId
select new { Customer = c, Order = o, Sku = s };
Console.WriteLine("CustId\tCust\t\tOrder\tSku\tPaid");
foreach (var v in query) {
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}",
v.Customer.CustomerId,
v.Customer.Name,
v.Order.OrderId,
v.Sku.SkuId,
v.Order.SpecificPrice ?? v.Sku.DefaultPrice);
}
which results in
CustId Cust Order Sku Paid
1 Acme Corp 1 1 10.0
1 Acme Corp 2 2 15.0
1 Acme Corp 3 3 30.0
2 Beta Corp 4 1 9.99
Note the only data is where there are matching objects of each type, so
there is nothing from the third customer (has no orders) and only one row
from the second customer (doesn't have any orders for other SKUs): there
are no null objects to remove.
The second step is to perform the grouping. This will result in
a rather different data structure something like:
class AnonOfGroup : IEnumerable<AnonOfRow> {
KeyType Key;
}
where AnonOfRow is the type of whatever you are grouping.
Thus:
var query = from c in ctx.Customers
join o in ctx.Orders on c.CustomerId equals o.CustomerId
join s in ctx.Skus on o.SkuId equals s.SkuId
select new { Customer = c, Order = o, Sku = s } into joinRes
group joinRes by joinRes.Customer.CustomerId into g
select g;
has, using the above terms the type of Key being the type of CustomerId and
AnonOfRow being the type of joinRes which is the type of the first
select clause.
This can be shown with a double loop:
Outer over the each different group (with the same key)
Inner over each object in each group (created by the first select
clause)
so:
Console.WriteLine("Customer");
Console.WriteLine("\tOrder\tSku\tPrice");
foreach (var grp in query) {
Console.WriteLine("{0}: {1}", grp.Key, grp.First().Customer.Name);
foreach (var row in grp) {
Console.WriteLine("\t{0}\t{1}\t{2}",
row.Order.OrderId,
row.Sku.SkuId,
row.Order.SpecificPrice ?? row.Sku.DefaultPrice);
}
}
gives:
Customer
Order Sku Price
1: Acme Corp
1 1 10.0
2 2 15.0
3 3 30.0
2: Beta Corp
4 1 9.99
Note also that I'm alble to access the "inner data" from the outer loop
by performing some enumeration (in this case I know the customer is
the same in each inner object so I'll use the first). This is safe
because the underlying join is an inner join.
The Final Step is to sum over each order within the group. This can
be done for each group in the final select cluase. This will be processed
once for each group, but within that clause we can aggregate
over the rows in that group:
var query = from c in ctx.Customers
join o in ctx.Orders on c.CustomerId equals o.CustomerId
join s in ctx.Skus on o.SkuId equals s.SkuId
select new { Customer = c, Order = o, Sku = s } into joinRes
group joinRes by joinRes.Customer.CustomerId into g
select new {
CustomerId = g.Key,
CustomerName = g.First().Customer.Name,
TotalPrice = g.Sum(r => r.Order.SpecificPrice ?? r.Sku.DefaultPrice)
};
Console.WriteLine("Cust\tName\t\tTotal");
foreach (var row in query) {
Console.WriteLine("{0}\t{1}\t{2}", row.CustomerId, row.CustomerName, row.TotalPrice);
}
In this case the aggregation changes the list of lists into a (flat)
list, so only needing a single loop to see all the data. The use of
the null check within the summation means that there are no nulls:
Cust Name Total
1 Acme Corp 55.0
2 Beta Corp 9.99
which is clearly correct for the input data.
Your Solution should just be a matter of substituting your types, adding the fourth
as an extra join, and adjusting to the slightly different types.
class Customer {
public int CustomerId;
public string Name;
}
class Sku {
public int SkuId;
public decimal DefaultPrice;
}
class Order {
public int OrderId;
public int CustomerId;
public int SkuId;
public decimal? SpecificPrice;
}
class Context {
public List<Customer> Customers;
public List<Sku> Skus;
public List<Order> Orders;
}
static Context GetTestData() {
var customers = new List<Customer> {
new Customer { CustomerId = 1, Name = "Acme Corp" },
new Customer { CustomerId = 2, Name = "Beta Corp" },
new Customer { CustomerId = 3, Name = "Gamma Corp" }
};
var skus = new List<Sku> {
new Sku { SkuId = 1, DefaultPrice = 10.0m },
new Sku { SkuId = 2, DefaultPrice = 20.0m },
new Sku { SkuId = 3, DefaultPrice = 30.0m }
};
var orders = new List<Order> {
new Order { OrderId = 1, CustomerId = 1, SkuId = 1 },
new Order { OrderId = 2, CustomerId = 1, SkuId = 2, SpecificPrice = 15.0m },
new Order { OrderId = 3, CustomerId = 1, SkuId = 3 },
new Order { OrderId = 4, CustomerId = 2, SkuId = 1, SpecificPrice = 9.99m }
};
return new Context {
Customers = customers,
Skus = skus,
Orders = orders
};
}

Related

Join table on multiple conditions with LINQ, always show all results of primary key and coalesce nulls

Essentially I'm trying to replicate this query, which works beautifully as a direct SQL query:
SELECT *
FROM Products p
LEFT OUTER
JOIN EventProducts ep ON ep.Product_index = p.[index]
AND COALESCE(ep.Event_index,'3') = '3'
I have two relevant tables:
Products: has index and other information
EventProducts: has index, Product.index, and Event.index and other information including sold, allocated, etc.
In my application view, the Event is already selected and its index will have a constant value. I want to select ALL products, and join the EventProduct data if it has an entry in the database then populate that information into a data grid.
If a Product has no associated EventProduct, it should return an object like:
{
index = 1,
name = productName,
sold = 0,
allocated = 0
...
}
but if there is an associated EventProduct entry, return the object
{
index = 2,
name = product2Name
sold = 10
allocated = 15
...
}
This is my LINQ query right now:
var eventProducts = dbContext.Products
.Join(dbContext.EventProducts,
product => new { productIndex = product.index, eventIndex = currentEvent.index },
eventProduct => new { productIndex = eventProduct.Product.index, eventIndex = eventProduct.Event.index },
(product, eventProduct) => new
{
Product = product,
EventProduct = eventProduct
});
This query always returns 0 objects, where it should return 8 (for each of my products) even though my EventProducts table is currently empty.
Join performs inner join, to perform left join you can either switch to query syntax looking something like this:
var query = from product in dbContext.Products
join eventProduct in dbContext.EventProducts.Where(ep => ep.Event_index == currentEvent.index)
on product.index equals eventProduct.Product_index into gj
from sub in gj.DefaultIfEmpty()
select new { Product, EventProduct = sub };
Or use GroupJoin as in this answer.
Or, if Product entity has correctly set up navigation you can try to use Include with Where clause looking like this(not sure it will work though, can't check ATM):
var productWithEventProducts = dbContext.Products
.Include(p => p.EventProducts)
.Where(p => p.EventProducts.Any(ep => ep.Event_index == currentEvent.index)
|| !p.EventProducts.Any())
.ToList()

Linq Sql Query Error for Get value

Here is my linq query for get Qty and Number from first collection Qty - Second Collection Qty and first collection Number - Second Collection Number, Some times First collection RM not contain second colection
var summary = (from r in firstCollection
join s in secondCollection
on new { r.RM, r.Size } equals new { s.RM, s.Size }
group new { r, s } by new { RM = r.RM, Size = s.Size, Qty = (r.Qty - s.Qty), Number = (r.Number - s.Number) }
into grp
select new
{
RM = grp.Key.RM,
RMsize = grp.Key.Size,
Qty = grp.Key.Qty,
Number = grp.Key.Number
}).ToList();
there is an error like
Additional information: A group by expression can only contain
non-constant scalars that are comparable by the server. The expression
with type 'Manufacturing.DataAccess.tbl_RawMaterial' is not
comparable.
How can i solve this ?
You can project to anonymous type first and then do a grouping. Try this:
var summary = (from r in firstCollection
join s in secondCollection
on new { r.RM, r.Size } equals new { s.RM, s.Size }
select new
{
RM = r.RM,
Size = s.Size,
Qty = (r.Qty - s.Qty),
Number = (r.Number - s.Number)
} into tmp
group tmp by new
{
RM,
Size,
Qty,
Number
} into grp
select new
{
RM = grp.Key.RM,
RMsize = grp.Key.Size,
Qty = grp.Key.Qty,
Number = grp.Key.Number
}).ToList();
Looks like the problem is RM member which I assume is some navigation property of type Manufacturing.DataAccess.tbl_RawMaterial. As the exception message states, you can only group by simple properties.
Let say your entity Manufacturing.DataAccess.tbl_RawMaterial primary key is called Id (you can replace it with the actual name). Then the query could be something like this
var summary =
(from r in firstCollection
join s in secondCollection
on new { r.RM.Id, r.Size } equals new { s.RM.Id, s.Size }
group new { r, s }
by new { Id = r.RM.Id, Size = s.Size, Qty = (r.Qty - s.Qty), Number = (r.Number - s.Number) }
into grp
select new
{
RM = grp.FirstOrDefault(e => e.r),
RMsize = grp.Key.Size,
Qty = grp.Key.Qty,
Number = grp.Key.Number
}).ToList();

Doing 2 Left Joins on the same table with one call

I have the following tables:
Users:
userId, userFirstName, userLastName.
holdBilling:
bEntityID, CarrierOID, PayerOID, holdTYPE, createUserID.
carrier:
carrierOID, carrierName.
payer:
payerOID, payerName.
I want the code to save in a new entity
holdBilling => new
{
FirstName, LastName, CarrierName, PayerName
}
One of these Entities has either a payer or a carrier value (cannot have both). Basically i want to make 2 left joins on the same table with one call. This would be the SQL query that would work for me.
SELECT TOP 1000 [ID]
,[bEntityID]
,c.carrierID
,c.carrierName
,p.payerID
,p.payerName
,[holdType] ( this is "C" for carrier and "P" for payer )
FROM .[dbo].[holdBilling] hb
left join dbo.payer p on hb.payerID = p.payerID
left join dbo.carrier c on hb.carrierID = c.carrierID
where [bEntityID] = 378
The temporary solution I've found is getting a list of all Carriers
var listC = (from hold in holdBilling
join u in Users on hold.createUserID equals u.userID
join c in carrier.DefaultIfEmpty() on hold.carrierID equals c.carrierID
select new
{
Elem = hold,
FName = u.userFirstName,
LName = u.userLastName,
Carrier = c.carrierName,
Payer = ""
}).ToList();
and one for all payers
select new
{
Elem = hold,
FName = u.userFirstName,
LName = u.userLastName,
Carrier = "",
Payer = p.payerName
}).ToList();
and merging the two,I'm confident there has to be a solution for doing both in one query.
Something like this maybe:
var listC = (
from hb in holdBilling
from p in payer.Where(a=>a.payerID==hb.payerID).DefaultIfEmpty()
from c in carrier.Where(a=>a.carrierID=hb.carrierID).DefaultIfEmpty()
where hb.bEntityID==378
select new
{
hb.bEntityID,
c.carrierID,
c.carrierName,
p.payerID,
p.payerName,
holdType=(payer==null?"P":"C")
}
).Take(1000)
.ToList();
You need to use DefaultIfEmpty to do a left join
var listC = (from hold in holdBilling
from u in Users.Where(x => hold.createUserID == x.userID).DefaultIfEmpty()
from c in carrier.Where(x => hold.carrierID == x.carrierID).DefaultIfEmpty()
select new
{
Elem = hold,
FName = u.userFirstName,
LName = u.userLastName,
Carrier = c.carrierName,
Payer = ""
}).ToList();

LINQ Join With Multiple Where Clause

I am struggling once again so any help would be gratefully received.
I have the following LINQ that pulls back a list of data:
public static List<tblWeight> GetWeights(string memberid, string locationid, string buyer, string subcategory, string product)
{
MyEntity getweights = new MyEntity ();
var r = (from p in getweights.tblWeights
where p.MemberId == memberid &&
p.LocationId == locationid
select p);
if (buyer != "Not Specified")
r = r.Where(p => p.UnitUserField1 == buyer);
if (subcategory != "Not Specified")
r = r.Where(p => p.UnitUserField2 == subcategory);
if (product != "Not Specified")
r = r.Where(p => p.IDDesc == product);
return r.ToList();
}
Lovely!
What I would like to do now is based upon this result set and the unit IDs (IDDesc), I then go to tblPurchase, pull back a few columns from tblPurchases and group the columns.
So for example, we have tblWeight looking like so:
MemberID LocationID Buyer SubCategory IDDesc
1 1 Cat1 Sub1 ab
1 1 Cat1 Sub1 abc
1 1 Cat1 Sub2 abcd
The user makes a search for Sub1 in subcategory and the above LINQ does the trick and pulls back the first two rows from above. Fine.
What I need the LINQ to do now is to go to tblPurchases:
MemberID LocationID IDDesc SupplierID SupplierStatus
1 1 ab Sup1 Live
1 1 abc Sup1 Live
1 1 abcd Sup2 Dead
And then pull back the following result so it is joined on MemberID, LocationID and IDDesc but just selects tblPurchases.
Sup1 Live (or all columns in tblPurchases, just grouped/distinct)
I have tried to add in a join and on but no matter how many different variations, I still come across the red squiggle of doom!!!
If anyone can help, beer/kiss is on offer again.
The following LINQ query should do what you want:
var result = from w in tblWeight
where w.SubCategory == "Sub1"
join p in tblPurchases on
new { w.MemberID, w.LocationID, w.IDDesc } equals
new { p.MemberID, p.LocationID, p.IDDesc }
group p by new { p.SupplierID, p.SupplierStatus } into pg
select pg.Key;
The variable result is a list containing tuples of SupplierID and SupplierStatus.
If you also want to put the conditional parts in there, it gets a little more complicated. Here's how to do it:
var weights = from w in tblWeight
select w;
weights = weights.Where(w => w.SubCategory == "Sub1");
// You can add additional where clauses here.
// Now join with tblPurchases and group by SupplierID and SupplierStatus.
var result =
weights.Join(tblPurchases,
w => new { w.MemberID, w.LocationID, w.IDDesc },
p => new { p.MemberID, p.LocationID, p.IDDesc },
(w, p) => p)
.GroupBy(p => new { p.SupplierID, p.SupplierStatus },
(k, ps) => new
{
k.SupplierID,
k.SupplierStatus,
TotalQty = ps.Sum(p => p.PurchaseQty)
});

LINQ to pull back object graph

Is it possible to group by more than one group in LINQ?
For instance, I have this query (obviously incomplete)
from lxr in LOCATION_XREFs
join l in LOCATIONs on lxr.LOCATION_SKEY equals l.LOCATION_SKEY
join c in COMPANies on l.COMPANY_SKEY equals c.COMPANY_SKEY
join prlx in PeopleRoleLocationXrefs on lxr.LOCATION_XREF_SKEY equals prlx.LOCATION_XREF_SKEY
join p in PEOPLEs on prlx.PEOPLE_SKEY equals p.PEOPLE_SKEY
join pr in PeopleRoles on prlx.PeopleRoleKey equals pr.PeopleRoleKey
... and I'd like to get back a bunch of companies that have a bunch of locations that, in turn, have a bunch of people.
If you're looking to group by multiple parameters, that is possible:
from lxr in LOCATION_XREFs
join l in LOCATIONs on lxr.LOCATION_SKEY equals l.LOCATION_SKEY
join c in COMPANies on l.COMPANY_SKEY equals c.COMPANY_SKEY
join prlx in PeopleRoleLocationXrefs on lxr.LOCATION_XREF_SKEY equals prlx.LOCATION_XREF_SKEY
join p in PEOPLEs on prlx.PEOPLE_SKEY equals p.PEOPLE_SKEY
join pr in PeopleRoles on prlx.PeopleRoleKey equals pr.PeopleRoleKey
group c by new { c.COMPANY_SKEY, l.LOCATION_SKEY} into myGroup
select new
{
myGroup.Key.COMPANY_SKEY,
myGroup.Key.LOCATION_SKEY,
myGroup.Count()
}
I ultimately used the following code to achieve what I wanted. Interesting note: notice that I wanted the Roles flattened out and concatenated with commas, so I did this using a wrapper accessor that uses the Aggregate operator. Apparently the Aggregate operator is not suuported within a LINQ query, as you get "the query operator "Aggregate" is not supported" otherwise I would have done it right there inline like this Roles = new List<string>(g.Select(u => u.pr.RoleName)).Aggregate((a, b) => a + ", " + b)
public class DAOPerson
{
public string Name { get; set; }
public int PeopleSkey { get; set; }
public List<string> RolesCollection { get; set; }
public string Roles
{
get { return RolesCollection.Aggregate((a, b) => a + ", " + b); }
}
}
IQueryable<DAOLocation> gridData;
gridData = (from lxr in db.LOCATION_XREFs
join l in db.LOCATIONs on lxr.LOCATION_SKEY equals l.LOCATION_SKEY
join c in db.COMPANies on l.COMPANY_SKEY equals c.COMPANY_SKEY
where lxr.DEPARTMENT_NUMBER == Department.BINDING_AUTHORITY_KEY
&& lxr.BRANCH_NUMBER == Branch.ATLANTAKEY
orderby c.NAME, l.INTERNAL_NAME ascending
select new DAOLocation
{
CompanyName = c.NAME,
CompanySkey = c.COMPANY_SKEY,
LocationName = l.INTERNAL_NAME,
LocationSkey = l.LOCATION_SKEY,
Persons = (from prlx in db.PeopleRoleLocationXrefs
join lxr2 in db.LOCATION_XREFs on prlx.LOCATION_XREF_SKEY equals lxr.LOCATION_XREF_SKEY
join p in db.PEOPLEs on prlx.PEOPLE_SKEY equals p.PEOPLE_SKEY
join pr in db.PeopleRoles on prlx.PeopleRoleKey equals pr.PeopleRoleKey
where lxr2.LOCATION_SKEY == l.LOCATION_SKEY
group new { p, pr } by p.PEOPLE_SKEY into g
select new DAOPerson
{
Name = g.First().p.LAST_NAME,
PeopleSkey = g.First().p.PEOPLE_SKEY,
RolesCollection = new List<string>(g.Select(u => u.pr.RoleName))
}).ToList()
});

Resources