LINQ to pull back object graph - linq

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()
});

Related

How to use condition in LINQ take() method

How to join two tables and get the records based on the class B variable values
Class A
{
RecordID int {get;set;}
Name string {get;set;}
}
Class B
{
Name string {get;set;}
DataToPick int {get;set;}
}
My inputs are
Class A
---------------------
RecordID name
1 Sathish
2 Sathish
3 Kumar
4 Kumar
5 Ajay
Class B
--------------------------------------
Name DataToPick
Sathish 1
kumar 2
Now i want to join two tables and get the values from Class A i.e 3 records, based on the Class B variable (Datatopick)
Please help to write it in LINQ like below
var qry= (from c in classA
join d in ClassB on c.name equals d.name
select c).take(d.datatopick)
You can group ClassA and then select the DataToPick from the other list.
var result =
classA.GroupBy(a => a.Name)
.SelectMany(g => g.Take(classB.Where(b => b.Name == g.Key)
.Select(b => b.DataToPick)
.DefaultIfEmpty(0)
.Single()));
Even better join first and the take:
var result = classA.Join(classB, a => a.Name, b => b.Name, (a, b) => new {a, b.DataToPick})
.GroupBy(arg => new {arg.a.Name, arg.DataToPick})
.SelectMany(g => g.Select(arg => arg.a).Take(g.Key.DataToPick));
You could join classA and classB, group by Name, DataToPick and use Take like that
(from a in listA
join b in listB on a.Name equals b.Name
group new { a, b } by new { a.Name, b.DataToPick } into gr
select new
{
Name = gr.Key.Name,
ListA = gr.Select(x=>x.a).Take(gr.Key.DataToPick)
}
).SelectMany(x => x.ListA).ToList();

Join in Linq query not working

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
};
}

Can you make this Many to Many script more dynamic?

This is just my simple way of making a many to many relationship, but i would like something that makes it easy to create many to many, I have wished i could make a method return type anonymous, that would open some doors. But i am noot good with anonymous types, Yet!
Hope you can help !
What do you guys do when you need
private class Ids
{
public int Id;
}
.
...
. And in some method:
IList<Ids> objIds = new List<Ids>();
var q = from c in dt.FK_Tags_Blogs
where c.BlogId == someId
select c.TagId;
foreach (var item in q)
{
objIds.Add(new Ids { Id = item });
}
var w = from c in objIds
join p in dt.Tags on c.Id equals p.Id
select p;
David B: I Used your code like this:
public static IQueryable<Tag> printTags(int id)
{
DataClassesDataContext dt = new DataClassesDataContext();
return
from b in dt.Blogs
where b.Id == id
from xr in b.FK_Tags_Blogs
select xr.Tag;
}
Is that stupid?
What I do is:
//linq to objects
IEnumerable<Tag> tags =
from c in dt.FK_Tags_Blogs
where c.BlogId == someId
join p in dt.Tags on c.TagId equals p.Id
select p;
//linq to sql with association properties
IQueryable<Tag> tagsQuery =
from b in dc.Blogs
where b.BlogId == someId
from xr in b.FK_Tags_Blogs
select xr.Tag;

How to return specific list result from linq to entities

Hi Experts
I currenly write this code:
public IList<TResult> GetCustomQuery<TResult>(int orderID, Func<Order, TResult> selector)
{
using(RepositoryDataContext = new NorthwindEntities())
{
IList<TResult> res = (from od in RepositoryDataContext.Order_Details
join o in RepositoryDataContext.Orders
on od.OrderID equals o.OrderID
join p in RepositoryDataContext.Products
on od.ProductID equals p.ProductID
join c in RepositoryDataContext.Customers
on o.CustomerID equals c.CustomerID
where o.OrderID > orderID
select new
{
o.OrderID,
od.UnitPrice,
od.Quantity,
p.ProductName,
c.CompanyName
}).Select<Order, TResult>(selector).ToList();
}
}
I want to return result of my linq to entities in specific format(TResult).
writing this code using Lambda Expression is hard because of joins.
this line has exception :Select < Order, TResult > (selector)
how I can Fix that?
thanks
Instead of creating an Anonymous type, create a POCO or in your case an Order
select new Order()
{
OrderProperty1 = o.OrderID,
OrderProperty2 = od.UnitPrice,
OrderProperty3 = od.Quantity,
OrderProperty4 = p.ProductName,
OrderProperty5 = c.CompanyName
}).Select<Order, TResult>(selector).ToList();
In your second last line: Try to call ToList() before the Select(). Like this:
}).ToList().Select<Order, TResult>(selector);
Is it possible Iterate "res" and return a list of TResult?

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)
});

Resources