I've two Tables lets simply say :
Products : ProductID, ProductName
Orders : OrderID,ProductID,Amount,Status
I want to write a LINQ query in C# which would
select ProductName,Sum(Amount) where Status = 1
I am stuck with this simple query :(
class Program
{
class Product
{
public int ProductID { get; set; }
public string ProductName {get; set; }
public Product(int ProductID, string ProductName)
{
this.ProductID = ProductID;
this.ProductName = ProductName;
}
}
class Order
{
public int OrderID { get; set; }
public int ProductID { get; set; }
public decimal Amount { get; set; }
public int Status { get; set; }
public Order(int OrderID, Product product, decimal Amount, int Status)
{
this.OrderID = OrderID;
this.ProductID = product.ProductID;
this.Amount = Amount;
this.Status = Status;
}
}
private static Product[] Products;
private static Order[] Orders;
static void Main(string[] args)
{
Products = new Product[]
{
new Product(1, "Bolt"),
new Product(2, "Nut"),
new Product(3, "Mounting Plate A"),
new Product(4, "Mounting Plate B")
};
Orders = new Order[]
{
new Order(1, Products[0], 1.12M, 0),
new Order(2, Products[1], 0.66M, 1),
new Order(3, Products[2], 4.12M, 0),
new Order(4, Products[0], 1.11M, 1),
new Order(5, Products[1], 0.67M, 1)
};
var results = from p in Products
join o in Orders on p.ProductID equals o.ProductID
where o.Status == 1
group o by p
into orderTotals
select new {ProductName = orderTotals.Key.ProductName, TotalAmount = orderTotals.Sum(o => o.Amount)};
foreach(var result in results)
{
Console.WriteLine("{0}: {1}", result.ProductName, result.TotalAmount);
}
}
}
Output:
Bolt: 1.11
Nut: 1.33
Although not the optimal, but this should work:
var interim =
from o in orders
where o.Status == 1
select new { o.OrderID, o.ProductID, o.Amount, o.Status};
var final =
from p in products
join x in interim on p.ProductID equals x.ProductID into g
select new { p.ProductName, Total = g.Sum(y => y.Amount) };
Related
I have this list
List<Order> OL = new List<Order>()
{
new Order("O-1","P1",200,2),
new Order("O-2","P1",200,3),
new Order("O-3","P1",1000,1),
new Order("O-4","P2",200,2)
};
The Order class :
class Order
{
public string ID { get; set; }
public string Product { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
public int Total { get { return Price * Quantity; } }
public Order(string _ID, string _Product, int _Price, int _Quantity)
{
ID = _ID;
Product = _Product;
Price = _Price;
Quantity = _Quantity;
}
public Order()
{
}
}
So I want to return the name and the counting (Number of times the product repeated in orders) for each product.
I tried :
var P = OL.OrderByDescending(x => x.Product.Count()).Take(2);
MessageBox.Show(P.ElementAt(0).Product);
But just getting the product name, Please any help? and thanks in advance.
How about:
var groupedProducts = OL.GroupBy(o => o.Product)
.Select(g => new { Product = g.Key, Quantity = g.Count() })
.OrderByDescending(p => p.Quantity);
Group by Product then sort by Count()
var P = OL.GroupBy(x => x.Product)
.OrderByDescending(x => x.Count())
.Select(g => new { Product = g.Key, Count = g.Count() });
var products = from P in product.GetAllAsync().Result
join PV in
_repositoryVariation.GetAllAsync().Result on P.Id equals PV.ProductId
into PVLJ
from PV in PVLJ.DefaultIfEmpty()
select new ProductwithVarientsGroupDto
{
Label = P.ProductName,
ProductId = P.Id,
Items = new List<ProdcutVariantsdto>() { new ProdcutVariantsdto { Label = P.ProductName.ToString() + (PV == null ? "" : " (" + PV?.VariantName.ToString() + ")"), ProductId = P.Id, ProdVariantId = PV?.Id > 0 ? PV?.Id : 0 } }
//here i need to add multiple variant records to Items, now it
// showing each item as a separate row data
};
var result = products.ToList();
return result;
The following query will work. After you left joined the data, just switch to client-evaluation (using AsEnumerable() or ToList()) and do the actual grouping with Linq. EF Core will not be able to do a Linq style grouping on its own and the data is being retrieved anyway, so there is no downside doing it client-side:
var products = (from p in context.Products
join v in
context.ProductVariations on p.Id equals v.ProductId
into g
from v in g.DefaultIfEmpty()
select new {product = p, variation = v})
.AsEnumerable() // <-- switch to client-evaluation
.GroupBy(g => g.product, g => g.variation)
.Select(g => new ProductwithVarientsGroupDto
{
Label = g.Key.ProductName,
ProductId = g.Key.Id,
Items = g.Select(v => new ProdcutVariantsdto()
{
Label = g.Key.ProductName + (v == null
? ""
: " (" + v.VariantName.ToString() + ")"),
ProductId = g.Key.Id,
ProdVariantId = v.Id > 0
? v.Id
: 0
}).ToList()
});
As #PanagiotisKanavos already mentioned in the comments, your original code first retrieves all entities from the database and then executes your query on them in memory. If you want to keep doing that, just replace context.Products with product.GetAllAsync().Result and context.ProductVariations with _repositoryVariation.GetAllAsync().Result. You could then also drop the AsEnumerable(), because you are already processing the query client-side.
Here is the fully working sample console project I used for testing:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
}
public class ProductVariation
{
public int Id { get; set; }
public int ProductId { get; set; }
public string VariantName { get; set; }
}
public class ProductwithVarientsGroupDto
{
public string Label { get; set; }
public int ProductId { get; set; }
public List<ProdcutVariantsdto> Items = new List<ProdcutVariantsdto>();
}
public class ProdcutVariantsdto
{
public string Label { get; set; }
public int ProductId { get; set; }
public int? ProdVariantId { get; set; }
}
public class Context : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProductVariation> ProductVariations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63031344")
.UseLoggerFactory(
LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasData(
new Product {Id = 1, ProductName = "Car"},
new Product {Id = 2, ProductName = "Bus"});
modelBuilder.Entity<ProductVariation>().HasData(
new ProductVariation {Id = 1, ProductId = 1, VariantName = "Minivan"},
new ProductVariation {Id = 2, ProductId = 1, VariantName = "Convertible"},
new ProductVariation {Id = 3, ProductId = 2, VariantName = "Public Transportation"},
new ProductVariation {Id = 4, ProductId = 2, VariantName = "Shuttle"});
}
}
internal static class Program
{
private static void Main()
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var products = (from p in context.Products
join v in
context.ProductVariations on p.Id equals v.ProductId
into g
from v in g.DefaultIfEmpty()
select new {product = p, variation = v})
.AsEnumerable()
.GroupBy(g => g.product, g => g.variation)
.Select(g => new ProductwithVarientsGroupDto
{
Label = g.Key.ProductName,
ProductId = g.Key.Id,
Items = g.Select(v => new ProdcutVariantsdto()
{
Label = g.Key.ProductName + (v == null
? ""
: " (" + v.VariantName.ToString() + ")"),
ProductId = g.Key.Id,
ProdVariantId = v.Id > 0
? v.Id
: 0
}).ToList()
});
var result = products.ToList();
Debug.Assert(result.Count == 2);
Debug.Assert(result[0].ProductId == 1);
Debug.Assert(result[0].Items.Count == 2);
Debug.Assert(result[0].Items[0].ProdVariantId == 1);
}
}
}
I have list of OrderInfo which contains Id of Product.
I'd want to load all Orders (that has List<Many2Many> inside)
which has at least one Product with Id from my OrderInfo list
Basically it is nested Any Any
The code that I wrote below as proof of concept works fine, but the problem is that when I try to do the same on EF Core then it is being evaluated on client side.
How can I make it to evaluate properly?
var list_of_details = new List<OrderInfo> {...};
context
.Orders
.Where(o => o.OrdersProducts.Any(p => list_of_details.Any(d => d.ProductId == p.ProductId)));
public class OrderInfo
{
public Guid ProductId { get; set; }
(...)
}
public class Order
{
public List<Many2Many> OrdersProducts = new List<Many2Many>();
(...)
}
public class Product
{
public List<Many2Many> OrdersProducts = new List<Many2Many>();
(...)
}
public class Many2Many
{
public Order Order { get; set; }
public Guid OrderId { get; set; }
public Product Product { get; set; }
public Guid ProductId { get; set; }
}
Here's example in non-ef way, but ugly and just as proof of concept
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public class OrderInfo
{
public int ProductId { get; set; }
}
public class Order
{
public int Id;
public List<Many2Many> OrdersProducts = new List<Many2Many>();
}
public class Product
{
public int Id;
public List<Many2Many> OrdersProducts = new List<Many2Many>();
}
public class Many2Many
{
public Order Order { get; set; }
public int OrderId { get; set; }
public Product Product { get; set; }
public int ProductId { get; set; }
}
public static void Main()
{
Console.WriteLine("List of ids that have to be in 'Order' in order to qualify him");
var list_of_details = Enumerable.Range(0, 5).Select(x => new OrderInfo(){ ProductId = x}).ToList();
foreach (var item in list_of_details)
{
Console.Write(item.ProductId);
}
Console.WriteLine();
// setup
var orders = new List<Order>();
var order = new Order(){Id = 2};
var product = new Product()
{
Id = 3,
};
order.OrdersProducts.Add(new Many2Many()
{
Order = order,
OrderId = order.Id,
Product = product,
ProductId = product.Id
});
var order2 = new Order(){Id = 3};
var product2 = new Product()
{
Id = 4,
};
order2.OrdersProducts.Add(new Many2Many()
{
Order = order2,
OrderId = order2.Id,
Product = product2,
ProductId = product2.Id
});
var order3 = new Order(){Id = 1};
var product3 = new Product()
{
Id = 5,
};
order3.OrdersProducts.Add(new Many2Many()
{
Order = order3,
OrderId = order3.Id,
Product = product3,
ProductId = product3.Id
});
orders.Add(order);
orders.Add(order2);
orders.Add(order3);
Console.WriteLine();
// end setup
foreach (var ord in orders)
{
Console.WriteLine();
Console.WriteLine($"Order Id: {ord.Id}");
Console.Write('\t' + "Product Ids: ");
foreach (var prod in ord.OrdersProducts)
{
Console.Write(prod.ProductId);
}
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("found orders");
foreach (var item in orders.Where(o => o.OrdersProducts.Any(p => list_of_details.Any(d => d.ProductId == p.ProductId))))
{
Console.WriteLine(item.Id);
}
}
}
Output:
List of ids that have to be in 'Order' in order to qualify him
01234
Order Id: 2
Products Ids: 3
Order Id: 3
Products Ids: 4
Order Id: 1
Products Ids: 5
found orders
2
3
One method is to reduce the list to just the ids,
var list_of_details = new List<OrderInfo> {...};
var orderInfoIds = list_of_details.Select(d => d.ProductId);
context
.Orders
.Where(o => o.OrdersProducts.Any(p => orderInfoIds.Contains(p.ProductId)));
What is the best practice to convert Sub Query into LINQ,
for example I use this following query :
select VerID = (select top 1 x.INTERNALPACKINGSLIPID from
CUSTPACKINGSLIPVERSION x where a.RECID = x.CUSTPACKINGSLIPJOUR
order by x.VERSIONDATETIME desc),
c.LINENUM, c.RECID, *
from CUSTPACKINGSLIPJOUR a inner join CUSTPACKINGSLIPTRANS c
on a.PACKINGSLIPID = c.PACKINGSLIPID
I simulated you database with classes to get the syntax correct. Make modifications as necessary. There is no best method. Some people like using Where instead of joins. I like joins.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<CUSTPACKINGSLIPVERSIONs> CUSTPACKINGSLIPVERSION = new List<CUSTPACKINGSLIPVERSIONs>();
List<CUSTPACKINGSLIPJOURs> CUSTPACKINGSLIPJOUR = new List<CUSTPACKINGSLIPJOURs>();
List<CUSTPACKINGSLIPTRANSs> CUSTPACKINGSLIPTRANS = new List<CUSTPACKINGSLIPTRANSs>();
var VerId = (from vId in CUSTPACKINGSLIPVERSION
join slipId in CUSTPACKINGSLIPJOUR on vId.INTERNALPACKINGSLIPID equals slipId.RECID
join cId in CUSTPACKINGSLIPTRANS on vId.INTERNALPACKINGSLIPID equals cId.PACKINGSLIPID
select new { vid = vId, slipId = slipId, cId = cId })
.GroupBy(x => x.vid.VERSIONDATETIME)
.OrderBy(x => x.Key)
.FirstOrDefault()
.Select(x => new { linenum = x.cId.LINENUM, recid = x.cId.RECID })
.ToList();
}
}
public class CUSTPACKINGSLIPVERSIONs
{
public int INTERNALPACKINGSLIPID { get; set; }
public DateTime VERSIONDATETIME { get; set; }
}
public class CUSTPACKINGSLIPJOURs
{
public int RECID { get; set; }
public int PACKINGSLIPID { get; set; }
}
public class CUSTPACKINGSLIPTRANSs
{
public int PACKINGSLIPID { get; set; }
public int LINENUM { get; set; }
public int RECID { get; set; }
}
}
You have this in your query and I am assuming it is a typo: a.RECID = x.CUSTPACKINGSLIPJOUR because CUSTPACKINGSLIPJOUR is the collection name. Therefore, I used SomeId instead. But this should give you the idea:
Do a subquery within the projection. You will also need to order by descending and then take the first record-this will be like top 1. Here is the query:
var query = from a in CUSTPACKINGSLIPJOUR
join c in CUSTPACKINGSLIPTRANS on a.PACKINGSLIPID equals c.PACKINGSLIPID
select new
{
VerID = (from x in CUSTPACKINGSLIPVERSION
where a.RECID == x.SomeId select x)
.OrderByDescending(o => o.VERSIONDATETIME)
.First().INTERNALPACKINGSLIPID,
c.LINENUM,
c.RECID
};
You can test it with the following collections:
var CUSTPACKINGSLIPJOUR = new List<CUSTPACKINGSLIPJOUR> { new CUSTPACKINGSLIPJOUR { PACKINGSLIPID = 1, RECID = 1 },
new CUSTPACKINGSLIPJOUR { PACKINGSLIPID = 2, RECID = 2 }};
var CUSTPACKINGSLIPTRANS = new List<CUSTPACKINGSLIPTRANS>
{
new CUSTPACKINGSLIPTRANS { LINENUM = 1, RECID = 1, PACKINGSLIPID = 1 }
};
var CUSTPACKINGSLIPVERSION = new List<CUSTPACKINGSLIPVERSION>
{
new CUSTPACKINGSLIPVERSION { INTERNALPACKINGSLIPID = 1, SomeId = 1, VERSIONDATETIME = DateTime.Today.AddDays(-1) },
new CUSTPACKINGSLIPVERSION { INTERNALPACKINGSLIPID = 2, SomeId = 1, VERSIONDATETIME = DateTime.Today }
};
It will be the same query if you use a DbSet<T>.
Suppose you have those setup:
public class TBL_CUSTPACKINGSLIPVERSION
{
public int CUSTPACKINGSLIPVERSION_ID { get; set; }
public int INTERNALPACKINGSLIPID { get; set; }
public DateTime VERSIONDATETIME { get; set; }
}
public class TBL_CUSTPACKINGSLIPJOUR
{
public int RECID { get; set; }
public int PACKINGSLIPID { get; set; }
}
public class TBL_CUSTPACKINGSLIPTRANS
{
public int PACKINGSLIPID { get; set; }
public int LINENUM { get; set; }
public int RECID { get; set; }
}
var CUSTPACKINGSLIPVERSION = new List<TBL_CUSTPACKINGSLIPVERSION>();
var CUSTPACKINGSLIPJOUR = new List<TBL_CUSTPACKINGSLIPJOUR>();
var CUSTPACKINGSLIPTRANS = new List<TBL_CUSTPACKINGSLIPTRANS>();
Then you can setting up the combined query as given in example below (assumed CUSTPACKINGSLIPVERSION_ID is the identity key which used in comparison between CUSTPACKINGSLIPJOUR & CUSTPACKINGSLIPTRANS):
var query = (from a in CUSTPACKINGSLIPJOUR
join c in CUSTPACKINGSLIPTRANS
on a.PACKINGSLIPID equals c.PACKINGSLIPID
select new {
VerID = (from x in CUSTPACKINGSLIPVERSION
where a.RECID == x.CUSTPACKINGSLIPVERSION_ID // should be an identity column/primary key to compare with
orderby x.VERSIONDATETIME descending
select x).FirstOrDefault().INTERNALPACKINGSLIPID,
c.LINENUM,
c.RECID
}).ToList();
Note that your query includes * at the end of SELECT statement which may indicate the query will return all records from both tables involved in join clause. If you want to return entire records of CUSTPACKINGSLIPJOUR only but not return all CUSTPACKINGSLIPTRANS, include a into the select new statement (incorporates SQL usage of a.*):
select new {
VerID = (from x in CUSTPACKINGSLIPVERSION
where a.RECID == x.CUSTPACKINGSLIPVERSION_ID // should be an identity column/primary key to compare with
orderby x.VERSIONDATETIME descending
select x).FirstOrDefault().INTERNALPACKINGSLIPID,
c.LINENUM,
c.RECID,
a // used if you want to return all records from CUSTPACKINGSLIPJOUR
}).ToList();
I created an EF Join. The frameStart has 19 records and frameEnd has 42 records, but when I do the join ( framing ), there are only 10 records. I believe there are only 10 records because it is joining records that can be grouped with the matching key. The code is below. How do I make it so that when it does the join, I get a list of all the records, even the ones that do not "match"?
public class s84_Report_FrameLabor
{
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public int SubdivisionID { get; set; }
public string SubdivisionName { get; set; }
public int LotNumber { get; set; }
public string InstallManagerStart { get; set; }
public string InstallManagerComplete { get; set; }
public DateTime FrameLaborStart { get; set; }
public DateTime FrameLaborComplete { get; set; }
public int Duration { get; set; }
/*
Frame Labor Start ------ Product ID: 26
Frame Labor Complete ------ Product ID: 8
*/
public static List<s84_Report_FrameLabor> getDurationReport()
{
using (var context = PrimaryConnection.returnNewConnection())
{
var frameStart = (from c in context.s84_Schedule
where c.ProductID == 26 && c.Completed == false
select new
{
CustomerID = c.CustomerID,
CustomerName = c.s84_Customer.CustomerName,
SubdivisionID = c.SubdivisionID,
SubdivisionName = c.s84_Subdivision.SubdivisionName,
LotNumber = c.LotNumber,
FrameLaborStart = c.CustomerExpectedDate
}).ToList();
var frameEnd = (from c in context.s84_Schedule
where c.ProductID == 8 && c.Completed == false
select new
{
CustomerID = c.CustomerID,
SubdivisionID = c.SubdivisionID,
LotNumber = c.LotNumber,
FrameLaborComplete = c.CustomerExpectedDate
}).ToList();
var framing = from c in frameStart
join e in frameEnd on new { c.CustomerID, c.SubdivisionID, c.LotNumber } equals new { e.CustomerID, e.SubdivisionID, e.LotNumber }
select new s84_Report_FrameLabor
{
CustomerID = c.CustomerID,
CustomerName = c.CustomerName,
SubdivisionID = c.SubdivisionID,
SubdivisionName = c.SubdivisionName,
LotNumber = c.LotNumber,
FrameLaborStart = c.FrameLaborStart,
FrameLaborComplete = e.FrameLaborComplete,
Duration = (e.FrameLaborComplete - c.FrameLaborStart).Days
};
return framing.ToList();
}
}
}
Thanks to Andre, stuartd, and James R., I found out the solution was to use the EntityFramework DefaultIfEmpty().
var framing = from c in frameStart
join e in frameEnd on new { c.CustomerID, c.SubdivisionID, c.LotNumber } equals new { e.CustomerID, e.SubdivisionID, e.LotNumber } into jointable
from z in jointable.DefaultIfEmpty()
select new s84_Report_FrameLabor
{
CustomerID = c.CustomerID,
CustomerName = c.CustomerName,
SubdivisionID = c.SubdivisionID,
SubdivisionName = c.SubdivisionName,
LotNumber = c.LotNumber,
FrameLaborStart = c.FrameLaborStart,
FrameLaborComplete = z.FrameLaborComplete,
Duration = c.FrameLaborStart == null ? z.FrameLaborComplete == null ? (z.FrameLaborComplete - c.FrameLaborStart).Days : 0 : 0
};
return framing.ToList();