I am trying out C# LINQ Joins from this msdn page.
I am not able to apply order by on inner join even though it works with group join.
The data on which queries are run are:
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}
class Category
{
public string Name { get; set; }
public int ID { get; set; }
}
// Specify the first data source.
static List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001},
new Category(){ Name="Condiments", ID=002},
new Category(){ Name="Vegetables", ID=003},
new Category() { Name="Grains", ID=004},
new Category() { Name="Fruit", ID=005}
};
// Specify the second data source.
static List<Product> products = new List<Product>()
{
new Product{Name="Cola", CategoryID=001},
new Product{Name="Tea", CategoryID=001},
new Product{Name="Mustard", CategoryID=002},
new Product{Name="Pickles", CategoryID=002},
new Product{Name="Carrots", CategoryID=003},
new Product{Name="Bok Choy", CategoryID=003},
new Product{Name="Peaches", CategoryID=005},
new Product{Name="Melons", CategoryID=005},
};
The desired output is (list order by categories in brackets and then by product) :
Cola(Beverages)
Tea(Beverages)
Mustard(Condiments)
Pickles(Condiments)
Melons(Fruit)
Peaches(Fruit)
Bok Choy(Vegetables)
Carrots(Vegetables)
I was able to produce this output using group-join with orderby and 2nd from-select to deform group hierarchy and produce plain list as follows:
var listGroupJoinOrderBy =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby category.Name, prod.Name //first order by category name then product name
select
new
{
Category = category.Name,
Product = prod.Name
};
But then I am unable to produce this same output with orderby applied to inner join (that is group-join without into clause). I tried following variants:
Variant #1
var innerJoinOrderBy =
from category in categories
orderby category.Name //orderby category name
join product in products on category.ID equals product.CategoryID
orderby product.Name //orderby product name
select
new
{
Category = category.Name,
Product = product.Name
};
Variant #2
var innerJoinOrderBy =
from category in categories
join product in products on category.ID equals product.CategoryID
orderby category.Name, product.Name //orderby category first and then by product name
select
new
{
Category = category.Name,
Product = product.Name
};
However both variants give the same output as if no orderby is used, and produces the following output:
Cola (Beverages)
Tea (Beverages)
Mustard (Condiments)
Pickles (Condiments)
Carrots (Vegetables)
Bok Choy (Vegetables)
Peaches (Fruit)
Melons (Fruit)
Q. How can I produce the desired output with inner-join and orderby?
Anyway, to print the query result one can use following foreach (just change the query variable name) :
foreach (var product in simpleInnerJoin)
{
Console.WriteLine(product.Product + " (" + product.Category + ")");
}
Your second option should work fine
var innerJoinOrderBy =
from c in categories
join p in products on c.ID equals p.CategoryID
orderby c.Name, p.Name
select new {
Category = c.Name,
Product = p.Name
};
Output:
[
{ Product="Cola", Category="Beverages" },
{ Product="Tea", Category="Beverages" },
{ Product="Mustard", Category="Condiments" },
{ Product="Pickles", Category="Condiments" },
{ Product="Melons", Category="Fruit" },
{ Product="Peaches", Category="Fruit" },
{ Product="Bok Choy", Category="Vegetables" },
{ Product="Carrots", Category="Vegetables" }
]
From your output I see that items go in their original order. Make sure you have applied orderby operator in query.
Take the Orderby out of the main Linq and use
foreach (var product in simpleInnerJoin.Orderby(i=> i.Category.Name).ThenBy(i=>i.Product.Name))
{
Console.WriteLine(product.Product + " (" + product.Category + ")");
}
I think this should give you the output you require.
Related
I'm currently working to add Data to a GridView. The data comes from 2 tables that are on different databases. Currently I am able to populate the first entry, but it does not populate past that. here is the code:
void FillOrder(int inv)
{
var _ord = new OrdersContext();
var _pro = new ProductContext();
var qryOrder = (from o in _ord.OrderDetails
where o.InvNumberId == inv
select new
{
o.ProductID,
o.Quantity
}).ToList();
foreach (var order in qryOrder)
{
int prodID = order.ProductID;
int itemCount = qryOrder.Count;
var qryProducts = (from p in _pro.Products
where p.ProductID == prodID
select new
{
p.ProductID,
p.ProductName
}).ToList();
var results = (from t in qryOrder
join s in qryProducts
on t.ProductID equals prodID
select new
{
t.ProductID,
t.Quantity,
s.ProductName
}).ToList();
OrderItemList.DataSource = results;
OrderItemList.DataBind();
}
}
Can anyone help as to why it's only populating the first entry?
If the number of products involved is relatively small, (and since this query seems to be relate to one invoice, I would think that is true), then you can probably use something like the code below.
This is removing the loop, but the contains method will probably generate a SQL statement something like select ProductID, ProductName from products where productID in (,,,,,,) so may fail if the number of parameters is extremely large.
var _ord = new OrdersContext();
var _pro = new ProductContext();
var qryOrder = (from o in _ord.OrderDetails
where o.InvNumberId == inv
select new
{
o.ProductID,
o.Quantity
}).ToList();
// Get the productIDs
var productIDS = qryOrder.Select(o=>o.ProductID).Distinct().ToList();
// Get the details of the products used.
var qryProducts = (from p in _pro.Products
where productIDS.Contains(p.ProductID)
select new
{
p.ProductID,
p.ProductName
}).ToList();
// Combine the two in memory lists
var results = (from t in qryOrder
join s in qryProducts
on t.ProductID equals s.ProductID
select new
{
t.ProductID,
t.Quantity,
s.ProductName
}).ToList();
OrderItemList.DataSource = results;
OrderItemList.DataBind();
I have two tables.
Order:
ID
ProductID
DeliveryDate
Product:
ID
Name
Description
I want to get the list of all product with earliest delivery date.
I am getting correct data by using following sql.
This gives me list of all products with earliest delivery date.
SELECT
p.Name, p.Description, min(o.DeliveryDate)
FROM Product p
JOIN Order o
On o.ProductID = p.ID
Group By p.ID;
I have tried to write it using Linq but something is missing.
I am not able to identify how can I write this query.
I have tried out relative stack overflow solutions but not helped in my case.
My Linq is :
await (from p in dbContext.Product
join o in dbContext.Order
on o.ProductID equals p.ID
select new
{
p.ID,
p.Name,
p.Description,
o.DeliveryDate
}).GroupBy(g => g.ID).ToListAsync();
It gives me data after join and group by. Where should I put Min in the Linq to get the Earliest DeliveryDate for the Product?
I assume you have a navigation property Product.Orders. If not, it's highly recommended to add it to your code. Having that property, this will achieve what you want:
from p in dbContext.Product
select new
{
p.ID,
p.Name,
p.Description,
MinDeliveryDate = (DateTime?)o.Orders.Min(o => o.DeliveryDate)
}
The cast to DateTime? is to prevent exceptions when products don't have orders.
If for some reason you don't have the navigation property and really can't add it at the moment you can use:
from p in dbContext.Product
select new
{
p.ID,
p.Name,
p.Description,
MinDeliveryDate = (DateTime?)dbContext.Order
.Where(o => o.ProductID == p.ID)
.Min(o => o.DeliveryDate)
}
Note that by starting the query at Product and not joining with Order you don't need the grouping any more.
I used some classes to stand in for the database, but does this work?
class Product
{
public int ID;
public string Name;
public string Description;
}
class Order
{
public int ProductID;
public DateTime DeliveryDate;
}
class Program
{
static void Main(string[] args)
{
Product[] proTestData = new Product[]
{
new Product() {ID=1, Name="Widget", Description="Banded bulbous snarfblat"},
new Product() {ID=2, Name="Gadget", Description="Hitchhiker's guide to the galaxy"}
};
Order[] ordTestData = new Order[]
{
new Order() {ProductID=1, DeliveryDate=new DateTime(2017,12,14)},
new Order() {ProductID=1, DeliveryDate=new DateTime(2017,12,20)},
new Order() {ProductID=2, DeliveryDate=new DateTime(2017,12,23)},
new Order() {ProductID=2, DeliveryDate=new DateTime(2017,12,22)},
new Order() {ProductID=2, DeliveryDate=new DateTime(2017,12,21)},
new Order() {ProductID=1, DeliveryDate=new DateTime(2017,12,18)}
};
var rows =
(from p in proTestData
join o in ordTestData
on p.ID equals o.ProductID
group o.DeliveryDate by p into grp
select new {
grp.Key.ID,
grp.Key.Name,
grp.Key.Description,
minDate = grp.Min()}
).ToList();
foreach (var row in rows)
{
Console.WriteLine("{0}: {1}", row.Name, row.minDate);
}
}
}
The output is
Widget: 12/14/2017 12:00:00 AM
Gadget: 12/21/2017 12:00:00 AM
If you're trying to order your groups you can use the "OrderBy" linq method.
Apply the following after the .GroupBy()
.OrderBy(group => group.First().DeliveryDate).ToListAsync();
Say I have a
TableA
string Name
string Description
TableB
string Name
string Value
TableA and TableB are joined by Name. (In theory, i.e. not enforced in DB)
I want to create an object:
public MyObject
{
string Name
string Description
List<string> Values
}
I'm tying to understand how to combine these using LINQ.
var tableA = _oda.GetTableA();
var tableB = _oda.GetTableB();
var model = from a in tableA
join b in tableB on a.NAME equals b.NAME
select new MyObject
{
Name= a.Name,
Description = a.Description,
Values = "<Not sure to get list of tableb.Value>"
};
If it's an inner join, you can use GroupBy after your join:
var tableA = new List<TableA> { new TableA { Name = "1", Description = "D1" }, new TableA { Name = "2", Description = "D2"} };
var tableB = new List<TableB> { new TableB { Name = "1", Value = "V1" }, new TableB { Name = "1", Value = "V2"} };
var result = tableA.Join(tableB, a => a.Name, b => b.Name, (a, b) => new { A = a, B = b})
.GroupBy(k => k.A, e => e.B.Value)
.Select(g => new MyObject
{
Name = g.Key.Name,
Description = g.Key.Description,
Values = g.ToList()
});
foreach (var res in result)
{
Console.WriteLine("Name: {0}, Description: {1}, Value: {2}", res.Name, res.Description, string.Join(", ", res.Values));
}
Didn't try the code, but something like this should work.
var result = from a in TableA
join b in TableB on a.Name equal b.Name
group b.Value by a into g
select new MyObject
{
Name = g.Key.Name,
Description = g.Key.Description,
Values = g.ToList()
}
I have a linq query which is working fine.How can i use group by in this query.I need to group by username and itemid and i should get sum(Amount)(All are in table called Carts)
FoodContext db = new FoodContext();
List<CartListing> fd = (from e in db.FoodItems
join o in db.Carts on e.itemid equals o.itemid
where e.itemid == o.itemid
select new CartListing
{
Itemname =e.itemname,
Amount =o.amount,
Price=(float)(e.price*o.amount),
}).ToList();
CartModel vm = new CartModel { CartListings = fd };
I can't see username anywhere in your code example, but to group by Itemname and sum Amount, you would something like:
var grouped = fd.GroupBy(
cl => cl.Itemname,
(key, group) => new CartListing
{
Itemname = key,
Amount = group.Sum(cl => cl.Amount),
Price = group.Sum(cl => cl.Price)
});
To also group by username, just generate a text key containing both values, for instance delimited by a character you know will be contained in neither.
Use:
var grouped = fd.GroupBy((a => new { a.itemid,a.name }) into grp
select new MyClass
{
MyProperty1=grp.key.itemid,
MyProperty2 =grp.Sum(x=>x.whatever)
}
Public MyClass
{
public string MyProperty1 {get;set;}
public int MyProperty2 {get;set;}
}
This way it won't be anonymous
var sql = #"SELECT
a.id AS `Id`,
a.thing AS `Name`,
b.id AS `CategoryId`,
b.something AS `CategoryName`
FROM ..";
var products = connection.Query<Product, Category, Product>(sql,
(product, category) =>
{
product.Category = category;
return product;
},
splitOn: "CategoryId");
foreach(var p in products)
{
System.Diagnostics.Debug.WriteLine("{0} (#{1}) in {2} (#{3})", p.Name, p.Id, p.Category.Name, p.Category.Id);
}
Results in:
'First (#1) in (#0)'
'Second (#2) in (#0)'
CategoryId and CategoryName has values since the following
var products = connection.Query(sql).Select<dynamic, Product>(x => new Product
{
Id = x.Id,
Name = x.Name,
Category = new Category { Id = x.CategoryId, Name = x.CategoryName }
});
Results in:
'First (#1) in My Category (#10)'
'Second (#2) in My Category (#10)'
I'm connecting to a MySQL database if that has anything to do with it.
The simplest way is to call them all Id (case-insensitive, so just a.id and b.id are fine); then you can use:
public void TestMultiMapWithSplit()
{
var sql = #"select 1 as Id, 'abc' as Name, 2 as Id, 'def' as Name";
var product = connection.Query<Product, Category, Product>(sql,
(prod, cat) =>
{
prod.Category = cat;
return prod;
}).First();
// assertions
product.Id.IsEqualTo(1);
product.Name.IsEqualTo("abc");
product.Category.Id.IsEqualTo(2);
product.Category.Name.IsEqualTo("def");
}
If you can't do that, there is an optional splitOn (string) parameter that takes a comma-separated list of columns to treat at splits.